使用@Transactional注解的Spring OpenSessionInViewFilter

12

关于在服务层使用 @Transactional 注解时,如何结合 Spring 的 OpenSessionInViewFilter 进行处理。

我在 Stack Overflow 上查询了很多帖子,但仍然不确定是否应该使用 OpenSessionInViewFilter 来避免 LazyInitializationException 异常。如果有人能帮我回答以下问题,那将是很大的帮助:

  • 在具有复杂架构的应用程序中,使用 OpenSessionInViewFilter 是否是一种不好的实践?
  • 使用这个过滤器会导致 N+1 问题吗?
  • 如果我们使用 OpenSessionInViewFilter,是否意味着不需要使用 @Transactional

下面是我的 Spring 配置文件:

<context:component-scan base-package="com.test"/>
<context:annotation-config/>
 <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" value="resources/messages" />
        <property name="defaultEncoding" value="UTF-8" />
    </bean>
 <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
        p:location="/WEB-INF/jdbc.properties" />
 <bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.databaseurl}" p:username="${jdbc.username}"
        p:password="${jdbc.password}" />
       <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />     
        <property name="configLocation">
            <value>classpath:hibernate.cfg.xml</value>
        </property>
        <property name="configurationClass">
            <value>org.hibernate.cfg.AnnotationConfiguration</value>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${jdbc.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <!--
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                 -->
            </props>
        </property>
    </bean>
 <tx:annotation-driven /> 
 <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />

  </bean>
4个回答

13

OpenSessionInView 是一个servlet过滤器,只是打开一个Hibernate会话并将其存储在为服务请求的线程中的SessionHolder中。有了这个打开的会话,当在请求的呈现阶段使用时,Hibernate可以读取惰性初始化的集合和对象。当调用 SessionFactory.getCurrentSession() 时,可以访问此会话。

但是,OpenSessionInView只是打开会话,它不开始任何事务。打开会话后,您可以从数据库中读取对象。但是,如果要在事务中执行某些操作,则需要使用@Transactional注释或其他机制来标记事务的开始和结束时间。

然后回答问题:

在具有复杂模式的应用程序中使用OpenSessionInViewFilter是否是一种不好的做法?

如果您需要避免LazyInitializationException并且负载仅是打开新的Hibernate会话并在每个请求结束时关闭它,则这是一个好的实践。

使用此过滤器可能会导致N+1问题

我在许多项目中使用此过滤器,没有出现任何问题。

如果我们使用OpenSessionInViewFilter,是否意味着不需要使用@Transactional?

不需要。您只有一个Hibernate会话打开在线程的SessionHolder中,但是如果您需要事务,则需要放置@Transactional


谢谢您详细的回答。抱歉我在问题中忘记提及另一件事情了。我需要同时更改会话模式吗?我注意到人们在使用OpenSessionInView过滤器时遇到了更新和保存问题。 - Gautam
你说的“session mode”是什么意思? - Fernando Rincon
@Fernando,谢谢您的回复。我查看了一些帖子并注意到这段代码session.setFlushMode(FlushMode.AUTO);为什么我们需要显式设置会话刷新模式?抱歉,是会话刷新模式。 - Gautam
2
默认情况下,当通过OpenSessionInView打开会话时,flushMode被设置为MANUAL,因为它的目的仅是读取对象而不写入任何数据库内容。如果您使用HibernateTransactionManager并且所有修改都包含在事务中,则无需更改刷新模式,因为HibernateTransactionManager在启动新事务时会更改此模式。但是,如果您在事务之外更改对象(这不是一个好习惯),则需要将刷新模式设置为AUTO。 - Fernando Rincon

4

Fernando Rincon的优秀回答上补充我的0.02美分:

你不应该仅仅因为需要解决LazyInitializationException而使用OpenSessionInView过滤器。这只会给你的系统增加另一层混乱和复杂性。你应该从系统设计中准确知道前端何时需要访问集合。从那里开始,构建一个控制器方法来调用服务方法来检索你的集合会更容易且更符合逻辑(根据我的经验)。

然而,如果你有使用OpenSessionInView解决其他问题,并且幸运地打开了一个会话,那么我认为使用它来访问你的集合是没有害处的。但是,如果你在一个地方使用OpenSessionInView来获取一个集合对象,那么你应该重构其他地方的代码,以便在整个应用程序中标准化获取集合的策略。

请权衡重构的成本和编写控制器及服务方法的成本,以确定是否应使用OpenSessionInView过滤器。

2
OpenSessionInViewFilter的典型使用模式是某个实体被延迟加载,但在视图渲染阶段,视图需要这个实体的一些属性,而这些属性最初没有被加载,因此需要从数据库中获取这些数据。通常情况下,事务划分发生在Web应用程序的服务层,因此在视图渲染时,视图正在使用一个分离的实体,这会导致访问未加载属性时出现LazyInitializationException异常。可以从以下URL中了解更多信息:https://developer.jboss.org/wiki/OpenSessionInView 问题
在典型的 Web 应用程序中,常见的问题是在主要逻辑完成后渲染视图,因此 Hibernate Session 已关闭并且数据库事务已结束。如果您在 JSP(或任何其他视图呈现机制)中访问已从 Session 中加载的分离对象,则可能会遇到未加载的集合或未初始化的代理。您会收到的异常是:LazyInitializationException:Session 已关闭(或非常相似的消息)。当然,在您已经结束工作单元后,这是可以预料的。
第一个解决方案是为呈现视图打开另一个工作单元。这很容易做到,但通常不是正确的方法。完成操作后应将视图呈现放在第一个工作单元内,而不是单独的工作单元中。在两层系统中,通过 Session 访问数据,执行操作以及呈现视图都在同一虚拟机中,解决方案是保持 Session 处于打开状态,直到视图已被呈现。
作为另一种选择,考虑仅加载实体所需的视图数据量。这可以通过使用DTO投影来实现。本文列出了使用Open Session In View模式的一些缺点: https://vladmihalcea.com/the-open-session-in-view-anti-pattern/

2
OpenSessionInViewFilter是一个Servlet过滤器,它将Hibernate会话绑定到HTTP请求上,并且对于所有的数据库操作,事务性和非事务性,都使用相同的Hibernate会话来处理给定的HTTP请求。这暴露了数据库层到Web层,使其成为反模式。
我的经验是,当我们想要更改Java对象但不希望在数据库中反映出来时,这使得代码难以调试。由于Hibernate会话始终处于打开状态,它期望刷新数据库中的数据。
只有当JS基础的REST服务没有服务层时才应该使用此功能。

感谢您的建议,非常感激。 - Gautam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接