序本文主要研究一下spring data jpa的OpenSessionInViewOpen Session In ViewOpen Session In View简称OSIV,是为了解决在mvc的controller中使用了hibernate的lazy load的属性时没有session抛出的LazyInitializationException异常;对hibernate来说ToMany关系默认是延迟加载,而ToOne关系则默认是立即加载JpaPropertiesspring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java@ConfigurationProperties(prefix = “spring.jpa”)public class JpaProperties { /** * Additional native properties to set on the JPA provider. / private Map<String, String> properties = new HashMap<>(); /* * Mapping resources (equivalent to “mapping-file” entries in persistence.xml). / private final List<String> mappingResources = new ArrayList<>(); /* * Name of the target database to operate on, auto-detected by default. Can be * alternatively set using the “Database” enum. / private String databasePlatform; /* * Target database to operate on, auto-detected by default. Can be alternatively set * using the “databasePlatform” property. / private Database database; /* * Whether to initialize the schema on startup. / private boolean generateDdl = false; /* * Whether to enable logging of SQL statements. / private boolean showSql = false; /* * Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the * thread for the entire processing of the request. / private Boolean openInView; //……}JpaProperties有一个配置项为openInView(默认为true),用于决定是否注册OpenEntityManagerInViewInterceptor,它会一个请求线程绑定一个JPA EntityManagerJpaBaseConfigurationspring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java@Configuration@EnableConfigurationProperties(JpaProperties.class)@Import(DataSourceInitializedPublisher.Registrar.class)public abstract class JpaBaseConfiguration implements BeanFactoryAware { //…… @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(WebMvcConfigurer.class) @ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class }) @ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class) @ConditionalOnProperty(prefix = “spring.jpa”, name = “open-in-view”, havingValue = “true”, matchIfMissing = true) protected static class JpaWebConfiguration { // Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when // not on the classpath @Configuration protected static class JpaWebMvcConfiguration implements WebMvcConfigurer { private static final Log logger = LogFactory .getLog(JpaWebMvcConfiguration.class); private final JpaProperties jpaProperties; protected JpaWebMvcConfiguration(JpaProperties jpaProperties) { this.jpaProperties = jpaProperties; } @Bean public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() { if (this.jpaProperties.getOpenInView() == null) { logger.warn(“spring.jpa.open-in-view is enabled by default. " + “Therefore, database queries may be performed during view " + “rendering. Explicitly configure " + “spring.jpa.open-in-view to disable this warning”); } return new OpenEntityManagerInViewInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addWebRequestInterceptor(openEntityManagerInViewInterceptor()); } } } //……}JpaBaseConfiguration里头有个JpaWebMvcConfiguration配置,在web application的类型是Type.SERVLET的时候,且spring.jpa.open-in-view不是false的时候注册OpenEntityManagerInViewInterceptor,然后添加到mvc的webRequestInterceptor中OpenEntityManagerInViewInterceptorspring-orm-5.1.6.RELEASE-sources.jar!/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.javapublic class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor { /* * Suffix that gets appended to the EntityManagerFactory toString * representation for the “participate in existing entity manager * handling” request attribute. * @see #getParticipateAttributeName / public static final String PARTICIPATE_SUFFIX = “.PARTICIPATE”; @Override public void preHandle(WebRequest request) throws DataAccessException { String key = getParticipateAttributeName(); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); if (asyncManager.hasConcurrentResult() && applyEntityManagerBindingInterceptor(asyncManager, key)) { return; } EntityManagerFactory emf = obtainEntityManagerFactory(); if (TransactionSynchronizationManager.hasResource(emf)) { // Do not modify the EntityManager: just mark the request accordingly. Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST); int newCount = (count != null ? count + 1 : 1); request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST); } else { logger.debug(“Opening JPA EntityManager in OpenEntityManagerInViewInterceptor”); try { EntityManager em = createEntityManager(); EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(emf, emHolder); AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder); asyncManager.registerCallableInterceptor(key, interceptor); asyncManager.registerDeferredResultInterceptor(key, interceptor); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException(“Could not create JPA EntityManager”, ex); } } } @Override public void postHandle(WebRequest request, @Nullable ModelMap model) { } @Override public void afterCompletion(WebRequest request, @Nullable Exception ex) throws DataAccessException { if (!decrementParticipateCount(request)) { EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory()); logger.debug(“Closing JPA EntityManager in OpenEntityManagerInViewInterceptor”); EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); } } private boolean decrementParticipateCount(WebRequest request) { String participateAttributeName = getParticipateAttributeName(); Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); if (count == null) { return false; } // Do not modify the Session: just clear the marker. if (count > 1) { request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST); } else { request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); } return true; } @Override public void afterConcurrentHandlingStarted(WebRequest request) { if (!decrementParticipateCount(request)) { TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory()); } } /* * Return the name of the request attribute that identifies that a request is * already filtered. Default implementation takes the toString representation * of the EntityManagerFactory instance and appends “.FILTERED”. * @see #PARTICIPATE_SUFFIX */ protected String getParticipateAttributeName() { return obtainEntityManagerFactory().toString() + PARTICIPATE_SUFFIX; } private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) { CallableProcessingInterceptor cpi = asyncManager.getCallableInterceptor(key); if (cpi == null) { return false; } ((AsyncRequestInterceptor) cpi).bindEntityManager(); return true; }}OpenEntityManagerInViewInterceptor继承了抽象类EntityManagerFactoryAccessor,实现了AsyncWebRequestInterceptor接口(定义了afterConcurrentHandlingStarted方法);AsyncWebRequestInterceptor继承了WebRequestInterceptor(定义了preHandle、postHandle、afterCompletion方法)preHandle方法会判断当前线程是否有EntityManagerFactory,如果有的话则会在request的attribute中维护count;如果没有的话则会创建EntityManager(openSession),然后使用TransactionSynchronizationManager.bindResource进行绑定afterCompletion方法会先对request attribute中的count进行递减(如果有的话),当count为0的时候移除该attribute;如果request没有count则使用TransactionSynchronizationManager.unbindResource进行解绑,然后关闭EntityManager;异步的afterConcurrentHandlingStarted方法也类似,主要是进行unbind操作小结对hibernate来说ToMany关系默认是延迟加载,而ToOne关系则默认是立即加载;而在mvc的controller中脱离了persisent contenxt,于是entity变成了detached状态,这个时候要使用延迟加载的属性时就会抛出LazyInitializationException异常,而Open Session In View指在解决这个问题JpaBaseConfiguration里头有个JpaWebMvcConfiguration配置,在web application的类型是Type.SERVLET的时候,且spring.jpa.open-in-view不是false的时候注册OpenEntityManagerInViewInterceptor,然后添加到mvc的webRequestInterceptor中OpenEntityManagerInViewInterceptor的preHandle方法会判断当前线程是否有EntityManagerFactory,如果没有则会创建EntityManager(openSession),然后使用TransactionSynchronizationManager.bindResource绑定到当前线程;afterCompletion方法会使用TransactionSynchronizationManager.unbindResource进行解绑,然后关闭EntityManager通过OSIV技术来解决LazyInitialization问题会导致open的session生命周期过长,它贯穿整个request,在view渲染完之后才能关闭session释放数据库连接;另外OSIV将service层的技术细节暴露到了controller层,造成了一定的耦合,因而不建议开启,对应的解决方案就是在controller层中使用dto,而非detached状态的entity,所需的数据不再依赖延时加载,在组装dto的时候根据需要显式查询docEager/Lazy Loading In HibernateOpen Session in ViewOpen Session In View模式的基本常识The Open Session In View Anti-PatternOpen Session In View Design TradeoffsWhy is Hibernate Open Session in View considered a bad practice?Log a warning on startup when spring.jpa.open-in-view is enabled but user has not explicitly opted in #7107SPRING BOOT BEST PRACTICE – DISABLE OSIV TO START RECEIVING LAZYINITIALIZATIONEXCEPTION WARNINGS AGAIN