4.4 Hibernate高级功能


1.Hibernate批量处理

    在Hibernate应用中,有两种批量处理方法:一种是经过Hibernate的缓存另外一种是绕过Hibernate,直接调用JDBC API来处理java

 

1)批量插入

(1)经过Hibernate的缓存进行批量插入数据库

    使用这种方法时,首先要在Hibernate的配置文件hibernate.cfg.xml中设置批量尺寸属性hibernate.jdbc.batch_size且最好关闭Hibernate的二级缓存以提升效率数组

 

例如:缓存

<hibernate-configuration>
    <session-factory>
         …
        <property name="hibernate.jdbc.batch_size">50</property>                         // 设置批量尺寸
        <property name="hibernate.cache.use_second_level_cache">false</property>         // 关闭二级缓存
    </session-factory>
</hibernate-configuration>

    下面以4.2.1节的例子中的课程进行批量插入为例,说明批量插入操做的具体过程,这里假设批量插入500个课程到数据中:session

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
for(int i=0;i<500;i++){
    Kcb kcb=new Kcb();
    // 这里设置课程号为i,在实际应用中应该是被插入的课程对象
    // 已经放在集合或数组中,这里只要取出
    kcb.setKch(i+"");  
    session.save(kcb);
    if(i%50==0){          // 以50个课程为一个批次向数据库提交,此值应与配置的批量尺寸一致
        session.flush();  // 将该批量数据当即插入数据库中
        session.clear();  // 清空缓存区,释放内存供下批数据使用
    }
}
ts.commit();
HibernateSessionFactory.closeSession();

(2)绕过Hibernate直接调用JDBC进行插入并发

    因为Hibernate只是对JDBC进行了轻量级的封装,所以彻底能够绕过Hibernate直接调用JDBC进行批量插入。所以上例能够改为以下代码:分布式

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
Connection conn=session.connection();
try {
    PreparedStatement  stmt=conn.prepareStatement("insert into KCB(KCH) values(?)");
    for (int i=0; i < 500; i++) {
        stmt.setString(1, i+"");
        stmt.addBatch();                  // 添加到批处理命令中
    }
    stmt.executeBatch();                   // 执行批处理任务
} catch (SQLException e) {
    e.printStackTrace();
}
ts.commit();
HibernateSessionFactory.closeSession();

2)批量更新

(1)由Hibernate直接进行批量更新性能

    为了使Hibernate的HQL直接支持update/delete的批量更新语法,首先要在Hibernate的配置文件hibernate.cfg.xml设置HQL/SQL查询翻译器属性hibernate.query.factory_classspa

<hibernate-configuration>       
    <session-factory>
    ……
        <propertyname="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</property>
    </session-factory>
<hibernate-configuration>


下面使用HQL批量更新把课程表中的XS修改成30。因为这里是用Hibernate操做,故HQL要用类对象及其属性。.net

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
//在HQL查询中使用update进行批量更新
Query query=session.createQuery("update Kcb set xs=30");
query.executeUpdate();
ts.commit();
HibernateSessionFactory.closeSession();

(2)绕过Hibernate调用JDBC进行批量更新

    因为这里是直接操做数据库,故要操做对应表,而不是类。

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
Connection conn=session.connection();
try {
    Statement stmt=conn.createStatement();
    //调用JDBC的update进行批量更新
    stmt.executeUpdate("update KCB set XS=30");
} catch (SQLException e) {
    e.printStackTrace();
}
ts.commit();
HibernateSessionFactory.closeSession();

3)批量删除

(1)由Hibernate直接进行批量删除

    与批量更新同样,为了使Hibernate的HQL直接支持update/delete的批量删除语法,首先要在Hibernate的配置文件hibernate.cfg.xml中设置HQL/SQL查询翻译器属性hibernate.query.factory_class

<hibernate-configuration>       
    <session-factory>
    ……
        <propertyname="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</property>
    </session-factory>
<hibernate-configuration>

    下面将使用HQL批量删除课程表中课程号大于200的课程。

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
//在HQL查询中使用delete进行批量删除
Query query=session.createQuery("delete Kcb where kch>200");
query.executeUpdate();
ts.commit();
HibernateSessionFactory.closeSession();

