使用OpenSessionInViewFilter方案解决Hibernate懒加载异常的问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用OpenSessionInViewFilter方案解决Hibernate懒加载异常的问题相关的知识,希望对你有一定的参考价值。

参考技术A

当一个请求来了之后,先执行Action,在执行结果。在action里面有Service业务层,调用Service,Service做业务处理。

开始执行Service方法的时候,开始开启事务和Session,Service方法结束或回滚提交事务,会自动关闭Session。

在Service里面查询列表加载对象的时候,但是其相关连的对象并没有加载,但是Session关闭了,关联对象最终没有加载,在页面中用到了懒加载属性,但是是在之前加载的,且Session已经关闭了,所以有了懒加载异常,说没有Session。

从上面的异常说明中可以得知,主要原因是在页面中没有Session,那么我们可以使Session不关闭,不关闭Session又会出现问题,那么我们就在整个请求的过程中添加一个过滤器或者拦截器,过滤器或拦截器是先进后出。我们在过滤器或拦截器中关闭Session,也就是在当页面显示一些数据后,再在过滤器或拦截器里面关闭Session就可以了。但是需要设置当事务提交之后,不需要关闭Session。在spring中已经有一个过滤器可以帮助我们在过滤器中关闭Session了。OpenSessionInViewFilter

注意: 拦截的是所有的action,而且在Action里面调用的是Service,与struts的配置Action的扩展名一样

是否可以在 Spring 应用程序上下文中配置 OpenSessionInViewFilter 以便 context:property-placeholder 可用?

【中文标题】是否可以在 Spring 应用程序上下文中配置 OpenSessionInViewFilter 以便 context:property-placeholder 可用?【英文标题】:Is it possible to configure OpenSessionInViewFilter in Spring application context so that context:property-placeholder is useable? 【发布时间】:2014-02-21 15:44:13 【问题描述】:

初步情况

我的 Web 应用程序包含 Maven 模块 myapp-persistence(.jar)、myapp-model(.jar)、myapp-service(.jar) 和 myapp-web(.war) 以获得传统的、松散耦合的多层架构。所有模块都由一个父 Maven 模块连接在一起,它只包含父 POM 以及所有子模块的通用定义。

尤其是 myapp-service(.jar) 和 myapp-persistence(.jar) 拥有自己的可配置 (!) 应用程序上下文部分以及所需的对象。两个 jar 必须可以使用包含的变量定义进行部署,换句话说,这些 jar 不能具有变量的具体值。

myapp-service-context.xml 用服务器 URL 的变量声明了一个 solrServer bean:

<bean id="solrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
    <constructor-arg value="$solr.serverUrl" />
    <property name="connectionTimeout" value="60000"/>
    <property name="defaultMaxConnectionsPerHost" value="40"/>
    <property name="maxTotalConnections" value="40"/>
</bean>

myapp-persistence-context.xml 定义了一个带有连接变量的dataSource

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">      
    <property name="driverClassName" value="$jdbc.driverClassName" />
    <property name="url" value="$jdbc.url" />
    <property name="username" value="$jdbc.username" />
    <property name="password" value="$jdbc.password" />       
</bean>
...
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    ...
</bean>

myapp-web(.war) 引用 myapp-service(.jar) 和 myapp-persistence(.jar)。在 myapp-servlet.xml 中,它包括它们的应用程序上下文部分,并通过属性文件为声明的 bean 的配置提供属性值。通过 context:property-placeholder Spring 在内存中创建应用程序上下文时使用具体值初始化所有变量。

<context:property-placeholder location="classpath*:myapp-configuration.properties" />
<import resource="classpath*:myapp-persistence-context.xml"/>
<import resource="classpath*:myapp-service-context.xml"/>

对于开发配置文件,具体的 myapp-configuration.properties 可能如下所示:

solr.serverUrl=http://localhost:8983/solr
jdbc.dialect=org.hibernate.dialect.HSQLDialect
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:mem:myapp
jdbc.username=sa
jdbc.password=

这种配置是 imo 直截了当的并且可以工作 - 没有视图。当 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter 发挥作用时,问题就出现了。

问题描述

OpenSessionInViewFilter 确保在控制器处理期间未在打开事务中加载的对象图中的实例可以延迟加载,如果视图试图显示这些对象的内容(请参阅[1] )。正如经常描述的那样,这个过滤器在 delpoyment 描述符 web.xml 中声明(参见[2]):

<filter>
    <filter-name>OpenSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>OpenSessionInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

如果 myapp-persistence-context.xml 像上面一样包含在 myapp-servlet.xml 中,那么 context:property-placeholder 可以工作, OpenSessionInViewFilter 没有找到必要的sessionFactory。原因似乎是Spring首先处理web.xml,然后是myapp-servlet.xml,它导入myapp-persistence-context.xml .不幸的是,我无法通过参考来证明这个猜测。抛出以下异常:

