hibernate 批量插入

转载:Hibernate的clear(),flush(),evict()方法详解

session.flush()方法的作用其实就是让session的缓存的数据(session就是一级缓存)刷入到数据库里面去,让数据库同步,你可 以更简单的理解就是,强制让session的数据和数据库的数据同步,而不是什么清除缓存,我就奇怪了,清除缓存明明是session.clear()方 法,在使用flush方法一般之前都是对一个对象进行CRUD的操作,然后你调用flush方法,就及时的同步到数据库里面去,其实 session.flush()方法用的最好的一块是在处理大量数据的时候我们可以控制数量,比如,我们要存储1万个对象,我们可以这样做

if(i%20==0){

session.flush();//强制同步数据到数据库里面去

session.clear();清除缓存

}

这样提高工作性能。

Clear 方法

      无论是Load 还是 Get 都会首先查找缓存(一级缓存) 如果没有,才会去数据库查找,调用Clear() 方法,可以强制清除Session缓存。

例:

1. public void testClear(){  

2.         Session session =  HibernateUitl.getSessionFactory().getCurrentSession();  

3.         session.beginTransaction();  

4.         Teacher t = (Teacher) session.get(Teacher.class, 3);  

5.         System.out.println(t.getName());  

6.         Teacher t2 = (Teacher) session.get(Teacher.class, 3);  

7.         System.out.println(t2.getName());  

8.         session.getTransaction().commit();  

9.     }  

这里虽然用了个 get 方法( get 方法会立即执行 sql 语句),但因为第一次执行了会缓存一个 ID 为 3 的实体,所以虽然有 2 个 get 方法只执行一次 SQL 语句。

1. public void testClear(){  

2.         Session session =  HibernateUitl.getSessionFactory().getCurrentSession();  

3.         session.beginTransaction();  

4.         Teacher t = (Teacher) session.get(Teacher.class, 3);  

5.         System.out.println(t.getName());  

6.         session.clear();//这里不clear只会执行一次sql语句,有clear会执行2次  

7.         Teacher t2 = (Teacher) session.get(Teacher.class, 3);  

8.         System.out.println(t2.getName());  

9.         session.getTransaction().commit();  

10.     }  

这里在第次 get 前执行 session.clear(), 我们把 hibernate show_sql  出来,它就会执行 2 次 sql 语句了。 所以session.clear() 会清除缓存。

Flush方法

      可以强制进行从内存到数据库的同步。

例:

1. @Test  

2.     /** 

3.      * flush 强制与数据库同步 

4.      */  

5.     public void testFlush(){  

6.         Session session =  HibernateUitl.getSessionFactory().getCurrentSession();  

7.         session.beginTransaction();  

8.         Teacher t = (Teacher) session.get(Teacher.class, 3);  

9.         t.setName("yyy");  

10.    

11.         t.setName("yyyyy");  

12.         session.getTransaction().commit();  

13.     }  

看这段代码,我们setName() 2 次, 但程序只会更改数据库一次,在 commit 时。

1. @Test  

2.     /** 

3.      * flush 强制与数据库同步 

4.      */  

5.     public void testFlush(){  

6.         Session session =  HibernateUitl.getSessionFactory().getCurrentSession();  

7.         session.beginTransaction();  

8.         Teacher t = (Teacher) session.get(Teacher.class, 3);  

9.         t.setName("yyy");  

10.         session.flush();//有flush会执行2次UPDAE,没有会只执行一次  

11.         t.setName("yyyyy");  

12.         session.getTransaction().commit();  

13.     }  

我们在第次 setName ()时 执行 session.flush().

再看hibernate  执行的 sql  语句

1. Hibernate:   

2.     update  

3.         Teacher   

4.     set  

5.         birthday=?,  

6.         name=?,  

7.         title=?   

8.     where  

9.         id=?  

10. Hibernate:   

11.     update  

12.         Teacher   

13.     set  

14.         birthday=?,  

15.         name=?,  

16.         title=?   

17.     where  

18.         id=?  

执行了次 Update

所以看出来flush 方法会强制与数据库同步。

Flush方法是可以设置的,也就是 fulsh 什么时候执行是可以设置的

 session.beginTransaction 前设置 FlushMode

session.setFlushMode(FlushMode.Always|AUTO|COMMIT|NEVER|MANUAL)

FlushMode有 5 个值可选

