技术开发 频道

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

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

  下面先来看个例子:

/**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的配置方式

  首先修改根节点:

  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">
  class="com.sxy.service.EmpServiceImpl">
  
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  ref
="sessionFactory">
  expression
="execution(* com.sxy.service.*.*(..))"/>
  pointcut
-ref="serviceMethod"/>

  说明一下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类似的操作。

0
相关文章