序
本文主要研究一下spring data jpa的OpenSessionInView
Open Session In View
- Open Session In View简称OSIV,是为了解决在mvc的controller中使用了hibernate的lazy load的属性时没有session抛出的LazyInitializationException异常;对hibernate来说ToMany关系默认是延迟加载,而ToOne关系则默认是立即加载
JpaProperties
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;
//......
}
- JpaProperties有一个配置项为openInView(
默认为true
),用于决定是否注册OpenEntityManagerInViewInterceptor,它会一个请求线程绑定一个JPA EntityManager
JpaBaseConfiguration
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());
}
}
}
//......
}
- JpaBaseConfiguration里头有个JpaWebMvcConfiguration配置,在web application的类型是Type.SERVLET的时候,且spring.jpa.open-in-view不是false的时候注册OpenEntityManagerInViewInterceptor,然后添加到mvc的webRequestInterceptor中
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;
}
}
- 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的时候根据需要显式查询
doc
- Eager/Lazy Loading In Hibernate
- Open Session in View
- Open Session In View模式的基本常识
- The Open Session In View Anti-Pattern
- Open Session In View Design Tradeoffs
- Why 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 #7107
- SPRING BOOT BEST PRACTICE – DISABLE OSIV TO START RECEIVING LAZYINITIALIZATIONEXCEPTION WARNINGS AGAIN
来源:oschina
链接:https://my.oschina.net/u/2922256/blog/3036555