`

session.flush()

    博客分类:
  • ssh
阅读更多
以session的save方法为例来看一个简单、完整的事务流程,如下是代码片段:

…………………………………………………………………………

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

session.save(customer);//之前已实例化好了的一个对象

tx.commit();

…………………………………………………………………………

示例很简单,就是向数据库中插入一条顾客信息,这是一个最简单的数据库事务。在这个简单的过程中,Hibernate为我们做了一些什么事情呢?为了更好的观察,我们将Hibernate的”show_sql”属性设置为true,然后运行我们的程序,控制台打印出如下信息:

Hibernate: select max(ID) from CUSTOMER

Hibernate: insert into CUSTOMER (NAME, EMAIL, PASSWORD, PHONE, ADDRESS, SEX, IS_MARRIED, description, BIRTHDAY, REGISTERED_TIME, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

这里也许看不出什么端倪来,现在在session.save(customer)后面加一行代码,输出这个customer的OID,System.out.println(customer.getId()),再次运行程序,控制台输出为:

Hibernate: select max(ID) from CUSTOMER

22

Hibernate: insert into CUSTOMER (NAME, EMAIL, PASSWORD, PHONE, ADDRESS, SEX, IS_MARRIED, description, BIRTHDAY, REGISTERED_TIME, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

OID在insert语句之前输出,这可以说明两个问题:1.insert语句并不是在执行save的时候发送给数据库的;2.insert语句是在执行commit的时候发送给数据库的。结合前面我们所说过的:执行save的时候,Hibernate会首先把对象放入缓存,然后计划一条insert语句。一个基本的插入流程就出来了:

1.  判断所要保存的实例是否已处于持久化状态,如果不是,则将其置入缓存;

2.  根据所要保存的实例计划一条insert sql语句,注意只是计划,并不执行;

3.  事务提交时执行之前所计划的insert语句;

后台还打印出了select max(ID) from CUSTOMER,这主要是为了给customer赋予一个OID,因为一般情况下临时对象的OID是NULL。

接着我们做两个测试:

1.  将tx.commit();注释掉,此时控制台没有打印出insert语句;

2.  将tx.commit()换成session.flush,此时控制太打印出了insert语句,但是数据库中并没有添加新的记录;

通过查阅Hibernate的API可以知道flush方法的主要作用就是清理缓存,强制数据库

与Hibernate缓存同步,以保证数据的一致性。它的主要动作就是向数据库发送一系列的sql语句,并执行这些sql语句,但是不会向数据库提交。而commit方法则会首先调用flush方法,然后提交事务。这就是为什么我们仅仅调用flush的时候记录并未插入到数据库中的原因,因为只有提交了事务,对数据库所做的更新才会被保存下来。因为commit方法隐式的调用了flush,所以一般我们都不会显示的调用flush方法。





这是在一次事务提交时遇到的异常。
    an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
net.sf.hibernate.AssertionFailure: possible nonthreadsafe access to session
注:非possible non-threadsafe access to the session (那是另外的错误,类似但不一样)

    这个异常应该很多的朋友都遇到过,原因可能各不相同。但所有的异常都应该是在flush或者事务提交的过程中发生的。这一般由我们在事务开始至事务提交的过程中进行了不正确的操作导致,也会在多线程同时操作一个Session时发生,这里我们仅仅讨论单线程的情况,多线程除了线程同步外基本与此相同。

    至于具体是什么样的错误操作那?我给大家看一个例子(假设Hibernate配置正确,为保持代码简洁,不引入包及处理任何异常)


SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession();
Cat cat = new Cat();

Transaction tran = s.beginTransaction(); (1)
s.save(cat); (2)(此处同样可以为update delete)
s.evict(cat); (3)
tran.commit(); (4)
s.close();(5)


    这就是引起此异常的典型错误。我当时就遇到了这个异常,检查代码时根本没感觉到这段代码出了问题,想当然的认为在Session上开始一个事务,通过Session将对象存入数据库,再将这个对象从Session上拆离,提交事务,这是一个很正常的流程。如果这里正常的话,那问题一定在别处。

    问题恰恰就在这里,我的想法也许没有错,但是一个错误的论据所引出的观点永远都不可能是正确的。因为我一直以为直接在对数据库进行操作,忘记了在我与数据库之间隔了一个Hibernate,Hibernate在为我们提供持久化服务的同时,也改变了我们对数据库的操作方式,这种方式与我们直接的数据库操作有着很多的不同,正因为我们对这种方式没有一个大致的了解造成了我们的应用并未得到预先设想的结果。

那Hibernate的持久化机制到底有什么不同那?简单的说,Hibernate在数据库层之上实现了一个缓存区,当应用save或者update一个对象时,Hibernate并未将这个对象实际的写入数据库中,而仅仅是在缓存中根据应用的行为做了登记,在真正需要将缓存中的数据flush入数据库时才执行先前登记的所有行为。

在实际执行的过程中,每个Session是通过几个映射和集合来维护所有与该Session建立了关联的对象以及应用对这些对象所进行的操作的,与我们这次讨论有关的有entityEntries(与Session相关联的对象的映射)、insertions(所有的插入操作集合)、deletions(删除操作集合)、updates(更新操作集合)。下面我就开始解释在最开始的例子中,Hibernate到底是怎样运作的。
(1)生成一个事务的对象,并标记当前的Session处于事务状态(注:此时并未启动数据库级事务)。
(2)应用使用s.save保存cat对象,这个时候Session将cat这个对象放入entityEntries,用来标记cat已经和当前的会话建立了关联,由于应用对cat做了保存的操作,Session还要在insertions中登记应用的这个插入行为(行为包括:对象引用、对象id、Session、持久化处理类)。
(3)s.evict(cat)将cat对象从s会话中拆离,这时s会从entityEntries中将cat这个对象移出。
(4)事务提交,需要将所有缓存flush入数据库,Session启动一个事务,并按照insert,update,……,delete的顺序提交所有之前登记的操作(注意:所有insert执行完毕后才会执行update,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush),现在cat不在entityEntries中,但在执行insert的行为时只需要访问insertions就足够了,所以此时不会有任何的异常。异常出现在插入后通知Session该对象已经插入完毕这个步骤上,这个步骤中需要将entityEntries中cat的existsInDatabase标志置为true,由于cat并不存在于entityEntries中,此时Hibernate就认为insertions和entityEntries可能因为线程安全的问题产生了不同步(也不知道Hibernate的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个bug吧),于是一个net.sf.hibernate.AssertionFailure就被抛出,程序终止。

我想现在大家应该明白例子中的程序到底哪里有问题了吧,我们的错误的认为s.save会立即的执行,而将cat对象过早的与Session拆离,造成了Session的insertions和entityEntries中内容的不同步。所以我们在做此类操作时一定要清楚Hibernate什么时候会将数据flush入数据库,在未flush之前不要将已进行操作的对象从Session上拆离。

对于这个错误的解决方法是,我们可以在(2)和(3)之间插入一个s.flush()强制Session将缓存中的数据flush入数据库(此时Hibernate会提前启动事务,将(2)中的save登记的insert语句登记在数据库事务中,并将所有操作集合清空),这样在(4)事务提交时insertions集合就已经是空的了,即使我们拆离了cat也不会有任何的异常了。
前面简单的介绍了一下Hibernate的flush机制和对我们程序可能带来的影响以及相应的解决方法,Hibernate的缓存机制还会在其他的方面给我们的程序带来一些意想不到的影响。看下面的例子:


(name为cat表的主键)
Cat cat = new Cat();
cat.setName(“tom”);
s.save(cat);

cat.setName(“mary”);
s.update(cat);(6)

Cat littleCat = new Cat();
littleCat.setName(“tom”);
s.save(littleCat);

s.flush();


这个例子看起来有什么问题?估计不了解Hibernate缓存机制的人多半会说没有问题,但它也一样不能按照我们的思路正常运行,在flush过程中会产生主键冲突,可能你想问:“在save(littleCat)之前不是已经更改cat.name并已经更新了么?为什么还会发生主键冲突那?”这里的原因就是我在解释第一个例子时所提到的缓存flush顺序的问题,Hibernate按照insert,update,……,delete的顺序提交所有登记的操作,所以你的s.update(cat)虽然在程序中出现在s.save(littleCat)之前,但是在flush的过程中,所有的save都将在update之前执行,这就造成了主键冲突的发生。

这个例子中的更改方法一样是在(6)之后加入s.flush()强制Session在保存littleCat之前更新cat的name。这样在第二次flush时就只会执行s.save(littleCat)这次登记的动作,这样就不会出现主键冲突的状况。

再看一个例子(很奇怪的例子,但是能够说明问题)

Cat cat = new Cat();
cat.setName(“tom”);
s.save(cat); (7)
s.delete(cat);(8)

cat.id=null;(9)
s.save(cat);(10)
s.flush();


这个例子在运行时会产生异常net.sf.hibernate.HibernateException: identifier of an instance of Cat altered from 8b818e920a86f038010a86f03a9d0001 to null

这里例子也是有关于缓存的问题,但是原因稍有不同:
(7)和(2)的处理相同。
(8)Session会在deletions中登记这个删除动作,同时更新entityEntries中该对象的登记状态为DELETED。
(9)Cat类的标识符字段为id,将其置为null便于重新分配id并保存进数据库。
(10)此时Session会首先在entityEntries查找cat对象是否曾经与Session做过关联,因为cat只改变了属性值,引用并未改变,所以会取得状态为DELETED的那个登记对象。由于第二次保存的对象已经在当前Session中删除,save会强制Session将缓存flush才会继续,flush的过程中首先要执行最开始的save动作,在这个save中检查了cat这个对象的id是否与原来执行动作时的id相同。不幸的是,此时cat的id被赋为null,异常被抛出,程序终止(此处要注意,我们在以后的开发过程尽量不要在flush之前改变已经进行了操作的对象的id)。

这个例子中的错误也是由于缓存的延时更新造成的(当然,与不正规的使用Hibernate也有关系),处理方法有两种:
1、在(8)之后flush,这样就可以保证(10)处save将cat作为一个全新的对象进行保存。
2、删除(9),这样第二次save所引起的强制flush可以正常的执行,在数据库中插入cat对象后将其删除,然后继续第二次save重新插入cat对象,此时cat的id仍与从前一致。

这两种方法可以根据不同的需要来使用,呵呵,总觉得好像是很不正规的方式来解决问题,但是问题本身也不够正规,只希望能够在应用开发中给大家一些帮助,不对的地方也希望各位给与指正。

  总的来说,由于Hibernate的flush处理机制,我们在一些复杂的对象更新和保存的过程中就要考虑数据库操作顺序的改变以及延时flush是否对程序的结果有影响。如果确实存在着影响,那就可以在需要保持这种操作顺序的位置加入flush强制Hibernate将缓存中记录的操作flush入数据库,这样看起来也许不太美观,但很有效。

分享到:
评论
3 楼 xiaolng 2010-12-14  
在看你分析之前对这块一点不了解。好好看后发现分析的很清晰透彻。以后会都关注你的。嘿嘿。
2 楼 shelaine 2009-02-25  
bigfishyuwan 写道

说得很清楚

1 楼 bigfishyuwan 2009-02-24  
说得很清楚

相关推荐

    hibernate的session.flush

    `Session.flush()`方法是一个关键的操作,它强制Hibernate将内存中的对象状态同步到数据库,确保数据的一致性。这篇博客深入探讨了`Session.flush()`的工作原理和应用场景。 `Session`在Hibernate中主要有以下职责...

    hibernate的flush()、refresh()、clear()针对一级缓存的操作的区别.docx

    1. `session.flush()`: 这个方法的主要作用是将Session缓存中的改动同步到数据库中。在默认情况下,Hibernate并不会立即更新数据库,而是等到事务提交或者Session关闭时才进行同步。`flush()`方法强迫Hibernate执行...

    hibernate的flush机制

    1. **显式调用**:开发人员可以直接调用`session.flush()`方法来强制执行Flush操作。 2. **事务提交**:在事务的提交阶段,为了确保数据的一致性和持久性,Hibernate会自动进行Flush操作。 3. **查询执行前**:当...

    2022年Hibernate下数据批量处理Java教程.docx

    我们可以设置一个合理的 JDBC 批处理大小,例如 hibernate.jdbc.batch_size 20,然后在一定间隔对 Session 进行 flush() 和 clear()。这样可以避免内存溢出错误,并提高性能。 在批量插入数据时,我们可以使用以下...

    Hibernate code

    根据提供的文件信息,我们可以深入探讨Hibernate中的几个关键概念与操作,包括`Session.flush()`方法的使用、不同主键生成策略下的保存操作等。 ### Hibernate Session.flush() 方法详解 #### 一、基本概念 在...

    Hibernate下数据批量处理解决方案

    session.flush(); session.clear(); } } tx.commit(); session.close(); ``` 对于更新和删除操作,可以使用`scroll()`方法,这在Hibernate 2.1.6或更高版本中是支持的。`scroll()`方法返回一个`...

    java-hibernate持久化

    - 显式调用`session.flush()`方法。 总结来说,Hibernate的持久化机制和一级缓存是其高效处理数据库操作的关键。理解并熟练掌握这些概念,可以帮助开发者编写更高效、更易于维护的Java应用。在实际项目中,合理利用...

    第一个hibernate程序及解释

    session.flush(); // 提交事务 ``` **7. 删除数据** 通过Session的delete()方法删除记录。 ```java User user = (User) session.get(User.class, userId); session.delete(user); session.flush(); ``` **8. 显示...

    在Hibernate中处理批量更新和批量删除

    在每次更新后调用`session.flush()`强制Hibernate执行当前的数据库操作,然后使用`session.evict(entity)`将实体从缓存中移除。这样做可以确保每个更新操作仅执行一次,并且避免了持久化上下文中的内存浪费。 ```...

    Hibernate批量处理数据

    1. **定时刷新缓存**:在循环过程中,设定一定的阈值(如每20条记录),当达到该阈值时,使用`session.flush()`将缓存中的数据写入数据库,并使用`session.clear()`清空缓存,以释放内存空间。 ```java for (int...

    在Hibernate应用中处理批量更新和批量删除

    1. **使用`flush()`和`evict()`方法**:在修改实体后,主动调用`session.flush()`方法,使Hibernate将缓存中的变更同步到数据库,然后调用`session.evict(entity)`方法,从缓存中移除实体,这样下一次操作不会受到...

    hibernate连接数据[Mysql]的代码实例

    session.flush(); session.clear(); } } transaction.commit(); session.close(); ``` 以上就是使用Hibernate连接MySQL数据库的基本步骤和操作示例。注意,实际应用中还需要考虑事务管理、异常处理、性能...

    hql的增删改查

    session.flush(); } ``` **解析:** - `getCurrentSession()`方法用于获取当前的`Session`实例。 - `save()`方法用于将`item`对象持久化到数据库中。 - `flush()`方法用于刷新缓存并执行所有未完成的持久化操作。 ...

    hibernate性能优化.doc

    session.flush(); session.clear(); tx.commit(); tx = session.beginTransaction(); } } tx.commit(); ``` 在上面的代码中,我们使用了事务来批量处理数据,每 20 条记录就执行一次 flush() 方法来清空缓存,...

    jsp Hibernate批量更新和批量删除处理代码.docx

    session.flush(); session.evict(customer); } tx.commit(); session.close(); ``` 这段代码首先开启一个事务,然后通过HQL(Hibernate查询语言)查询所有年龄大于零的客户。对于每一个查询到的Customer对象,...

    hibernate更新操作

    总结来说,Hibernate的更新操作主要包括配置、创建SessionFactory、打开Session、查询数据、修改对象、调用flush()提交更改,最后关闭Session和SessionFactory。这个过程确保了数据的一致性和事务的管理,是...

    Hibernate中大量数据的更新

    因此,在批量更新时,需要定期调用 `session.flush()` 和 `session.clear()` 来清除一级缓存。 批量抓取 Hibernate 的批量抓取机制允许将多个INSERT 语句合并成一个批量插入语句,从而提高性能。在 Hibernate 配置...

    HibernateTemplate类的使用

    在上述示例中,开发者期望通过调用`session.flush()`方法来显式提交事务,但实际上这种做法并不符合Spring的事务管理机制。Spring管理的事务默认情况下是在`HibernateTemplate`执行完毕后自动提交的。如果在`...

    Hibernate对Blob,Clob的操作

    session.flush(); // 对于Clob String textContent = ...; // 长文本内容 session.saveOrUpdate(entity); entity.setTextData(session.createClob(textContent)); session.flush(); ``` 在读取数据时,我们可以...

    django之session与分页(实例讲解)

    request.session.flush() ``` 4. **检测Session是否存在**: ```python if "session_name" in request.session: # Session存在时的操作 ``` 在提供的实例中,我们看到如何在登录视图(views)中使用Session...

Global site tag (gtag.js) - Google Analytics