聊聊spring data jpa的OpenSessionInView
阿新 • • 發佈:2019-04-14
序
本文主要研究一下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 WARN