Always:任何代码都会 Flush 
AUTO:默认方式 – 自动 
Commit:COMMIT时 
Never:始终不 
MANUAL:手动方式

1Never:已经废弃了,被MANUAL取代了
2 MANUAL
如果FlushModeMANUALNEVEL,在操作过程中hibernate会将事务设置为readonly,所以在增加、删除或修改操作过程中会出现如下错误
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition
解决办法:配置事务,spring会读取事务中的各种配置来覆盖hibernatesession中的FlushMode
3 AUTO
设置成auto之后,当程序进行查询、提交事务或者调用session.flush()的时候,都会使缓存和数据库进行同步,也就是刷新数据库
4 COMMIT
提交事务或者session.flush()时,刷新数据库;查询不刷新
5 ALWAYS
每次进行查询、提交事务、session.flush()的时候都会刷数据库
ALWAYSAUTO的区别:当hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当 session设置为FlushMode.AUTO时,hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷数据库,不是则不刷, 而always是直接刷新,不进行任何判断。很显然autoalways要高效得多。

总结:若OpenSessionInViewFiltergetSession的 时候,会把获取回来的sessionflush mode 设为FlushMode.NEVER。然后把该sessionFactory绑定到 TransactionSynchronizationManager,使request的整个过程都使用同一个session,在请求过后再解除该 sessionFactory的绑定,最后closeSessionIfNecessary根据该session是否已和transaction绑定来决定是否关闭session。在这个过程中,若HibernateTemplate 发现自当前session有不是readOnlytransaction,就会获取到FlushMode.AUTO Session,使方法拥有写权限。
也即是,如果有不是readOnlytransaction就可以由Flush.NEVER转为Flush.AUTO,拥有 insert,update,delete操作权限,如果没有transaction,并且没有另外人为地设flush model的话,则doFilter的整个过程都是Flush.NEVER。所以受transaction保护的方法有写权限,没受保护的则没有。

设置FlushMode  有个好处是可以节省开销,比如默认 session 只做查询时,就可以不让他与数据库同步了。

session.evict(obj) :会把指定的缓冲对象进行清除。 
session.clear() :把缓冲区内的全部对象清除,但不包括操作中的对象。 
  Hibernate 执行的顺序如下: 
 (1) 生成一个事务的对象,并标记当前的 Session 处于事务状态(注:此时并未启动数据库级事务)。 
 (2) 应用使用 s.save 保存对象,这个时候 Session 将这个对象放入 entityEntries ,用来标记对象已经和当前的会话建立了关联,由于应用对对象做了保存的操作, Session 还要在 insertions 中登记应用的这个插入行为(行为包括:对象引用、对象 id  Session 、持久化处理类)。 
 (3)s.evict 将对象从 s 会话中拆离,这时 s 会从 entityEntries 中将这个对象移出。 
 (4) 事务提交,需要将所有缓存 flush 入数据库, Session 启动一个事务,并按照 insert,update,……,delete 的顺序提交所有之前登记的操作(注意:所有 insert 执行完毕后才会执行 update ,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush ),现在对象不在 entityEntries 中,但在执行 insert 的行为时只需要访问 insertions 就足够了,所以此时不会有任何的异常。异常出现在插入后通知 Session 该对象已经插入完毕这个步骤上,这个步骤中需要将 entityEntries 中对象的 existsInDatabase 标志置为 true ,由于对象并不存在于 entityEntries 中,此时 Hibernate 就认为 insertions  entityEntries 可能因为线程安全的问题产生了不同步(也不知道 Hibernate 的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个 bug 吧),于是一个 net.sf.hibernate.AssertionFailure 就被抛出,程序终止。 
         一般我们会错误的认为 s.save 会立即执行,而将对象过早的与 Session 拆离,造成了 Session  insertions  entityEntries 中内容的不同步。所以我们在做此类操作时一定要清楚 Hibernate 什么时候会将数据 flush 入数据库,在未 flush 之前不要将已进行操作的对象从 Session 上拆离。解决办法是在 save 之后,添加 session.flush  

 

Hibernate的批量处理

