延迟加载机制是为了避免一些无谓的性能开销而提出来 的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
下面先来看个例子:
/**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类似的操作。