您的位置:首页 > 其它

一片关于懒加载不错的文章

2014-09-26 20:19 323 查看
前一段时间我参与的一个项目中使用了SSH(Spring2.5+Struts1.2+Hibernate3.1),对于数据库设计的很复杂,一大堆的外键,在测试的时候发现非常的慢,慢到难以忍受,最夸张的是一个计算投票结果并筛选复合条件的投票公告,点击后要等待30s,相信没人会想用这样的系统,这还是在本地机测试,虽然后来我简单的优化了一下投票存储和查询的方式,(响应时间降到了几秒),但是慢的真正原因并不在这里。其实我早就知道问题在Hibernate的延迟加载,很多资料里叫懒加载。我们所有的表都设置的是lazy=false,因为之前在写程序的时候一直发现无法访问到关联表的值,(比如有Person和Bulletin两张表,两者是一对多的关系),使用lazy=true时,person.bulletin会报异常,所以就武断的将所有表都设置成了lazy=false,结果可想而知,因为我们的数据库设计很复杂,表和表之间的关联关系很多,所以,我想象的到,所有表都设置为lazy=false可能会导致每次数据库操作都会查询数据库中所有的表,而且肯能还不止一次。既然意识到问题,就要解决,接下来的篇幅,我会边解决问题,边完成这篇文章。

首先我找到了传智博客里面的Hibernate教学视频,我发现里面将Hibernate后台执行的sql动作都打印到控制台上,我感觉对于发现我这个项目中的问题很有帮助,于是我在网上找到了配置的办法,很简单,就是在spring配置文件中加入一个属性就行:

[xhtml]
view plaincopy

<bean id="sessionFactory"

class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

<property name="dataSource">

<ref bean="dataSource" />

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.MySQLDialect

</prop>

<prop key="hibernate.show_sql">true</prop>

</props>

</property>

上面只是spring的配置文件的片段,需要添加的就是:

<prop key="hibernate.show_sql">true</prop>

配置好之后,运行程序,天呐!一个简单的登录操作,打印出的sql语句在MyEclipse的console里面打印翻了好几屏,里面见到了几乎所有的数据库表名,而且还不止一次,没办法统计,少说有上百条,这要是不慢才真见鬼呢....没想到一个延迟加载这么大的问题...看来之前的预计是完全正确的!

我在网上找到了一个自称的解决办法

在spring中通过getHibernateTemplate()来调用load和initialize,每一个getHibernateTemplate调用都会新开一个session,调用完就关闭了这个session.所以在第二次调用getHibernateTemplate().initialize来显示调用关联对象时就是报disconnected session的错。找到一个比较简单一点的解决的办法,就是用spring的OpenSessionInView. OpenSessionInViewFilter可以等到请求回到filte以后再把session关掉,在web.xml里面配置一下就好用了:

[xhtml] view
plaincopy

<filter>

<filter-name>hibernateFilter</filter-name>

<filter-class>org.springframework.orm.hibernate.support.OpenSessionInViewFilter</filter-class>

<init-param>

<param-name>singleSession</param-name>

<param-value>false</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>hibernateFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

但是我照如上改了之后, 却不行,数据库连接池连接不上,不知道为什么

继续...

经过一整天的奋斗,终于见了成果,但是还没有完全解决,先把手头的成果总结一下:

上面提到的数据库连接池连接不上,估计是数据库的问题,(没有具体研究),之前连接的数据库是别人机器上的,换成自己机器上的数据库,没有问题的。

上面提到的OpenSessionInViewFilter方法其实本没有错,但是就仅仅那样配是完全不够的!

步骤1:将所有的*.hbm.xml文件中的lazy="false"改成lazy="true"

步骤2:替换MyEclipse自动添加的jar包cglib-2.1.3.jar为cglib-2.2_beta1.jar,否则会出现如下异常

java.lang.NullPointerException org.hibernate.tuple.AbstractEntityTuplizer.createProxy(AbstractEntityTuplizer.java:372)

...

步骤3:在项目的web.xml中添加(注意这个Filter要在struts的Filter的前面)

[xhtml] view
plaincopy

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/applicationContext.xml</param-value>

</context-param>

lt;filter>

<filter-name>hibernateFilter</filter-name>

<filter-class>

org.springframework.orm.hibernate3.support.OpenSessionInViewFilter

</filter-class>

<!-- <init-param>

<param-name>singleSession</param-name>

<param-value>true</param-value>

</init-param> -->

</filter>

