本文主要研究一下spring data jpa的open-in-view
spring-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; //...... }
默认为true
),用于决定是否注册OpenEntityManagerInViewInterceptor,它会一个请求线程绑定一个JPA EntityManager spring-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()); } } } //...... }
spring-orm-5.1.6.RELEASE-sources.jar!/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.java
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
通过OSIV技术来解决LazyInitialization问题会导致open的session生命周期过长,它贯穿整个request,在view渲染完之后才能关闭session释放数据库连接;另外OSIV将service层的技术细节暴露到了controller层,造成了一定的耦合,因而不建议开启,即controller层使用dto,而非detached状态的entity,所需的数据不再依赖延时加载,在组装dto的时候根据需要显式查询