(2)绕过Hibernate调用JDBC进行批量删除

    一样删除课程表中课程号大于200的课程。

Session session=HibernateSessionFactory.getSession();
Transaction ts=session.beginTransaction();
Connection conn=session.connection();
try {
    Statement stmt= conn.createStatement();
    //调用JDBC的delete进行批量删除
    stmt.executeUpdate("delete from KCB where KCH>200");       
} catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
ts.commit();
HibernateSessionFactory.closeSession();

2.实体对象生命周期

    是Hibernate应用的一个关键概念。这里的实体对象,特指Hibernate O/R映射关系中的域对象(即O/R中的O)

    实体对象的生命周期有如下3种状态。

1)transient(瞬时态)

    瞬时态,即实体对象在内存中的存在与数据库中的记录无关。以下面的代码:

Student stu=new Student();
stu.setSnumber("081101");
stu.setSname("李方方");
stu.setSage(21);

2)persisent(持久态)

    在这种状态下,实体对象的引用被归入Hibernate实体容器中加以管理处于持久状态的对象,其变动将由Hibernate固化到数据库中。例以下面的代码。

Student stu=new Student();
Student stu1=new Student();
stu.setSnumber("081101");
stu.setSname("李方方");
stu.setSage(21);
stu1.setSnumber("081102");
stu1.setSname("程明");
stu1.setSage(22);                                        // 到此为止,stu和stu1均处于瞬时态
Transaction tx=session.beginTransaction();
session.save(stu); // 经过save()方法,stu对象转换为持久态,由Hibernate归入实体管理容器,而stu1仍然处于瞬时态
// 事务提交以后,数据库表中插入一条学生的记录,对于stu1则无任何操做
tx.commit();
Transaction tx2=session.beginTransaction();
stu.setSname("李方");
stu1.setSname("程明明");
// 虽然这个事务中没有显示调用session.save()方法保存stu对象,可是因为stu为持久态,将自动被固化到数据库,所以数据库的学号为“081101” 学生记录的姓名已被更改成“李方”,此时stu1仍然是一个普通Java对象,对数据库未产生任何影响
tx2.commit();

处于瞬时态的对象,能够经过Session的save()方法转换成持久状态。一样,若是一个实体对象由Hibernate加载,那么,它也处于持久状态。例以下面的

// 由Hibernate返回的持久对象
Student stu=(Student)session.load(Student.class,new Integer(1));

3)Detached(脱管状态)

    处于持久态的对象,其对应的Session实例关闭以后,此对象就处于脱管状态Session实例能够看作是持久对象的宿主,一旦此宿主失效,其从属的持久对象进入脱管状态。以下面的代码:

Student stu=new Student();// stu处于瞬时态
Student stu1=new Student();
stu.setSnumber("081101");
stu.setSname("李方方");
stu.setSage(21);
stu1.setSnumber("081102");
stu1.setSname("程明");
stu1.setSage(22);
Transaction tx=session.beginTransaction();
session.save(stu);// stu对象由Hibernate归入管理容器,处于持久状态
tx.commit();
session.close();// stu对象状态为脱管态,由于与其关联的session已经关闭

    瞬时状态的对象与库表中的数据库缺少对应关系;而托管状态的对象,却在库表中存在相应的记录,只不过因为其脱离Session这个数据库操做平台,其状态改变没法更新到数据库表中

3.Hibernate事务管理

    事务是数据库并发控制不可分隔的基本工做单位具备原子性、一致性、隔离性和持久性的特色

    事务(Transcation)是工做中的基本逻辑单位,能够用于确保数据库可以被正确修改,避免只修改了一部分致使数据不完整或者在修改时受到用户干扰

