本文主要研究一下spring data jpa的OpenSessionInViewjava
spring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.javagit
@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; //...... }
默認爲true
),用於決定是否註冊OpenEntityManagerInViewInterceptor,它會一個請求線程綁定一個JPA EntityManagerspring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.javagithub
@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()); } } } //...... }
spring-orm-5.1.6.RELEASE-sources.jar!/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.javaweb
public 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; } }
定義了afterConcurrentHandlingStarted方法
);AsyncWebRequestInterceptor繼承了WebRequestInterceptor(定義了preHandle、postHandle、afterCompletion方法
)openSession
),而後使用TransactionSynchronizationManager.bindResource進行綁定若是有的話
),當count爲0的時候移除該attribute;若是request沒有count則使用TransactionSynchronizationManager.unbindResource進行解綁,而後關閉EntityManager;異步的afterConcurrentHandlingStarted方法也相似,主要是進行unbind操做openSession
),而後使用TransactionSynchronizationManager.bindResource綁定到當前線程;afterCompletion方法會使用TransactionSynchronizationManager.unbindResource進行解綁,而後關閉EntityManager經過OSIV技術來解決LazyInitialization問題會致使open的session生命週期過長,它貫穿整個request,在view渲染完以後才能關閉session釋放數據庫鏈接;另外OSIV將service層的技術細節暴露到了controller層,形成了必定的耦合,於是不建議開啓,對應的解決方案就是在controller層中使用dto,而非detached狀態的entity,所需的數據再也不依賴延時加載,在組裝dto的時候根據須要顯式查詢