Detailed explanation of Hibernate's clear(), flush(), evict() methods

 

reprint

I watched the session recently and found that the session.flush() method is obviously misleading. It is obviously nonsense, it is not right, it is not clear, I am very angry about this, you are wrong and everyone is wrong. You are still reposting your mistakes everywhere. Are you shy? I want to ask the author.

 

I hereby correct many mistakes

 

The function of the session.flush() method is actually to flush the cached data of the session (the session is the first level cache) into the database to synchronize the database. You can understand it more simply, forcing the session data and the database Data synchronization, not clearing the cache, I am surprised. Clearing the cache is obviously the session.clear() method. Before using the flush method, CRUD operations are generally performed on an object, and then you call the flush method, and it will be timely. To synchronize to the database, in fact, the best part of the session.flush() method is that we can control the number when processing a large amount of data. For example, if we want to store 10,000 objects, we can do this

 

if(i%20==0){

 

session.flush();//Force data to be synchronized to the database

 

session.clear(); clear cache

 

}

 

This improves work performance.

 

1.Clear method

 

      Whether it is Load or Get, it will first look for the cache (first-level cache). If not, it will go to the database to find it, and call the Clear() method to force the Session cache to be cleared.

 

example:

 

  1. publicvoid 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.     }  

Although two get methods are used here  (  the  get  method  will execute the  sql  statement immediately), because the first execution will cache an entity with an  ID  of  3  , so although there are  two   get  methods, the  SQL  statement will only be executed once .

 

  1. publicvoid 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(); //If there is no clear, only one sql statement will be executed, and if there is clear, it will be executed twice  
  7.         Teacher t2 = (Teacher) session.get(Teacher.class, 3);  
  8.         System.out.println(t2.getName());  
  9.         session.getTransaction().commit();  
  10.     }  

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


2.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.     }  

我们在第2  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=?  

执行了2  Update

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

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

 在session.beginTransaction 前设置 FlushMode

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

FlushMode 5 个值可选

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


1、NEVEL:已经废弃了,被MANUAL取代了
2 MANUAL:
如果FlushMode是MANUAL或NEVEL,在操作过程中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会读取事务中的各种配置来覆盖hibernate的session中的FlushMode;
3 AUTO
设置成auto之后,当程序进行查询、提交事务或者调用session.flush()的时候,都会使缓存和数据库进行同步,也就是刷新数据库
4 COMMIT
提交事务或者session.flush()时,刷新数据库;查询不刷新
5 ALWAYS:
每次进行查询、提交事务、session.flush()的时候都会刷数据库
ALWAYS和AUTO的区别:当hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当 session设置为FlushMode.AUTO时,hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷数据库,不是则不刷, 而always是直接刷新,不进行任何判断。很显然auto比always要高效得多。

 


总结:若OpenSessionInViewFilter在getSession的 时候,会把获取回来的session的flush mode 设为FlushMode.NEVER。然后把该sessionFactory绑定到 TransactionSynchronizationManager,使request的整个过程都使用同一个session,在请求过后再解除该 sessionFactory的绑定,最后closeSessionIfNecessary根据该session是否已和transaction绑定来决定是否关闭session。在这个过程中,若HibernateTemplate 发现自当前session有不是readOnly的transaction,就会获取到FlushMode.AUTO Session,使方法拥有写权限。
也即是,如果有不是readOnly的transaction就可以由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  

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326998288&siteId=291194637