前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊spring data jpa的OpenSessionInView

聊聊spring data jpa的OpenSessionInView

原创
作者头像
code4it
修改2019-04-13 23:56:52
3.8K0
修改2019-04-13 23:56:52
举报
文章被收录于专栏:码匠的流水账

本文主要研究一下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

代码语言:javascript
复制
@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

代码语言:javascript
复制
@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

代码语言:javascript
复制
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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Open Session In View
  • JpaProperties
  • JpaBaseConfiguration
  • OpenEntityManagerInViewInterceptor
  • 小结
  • doc
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档