Hibernate完全以面向对象的方式操作数据库,当程序员以面向对象的方式操作持久化对象时,将自动转换为对数据的操作。例如我们Session的delete()方法,来删除持久化对象,Hibernate将负责删除对应的数据记录;当我们执行持久化对象的setter方法时,Hibernate将自动转换为底层的update语句,修改数据库的对应记录。

  问题是:如果我们需要同时更新100000条记录,是不是要逐一加载100000条记录,然后依次调用setter方法——这样不仅繁琐,数据访问的性能也十分糟糕。为了面对这种批量处理的场景,Hibernate提供了批量处理的解决方案。下面分别从批量插入,批量更新和批量删除三个方面介绍如何面对这种批量处理的情形。

  批量插入:

复制代码
1 Session session = HibernateSessionFactory.getSession();
2 Transaction tx = session.beiginTransaction();
3 //循环100000次插入100000
4 for(int i=0; i<100000; i++){
5     User u = new User();
6     session.save(u);              
7 }
8 tx.commit();
9 session.close();
复制代码

  但随着这个程序的运行,总会在某个时候运行失败,并且抛出OutOfMemoryException(内存溢出异常)。这是因为Hibernate的Session持有一个必选的一级缓存,所有的User实例都将在Session级别的缓存区进行缓存的缘故。

  为了解决这个问题,有个非常简单的思路:定时将Session缓存的数据刷入数据库,而不是一直在Session级别缓存。可以考虑设计一个累加器,每保存一个User实例,累加器增加1.根据累加器的值决定是否需要将Session缓存的数据刷入数据库。

  下面是增加100000个User实例的代码:

复制代码
 1 //打开Session
 2 Session session = HibernateSessionFactory.getSession();
 3 //开始事务
 4 Transaction tx = session.beginTransaction();
 5 //循环100000次,插入100000条记录
 6 for(int i = 0; i < 100000; i++){
 7      User u = new User();
 8      u.setName("XXX");
 9      ....
10      //在session级别缓存Usr实例
11      session.save(u);
12      //每当累加器是20的倍数时,将Session中数据刷入数据库
13      if(i % 20 == 0){
14           session.fluah();
15           session.clear();
16     }            
17 }
18 //提交事务
19 tx.commit();
20 //关闭事务
21 session.close();
复制代码

  上面的代码中 i % 20 == 0时,手动将Session处缓存的数据写入数据库,并且清空Session缓存里的数据。除了要对Session级别缓存进行处理外,换应该通过如下配置来关闭SessionFactory的二级缓存。

1 hibernate.cache.use_second_level_cache false

        除了手动清空Session级别的缓存外,最好关闭SessionFactory级别的二级缓存。否则,即使手动flush Session级别的缓存,但因为在SessionFactory还有二级缓存,也可能引发异常。

  批量更新

  上面的方法同样适用于批量更新数据,如果需要换回多行数据,应该使用scroll()方法,从未可以充分利用服务器游标带来的性能优势。下面是进行批量更新的代码片段。

复制代码
 1         // 打开Session
 2         Session session = HibernateSessionFactory.getSession();
 3         // 开始事务
 4         Transaction tx = session.beginTransaction();
 5         // 查询User表中的所有记录
 6         ScrollableResults users = session.createQuery("from User")
 7                 .setCacheMode(CacheMode.IGNORE).scroll(ScrollMode.FORWARD_ONLY);
 8         int count = 0;
 9         //遍历User表中所有的记录
10         while(users.next()){
11             User = (User)users.get(0);
12             u.setName("新用户名"+count);
13             //当count为20的倍数时
14             //将更新的结果从Session中flush到数据库
15             if(++count % 20 == 0){
16                 session.flush();
17                 session.clear();
18             }
19         }
20         tx.commit();
21         session.close();
复制代码

  通过这种方式,虽然可以执行批量更新,但效果非常不好。执行效率不高,需要先执行数据查询,然后在执行数据更新,而且这种更新将是逐行更新,即没更新一行记录,都需要执行一条update语句,性能也非常低下。

  为了避免这种情况,Hibernate提供了一种类似于DML语句的批量更新,批量删除的HQL语法。

  DML 风格的批量更新/删除

  Hibernate提供的HQL也支持批量的UPDATE和DELETE语法

  批量 UPDATE 和 DELETE语句的语法格式如下:

1 UPDATE | DELETE FROM? <ClassName> [WHERE WHERE_CONDITIONS]

  关于上面的语法格式有如下4点值得注意:

  》在FROM字句中,FROM关键字是可选的,即完全可以不写FROM关键字。

  》在FROM字句中只能有一个类名,该类名不能有别名。

  》不能在批量HQL语句中使用连接,显示或者隐式都不可以。但可以在WHERE字句中使用子查询。

  》整个WHERE字句是可选的。WHERE字句的语法和HQL语句中WHERE字句的语法完全相同。

  假设对于上面需要批量更改User类实例的name属性,可以采用如下代码片段完成。

复制代码
 1         // 打开Session
 2         Session session = HibernateSessionFactory.getSession();
 3         // 开始事务
 4         Transaction tx = session.beginTransaction();
 5         //定义批量更新的HQL语句
 6         String hqlUpdate = "update User set name = :newName";
 7         //执行更新
 8         int rows = session.createQuery(hqlUpdate).setString("newName", "新名字").executeUpdate();
 9         tx.commit();
10         session.close();
复制代码

  从上面的代码中可以看出,这种语法非常类似于PreparedStatement中的executeUpdate()语法,实际上,HQL的这种批量更新就是直接借鉴了SQL语法的UPDATE语句。

  使用这种批量更新语法时,通常只需要执行一次SQL的UPDATE语句,就可以完成所有满足条件记录的更新。但有可能需要执行多条UPDATE语句,这是因为有继承映射等特殊情况,例如有一个Person实例,他有Customer子类实例。当批量更新Person实例时,也需要更新Customer实例。如果采用 joined-subclass或union-subclass映射策略时,Person和Customer实例保存在不同的表中,因此可能需要多条UPDATE语句。

  执行HQL DELETE ,同样适用Query.executeUpdate()方法,下面是一次删除上面全部记录的代码片段。

复制代码
 1  1         // 打开Session
 2  2         Session session = HibernateSessionFactory.getSession();
 3  3         // 开始事务
 4  4         Transaction tx = session.beginTransaction();
 5  5         //定义批量删除的HQL语句
 6  6         String hqlDelete = "delete User";
 7  7         //执行删除
 8  8         int rows = session.createQuery(hqlDelete).executeUpdate();
 9  9         tx.commit();
10 10         session.close();
复制代码

  Query.executeUpdate()方法返回一个整数值,改值时受此操作影响的记录数量。我们知道 Hibernate的底层操作实际上都是由JDBC完成的,因此,如果有批量的UPDATE或DELETE操作将被转换成多条UPDATE或DELETE语句,该方法只能返回最后一条SQL语句影响的记录行数。

实际项目中的应用:

@Repository
public class AttPropertyCategoryDaoHibernate extends DaoSupport<AttPropertyCategory> implements AttPropertyCategoryDao {
   /**
    * 
    */
   private static final long serialVersionUID = 1L;

   @Override
   public boolean saveOrUpdatePropertyCategory(final Integer catId, final Integer[] propertyIds) {

      return this.getHibernateTemplate().execute(new HibernateCallback<Boolean>() {
         @Override
         public Boolean doInHibernate(Session session) throws HibernateException, SQLException {
            String deleteSql = " delete from att_property_category where  cat_id="+catId;
            Query query = session.createSQLQuery(deleteSql);
            query.executeUpdate();
            session.clear();

            Transaction tx = session.beginTransaction();开始事务
            int count = 0;
            for (Integer propertyId : propertyIds) {
               AttPropertyCategory attPropertyCategory = new AttPropertyCategory();
               attPropertyCategory.setCatId(catId);
               attPropertyCategory.setPropertyId(propertyId);
               session.save(attPropertyCategory);//session级别缓存AttPropertyCategory实例
               if (++count%20==0){每当累加器是20的倍数时,Session中数据刷入数据库
                  session.flush();
                  session.clear();
               }
            }
            tx.commit();提交事务
            session.close();
            return true;
         }
      });

   }
}

 同时在spr.xml配置中关闭二级缓存:

<bean id="sessionFactory"
      class="com.xxx.db.distributed.SessionFactoryCreator">
    <property name="packagesToScan">
        <list>
            <value>com.xxx</value>
        </list>
    </property>

    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
            <prop key="hibernate.show_sql">${sys.show_sql}</prop>
            <prop key="hibernate.format_sql">false</prop>
            <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
            <prop key="hibernate.jdbc.batch_size">50</prop><!-- 50条语句提交一次  -->
            <prop key="hiberante.cache.use_second_level_cache">false</prop><!--关闭二级缓存  -->
        </props>
    </property>
    <property name="entityInterceptor" ref="myInterceptor"/>
</bean>

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值