<filter-mapping>

<filter-name>hibernateFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

步骤4:将struts配置文件中的一段去掉(之前没有发现这里,导致配置完成后一部分懒加载还是抛异常)

javax.servlet.ServletException:

org.hibernate.LazyInitializationException:

could not initialize proxy - the owning Session was closed

[xhtml] view
plaincopy

<!--

<plug-in

className="org.springframework.web.struts.ContextLoaderPlugIn">

<set-property property="contextConfigLocation"

value="/WEB-INF/applicationContext.xml" />

</plug-in>

-->

至此,所有的页面访问全部都正常了,sql语句由原来的上百条减少到几条,(仅仅是那些必须的sql语句了),响应时间也由原来的几秒十几秒减少到了1秒以内,多数为0.5秒,有的减少到0.1秒以内。可见效果有多么明显!!但是现在所有的查询操作没问题了,但是update和save还是不行,还是哪里的配置不对,抛出的异常为:

javax.servlet.ServletException: org.springframework.dao.InvalidDataAccessApiUsageException:

Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL):

Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.

离胜利不远了,继续加油!!

今天终于解决了所有的问题,高兴!现在将昨天的问题总结一下。

javax.servlet.ServletException: org.springframework.dao.InvalidDataAccessApiUsageException:

Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL):

Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.

这个异常产生的主要原因是DAO采用了Spring容器的事务管理策略,如果操作方法的名称和事务策略中指定的被管理的名称不能够匹配上,spring 就会采取默认的事务管理策略(PROPAGATION_REQUIRED,read only).如果是插入和修改操作,就不被允许的,所以包这个异常

解决的办法是利用Spring的AOP,过滤那些update、delete、save、create等非只读操作,并将其设置正确的事务管理策略,而不是默认的设置。

在Spring配置文件applicationContext.xml中加入下面代码:

[xhtml] view
plaincopy

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

<property name="sessionFactory">

<ref bean="sessionFactory"/>

</property>

</bean>

<!-- 配置事务通知 -->

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="save*" propagation="REQUIRED" read-only="false"/>

<tx:method name="create*" propagation="REQUIRED" read-only="false"/>

<tx:method name="delete" propagation="REQUIRED" read-only="false"/>

<tx:method name="find*" propagation="REQUIRED" read-only="true"/>

<tx:method name="list*" propagation="REQUIRED" read-only="true"/>

<tx:method name="get*" propagation="REQUIRED" read-only="true"/>

<tx:method name="*" read-only="true"/>

</tx:attributes>

</tx:advice>

<!--添加事务-->

<aop:config>

<!--切入点-->

<aop:pointcut id="txPointCut" expression="execution(* cn.gov.ggj.service.impl.*.*(..))"/>

<!--通知器-->

<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>

</aop:config>

Eclipse不能识别<tx:advice/>标签

在开发Spring的过程中,有时会出现Eclipse不能识别<tx:advice/>标签。

提示出现以下错误:

The prefix "tx" for element "tx:advice" is not bound

原因是<beans>中要加入“xmlns:aop”的命名申明,并在“xsi:schemaLocation”中指定aop配置的schema的地址

配置文件如下:

[xhtml] view
plaincopy

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

至此不会再报错了,下面解释一下aop中的设置

Spring使用 <tx:advice>和 <aop:config> 用来配置事务,具体如何配置你可以参考Spring文档。

我解释一下(* cn.gov.ggj.service.impl.*.*(..))中几个通配符的含义:

第一个 * —— 通配 任意返回值类型

第二个 * —— 通配 包cn.gov.ggj.service.impl下的任意class

第三个 * —— 通配 包cn.gov.ggj.service.impl下的任意class的任意方法

第四个 .. —— 通配 方法可以有0个或多个参数

综上:包cn.gov.ggj.service.impl下的任意class的具有任意返回值类型、任意数目参数和任意名称的方法

之所以用一个Filter来解决Hibernate懒加载的问题,见下面文章的解释:

Hibernate的强大之处之一是懒加载功能,可以有效的降低数据库访问次数和内存使用量。但用的不好就会出现org.hibernate.LazyInitializationException。

这个异常出现的原因很简单,主要时在加载懒数据时Session已经关闭造成的,如下图:



那么OK,我们来考虑怎么解决吧。

我们只要在渲染JSP之前不要关闭Session,而在JSP渲染之后再关闭就OK啊。我们知道,在JSP/Servlet中,可以配置过滤器来实现这种功能。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: