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


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

    下面先来看个例子:

     /**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 %- %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类似的操作。





  • 相关阅读:
    接口interface实现与显示实现
    Binding ,抄自 http://www.cnblogs.com/cnblogsfans/archive/2011/02/19/1958586.html
    绑定 Binding Path=.,Binding.,Binding Source={StaticResource ResourceKey="Hello"} xmlns:sys="clr-namespace:System;assembly=mscorlib"
    WPF绑定 mode Using System.ComponentModel; IPropertyChanged, if(this.PropertyChanged!=null){ this.PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Name"))
    C# 方法默认访问级别 : private C# 类默认访问级别 : internal
    html和css的编码规范
    JSON.stringify()和JOSN.parse()
    js的几种数据类型
    sass的四种css编译风格
    angularJs
  • 原文地址:https://www.cnblogs.com/wjlstation/p/2042680.html
Copyright © 2020-2023  润新知