坟墓:servlet [myapp] 在路径 [/myapp] 的上下文中的 Servlet.service() 引发异常 org.springframework.beans.factory.NoSuchBeanDefinitionException:没有定义名为“sessionFactory”的bean 在 org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529) 在 org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095) 在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:277) 在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) 在 org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1097) 在 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:242) 在 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.lookupSessionFactory(OpenSessionInViewFilter.java:227) 在 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:171) 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) 在 org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) 在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) 在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) 在 org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) 在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) 在 org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) 在 org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936) 在 org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) 在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407) 在 org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004) 在 org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) 在 org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310) 在 java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 在 java.lang.Thread.run(Thread.java:662)

不同的应用程序上下文部分通常包含在带有 ContextLoaderListener 的部署描述符中,而不是 myapp-servlet.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:myapp-service-context.xml, 
        classpath*:myapp-persistence-context.xml
    </param-value>
</context-param>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>

不幸的是,有了这种配置,Spring 的 context:property-placeholder 机制似乎不再起作用了。

目标和问题

myapp-persistence(.jar) 和 myapp-service(.jar) 这样的模块必须在运行时通过引用上下文使用属性文件进行配置,例如myapp-web(.war) 的应用程序上下文。

问题是:是否可以在 Spring 应用上下文中配置 OpenSessionInViewFilter 使 context:property-placeholder 仍然可用?

或者:如果应用上下文部分包含在部署描述符 web.xml 中,Spring 如何在运行时初始化应用上下文中的变量?

从根本上说:为什么OpenSessionInViewFilter实际上是必须配置的,为什么Spring MVC开箱即用不透明支持视图延迟加载?

预期的评论

编译时的属性替换不是重点。配置文件依赖属性文件已使用 Maven 过滤创建。

dataSourcesolrServer 声明移动到 myapp-servlet.xml 中(请参阅[3]、[4])不是一个可接受的解决方案,因为它破坏了 myapp-persistence(.jar) 和 myapp-service(.jar) 的模块化和独立可测试性 - 实际上是依赖注入的精神!

【问题讨论】:

【参考方案1】:

您当然应该将sessionFactory 和非Web 相关bean 保留在由web.xml 中的contextConfigLocation 定义的根应用程序上下文中。除了模块化方面(正如您提到的),OpenSessionInViewFilter 需要这种方式,因为它在根 Web 应用程序上下文中查找 sessionFactory 和 it will error if it can't find it there - 正如您发现的那样。所以你的contextConfigLocation 设置是正确的方法。

PropertyPlaceholderConfigurerBeanFactoryPostProcessor,这意味着它在定义它的 bean 工厂的上下文中工作。在这种情况下,它是在 myapp-servlet.xml 中定义的,这意味着它将在 Web 上下文中工作,但它不会解析根应用程序上下文中的占位符(其中 dataSource 和 @ 987654331@ 已定义)。

我的建议是将 &lt;context:property-placeholder&gt; 从 web 移动到根应用程序上下文 - 但参数化 location 允许由封闭应用程序设置。例如,您可以将其添加到您的 myapp-service-context.xml

<context:property-placeholder location="$props.file"/>

然后您可以将其留给 myapp-web.war(或任何父应用程序)来设置文件的位置。例如,这可以作为系统属性来完成:

-Dprops.file=file:C:/myapp-configuration.properties

【讨论】:

这是一个很好的解决方案,因为配置是完全外部化的。谢谢!【参考方案2】:

与同事交谈带来了解决方案:如果我使用 Spring 的拦截器而不是 Servlet-API 的过滤器机制,context:property-placeholder 仍然可用。我从 web.xml 中删除了 contextConfigLocation 中的 myapp-persistence-context.xmlOpenSessionInViewFilter 的引用,并在 myapp 中声明了一个 OpenSessionInViewInterceptor -persistence-context.xml:

<mvc:interceptors>
    <bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</mvc:interceptors>

myapp-persistence(.jar)的类路径中带有myapp-persistence-context.xmlmyapp-servlet.xml中的import语句 如上所述,Spring 按照预期在运行时将所有属性变量替换为 myapp-configuration.properties 中的值。模块化被保存在最好的状态! Will Keeling´s externalization of the property file 完成项目设置。

另见讨论Spring HandlerInterceptor vs Servlet Filters 和Spring doc。

【讨论】:

您的朋友已经向您展示了在任何应用程序中处理休眠会话的正确方法。我通常更喜欢 Interceptor 方式,因为它是一种更安全的方法,具有松散耦合的架构,只需配置一些 spring bean。现在,使用 Java 配置风格,它比 any XML 配置更安全。

以上是关于使用OpenSessionInViewFilter方案解决Hibernate懒加载异常的问题的主要内容,如果未能解决你的问题,请参考以下文章

hibernate的OpenSessionInViewFilter用于管理session

使用OpenSessionInViewFilter方案解决Hibernate懒加载异常的问题

OpenSessionInViewFilter要怎么配置

OpenSessionInViewFilter配置和作用

Hibernate OpenSessionInViewFilter 过早关闭会话?

ssh中org.springframework.orm.hibernate4.support.OpenSessionInViewFilter的作用及配置