1)基于JDBC的事务管理

    Hibernate是JDBC的轻量级封装,自己并不具有事务管理能力在事务管理层,Hibernate将其委托给底层的JDBC或JTA以实现事务管理和调度功能

    在JDBC的数据库操做中,一项事务是由一条或多条表达式组成的不可分割的工做单元,经过提交commit()或回滚rollback()来结束事务的操做。

    在JDBC中,事物默认是自动提交。也就是说,一条更新表达式表明一项事物操做。操做成功后,系统将自动调用commit()提交。不然将调用rollback()回滚

    在JDBC中,能够经过调用setAutoCommit(false)禁止自动提交,以后就能够把多个数据库操做的表达式做为一个事物,在操做完成后调用commit()进行总体提交

    将事务管理委托给JDBC进行处理是最简单的实现方式Hibernate对于JDBC事务的封装也比较简单。以下面的代码:

Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
session.save(room);
tx.commit();


从JDBC层面而言,上面的代码实际上对应着

Connection cn=getConnection;
cn.setAutoCommit(false);
// JDBC调用相关的SQL语句
cn.commit();

    在sessionFactory.open()语句中,Hibernate会初始化数据库链接。与此同时,将其AutoCommit()设为关闭状态(false)。所以在调用commit()以前,下面的代码不会对数据库产生任何效果:

session session=session.Factory.openSession();
session.save(room);
session.close();

    若是要使代码真正做用到数据库,必须显示地调用Transaction指令

Session session =sessionFactory.openSession();
Transaction tx=sessio.beginTransaction();
session.save(room);
tx.commit();
session.close();

2)基于JTA的事务管理概念

    JTA(Java Transaction API)是由Java EE Transaction Manager去管理的事务。其最大的特色是调用UserTransaction接口的begin、commit和rollback方法来完成事务范围的界定、事务的提交和回滚。JTA能够实现统一事务对应不一样的数据库

    JTA主要用于分布式的多个数据源的两阶段提交的事务而JDBC的Connection提供单个数据源的事务。后者由于只涉及一个数据源,因此其事务能够由数据库本身单独实现。而JTA事务由于其分布式和多数据源的特性,不可能由任何一个数据源实现事务。所以,JTA中的事务是由“事务管理器”实现的。它会在多个数据源之间统筹事务,具体使用的技术就是所谓的“两阶段提交”

 

3)锁

    业务逻辑的实现过程当中,每每须要保证数据访问的排他性。如在金融系统的日终结算处理中,但愿对某个结算时间点的数据进行处理,而不但愿在结算过程当中(多是几秒,也多是几个小时),数据再发生变化。此时,须要经过一些机制来保证这些数据在某个操做过程当中不会被外界修改,这样的机制就是所谓的“锁”,即给选定的目标数据上锁,使其没法被其余程序修改。

    Hibernate支持两种锁机制,悲观锁(Pessimistic Locking)乐观锁(Optimistic Locking)。

(1)悲观锁,它指的是对数据被外界修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制

(2)乐观锁,悲观锁大多数状况下依靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。

    乐观锁,大可能是基于数据版本( Version )记录机制实现。读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然就报错。

    对于上面修改用户账户信息的例子而言,假设数据库中账户信息表中有一个version 字段,当前值为 1 ;而当前账户余额字段( balance )为 $100 。

    操做员 A 此时将其读出( version=1 ),并从其账户余额中扣除 $50( $100-$50 )。

    在操做员 A 操做的过程当中,操做员 B 也读入此用户信息( version=1 ),并从其账户余额中扣除$20 ( $100-$20 )。

    操做员 A 完成了修改工做,将数据版本号加一( version=2 ),连同账户扣除后余额( balance=$50 ),提交至数据库更新,此时因为提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。

    操做员 B 完成了操做,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操做员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不知足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,所以,操做员 B 的提交被驳回。这样,就避免了操做员 B 用基于 version=1 的旧数据修改的结果覆盖操做员 A 的操做结果的可能

    须要注意的是,乐观锁机制每每基于系统中的数据存储逻辑,所以也具有必定的局限性,如在上例中,因为乐观锁机制是在咱们的系统中实现,来自外部系统的用户余额更新操做不受咱们系统的控制所以可能会形成脏数据被更新到数据库中


附:目录《JavaEE基础实用教程》笔记说明

相关文章
相关标签/搜索