Hibernate延迟加载以及利用Spring事务完美解决延迟加载问题

来源:互联网 发布:淘宝为什么没有以纯 编辑:程序博客网 时间:2024/05/22 08:07

   延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。

下面先来看个例子:

   /**DAO方法*/

public Emp findById(java.lang.Long id) {

try {

Emp instance = 

(Emp) getHibernateTemplate().get("com.sxy.dao.Emp",id);

return instance;

     catch (RuntimeException re) {

 throw re;

 }

}

/**业务类*/

public class EmpServiceImpl implements IEmpService {

/**注入DAO类,这里整合了Spring,如果不整合Spring对象全部可以new出来*/

private EmpDAO empDAO;

public Emp searchEmpById(Long id)

{

Emp emp=empDAO.findById(id);

return emp;

}

public void setEmpDAO(EmpDAO empDAO) {

this.empDAO = empDAO;

}

}

保证Emp类里dept字段映射是这样的(lazy="proxy",默认为proxy;fetch="select")

<many-to-one name="dept" class="com.sxy.dao.Dept" 

   fetch="select" lazy="proxy">

   <column name="DEPTNO" precision="2" scale="0" />

</many-to-one>

/**模拟客户main方法调用*/

public static void main(String[] args) {

/**获得Spring上下文*/

String app = "applicationContext.xml";

ApplicationContext ctx = new ClassPathXmlApplicationContext(app);

/**到ico容器中获得业务类事例*/

IEmpService service = (IEmpService) ctx.getBean("EmpServiceImpl");

/**调用业务方法查询雇员*/

Emp emp = service.searchEmpById(7788L);

System.out.println("雇员姓名:" + emp.getEname());

System.out.println("所属部门:" + emp.getDept().getDname());

}

启动运行出现异常,意思说,无法初始化代理,Session已关闭!

雇员姓名:SCOTT

Exception in thread "main"

org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed

很显然错误是出在输出部门信息的时候,由于配置映射里设置了lazy="proxy",即使用代理,此时查询雇员信息的时候不会去查询部门信息,部门信息将在使用时才到数据库里查询,当我们在客户端调用时Session已被关闭,因此当再次通过代理查询数据库时抛出异常,

下面来做个测试,利用log4j输出运行日志,log4j.properties配置如下:

log4j.rootLogger=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n

在main方法第一句写上

PropertyConfigurator.configure("log4j.properties"); 

业务类添加输出语句

System.out.println("开始业务方法......");

Emp emp=empDAO.findById(id);

System.out.println("结束业务方法......");

运行后,输出部分信息如下,可以看到在结束调用DAO类的查询方法后Session就关闭了

开始业务方法......

....

org.springframework.orm.hibernate3.SessionFactoryUtils - 

Closing Hibernate Session

结束业务方法......

好,现在知道问题出在什么地方了,有多种方式可以解决这个问题,

1,设置fetch="join"

2,设置lazy="false"

3,修改DAO类的查询方法

4,OpenSessionInView

5,利用Spring事务机制

...我暂时就知道这么多,下面来介绍这几种方法,

1,fetch这个属性设置已什么方式去取得数据,默认是select,我们设置为join表示联接查询,因此当查询雇员的时候同时也把部门联接查询出来了,这样查询方式显然没用到延迟加载功能,因此效率还是没得到优化,当设置成join时lazy属性无效

2,lazy="false" 表示不使用代理,即不使用延时加载,因此效率也得不到提高,特别是 one-to-many或many-to-many的时候效率很低,如 查询部门名称,如果是false,与

此部门相关的雇员也会全部查询出来,但现在并不需要雇员信息,如果该部门下的雇员信息很多的情况下将极大地影响性能.

3,我们可以考虑这样,在调用方法到时候告诉Hibernate是否需要部门信息,因为此时

  Session还未被关闭

public Emp findById(java.lang.Long id,boolean isDepet) {

 Emp instance = 

(Emp) getHibernateTemplate().get("com.sxy.dao.Emp",id);

/**如果需要部门信息*/

if(isDepet==true)

   /**延迟加载的属性返回的是代理,在这里调用方法初始化装载数据*/

Hibernate.initialize(instance.getDept());

return instance;

}

这样添加多一个参数编写有点繁琐,几乎每个方法都要这样写,而且调用显得较麻烦

4,OpenSessionInView事务不能及时关闭,事务使得一些数据加锁,加锁的数据得不到及时的访问,此外Session存在的时间也大大的延长,Session里面有个一级缓存,如果查询有很多信息将长时间占用大量内存,直到返回到客户端后才释放,Session存在时间和客户端的网速度有关,另外数据库连接得不到及时释放.

5,利用Spring的事务机制,这种方法虽然配置麻烦点,但从设计模式,性能和易用性来讲都是特别优秀的,下面详细介绍.

<!-- 修改业务类id,把原来的名称留给代理使用,避免修改程序代码 -->

<bean id="EmpServiceImplTarget" 

                              class="com.sxy.service.EmpServiceImpl">

<!-- 注入业务类需要的DAO对象 -->

<property name="empDAO" ref="EmpDAO"></property>

</bean>

<!-- 创建事务管理器实例 -->

<bean id="tranManager" 

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

    <!-- 注入事务管理器需要的SessionFactory对象-->

<property name="sessionFactory" ref="sessionFactory"></property>

</bean>

<!-- 业务类代理,代理工厂Bean对象,产生代理 -->

<bean id="EmpServiceImpl" 

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

  <!-- 注入代理工厂需要的事务管理器 -->

<property name="transactionManager" ref="tranManager"></property>

<!-- 注入需要代理的类 -->

<property name="target" ref="EmpDAOTarget"></property>

<!-- 定义一个通知,某些方法按照某种事务规则处理 -->

<property name="transactionAttributes">

<props>

<!-- *号通配符,表示search打头的方法全部应用此通知 -->

<prop key="search*">PROPAGATION_REQUIRED</prop>

<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>

</props>

</property>

</bean>

然后修改业务方法,启动运行main方法,可以看到部门信息输出了

public Emp searchEmpById(Long id)

{

System.out.println("开始业务方法......");

Emp emp=empDAO.findById(id);

/**延迟加载的属性返回的是代理,在这里调用方法初始化装载数据*/

Hibernate.initialize(emp.getDept());

System.out.println("结束业务方法......");

return emp;

}

由于采用了事务代理,在调用业务方法就开始了事务,当调用DAO方法 findById时,它会被加到当前事务中,DAO方法执行完毕后返回到业务方法,此时由于事务还没结束(提交)所以Session也不会被关闭,因此还可以调用initialize方法进行数据装载..

查看log4j输出的运行日志,很清晰的可以看到执行过程

...

org.hibernate.transaction.JDBCTransaction - begin//开始事务

...

开始业务方法......

...

Select  emp0_.EMPNO as EMPNO1_0_,...

...

结束业务方法......

...

org.hibernate.transaction.JDBCTransaction - commit//提交

...

org.springframework.orm.hibernate3.SessionFactoryUtils - Closing Hibernate Session //关闭Session

...

这样的方式比第四种方式好很多了,事务也得到了及时的关闭,Session在使用完毕后就关闭了,生命期缩短提高了性能

上面配置是1.x的配置方式,下面看看2.x的配置方式

首先修改<beans>根节点

<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.0.xsd 

   http://www.springframework.org/schema/tx 

   http://www.springframework.org/schema/tx/spring-tx-2.0.xsd 

http://www.springframework.org/schema/aop 

   http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<!-- 2.x的配置方式 -->

<!--业务类的id名称不需修改,(1.x的配置方式需要修改)-->

<bean id="EmpServiceImpl" 

               class="com.sxy.service.EmpServiceImpl">

<!-- 注入业务类需要的DAO对象 -->

<property name="empDAO" ref="EmpDAO"></property>

</bean>

<!-- 创建事务管理器实例 -->

<bean id="tranManager" 

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

<!-- 注入事务管理器需要的SessionFactory对象-->

<property name="sessionFactory" 

ref="sessionFactory"></property>

</bean>

<!--定义一个通知,某些方法按照某种事务规则处理(类似aop通知) -->

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

<tx:attributes>

<!-- *号通配符,表示以search打头的方法全部应用此通知内包含的一个特征 -->

<tx:method name="search*" propagation="REQUIRED"/>

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

</tx:attributes>

</tx:advice>

<!--以aop的方式切入到应用通知的类 -->

<aop:config>

<!-- 定义切面(关注点) -->

<aop:pointcut  id="serviceMethod" 

                expression="execution(* com.sxy.service.*.*(..))"/>

<!-- 整合通知和切面,即把通知应用到切面-->

<aop:advisor advice-ref="txAdvice" 

                                pointcut-ref="serviceMethod"/>

<!--<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.sxy.service.*.*(..))/>也可这样用-->

</aop:config>

说明一下execution(* com.sxy.service.*.*(..))

第一个* 代表作用区域,如public,private,*代表全部作用域

第二个* 表示类,接口

第三个* 表示方法

com.sxy.service是包名,也可以用通配符

Spring事务传播行为类型

1.x中的transactionAttributes属性符和2.x的propagation属性的取值

(a)PROPAGATION_REQUIRED: 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

(b)PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。

(c)PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。

(d)PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

(e)PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

(f)PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

(g)PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。