由一个bug 引出的 hibernate 三种状态
(一)问题的引出:
假如现在我想把书名给改了,想改成struts2,book.setName("Struts2开发");
以前我们是要session.update(book)来完成修改。现在我们这里可以不用写update了,它可以自动完成数据的更新。
http://blog.sina.com.cn/s/blog_981ee5d80102w85a.html
为什么会有自动更新数据库的能力
因为它是依赖了hibernate的一级缓存区域的
http://blog.csdn.net/wangyang1354/article/details/50602658
(二)
之前一直没发现这个问题,后来是因为我的导航栏数据库中数据发生变动后,产生菜单的时候又update了一遍数据库中的数据,使得部分数据值为空了,当时觉得很奇怪,后来仔细看了下log4j打印的日志发现其中有更新的动作,在代码中将数据库中获取到的对象set一个对象属性时,数据库就update了,但是这个问题是怎么产生的呢?
问题重述
- Set<User> users = new HashSet<User>();
- User user = null;
- for(int i = 0; i < 10; i++){
- user = new User();
- user.setUserName("wy" + i);
- users.add(user);
- }
- Company company = userDao.getCompany();
- company.setUsers(users);
类似上面的代码,company是刚刚从数据库中获取到的,然后我构造了一个User对象的Set集合,然后在这里直接setUsers的时候就出现了更新数据库的动作,当然这段代码只是示例并不是实际的项目代码。
问题在哪里
Hibernate分为三种基本的状态:游离态、自由态(临时状态)、持久态。
持久化状态:与session关联并且和在数据库有数据,已经持久化了并且在数据库的缓存当中了。
我这里的这个对象应该是持久化状态的对象然后我直接构造了一个user对象的set集合,同时对这个对象进行set操作,那么缓存Session中的数据发生改变,那么接着数据库也会跟着进行相应的改变。所以就执行了update的更新操作。
怎么解决
其他两种状态:
1. 临时状态:就是直接new出来的对象,既没有持久化到数据库中去,也没有在session当中。
2. 游离状态:在Session中没有了,但是已经持久化到了数据库当中。
那么这个地方怎么解决呢?
1.如果这个对象(例子中的company)本身不需要用的话,可以直接new一个Company的对象出来然后再setUsers这个时候因为不是Session中的数据,那么不会因为对象的属性发生改变而同步到数据库中去。
2. 如果这个对象(例子中的company)要用的到,那么,在set之前可以先将其转为游离态,这样的话会用到session的几个方法:close、clear、evict。
close方法:关闭session这样这个对象肯定是游离态了,因为session已经关闭了,但是往往我们实际的开发过程中,session在后面是要用的到的,所以这个方法可行,但是不一定用得上,分清具体的情况。
clear方法:将session中的所有的对象全部清除出缓存,这个方式有点劳师动众,不过session清除了全部的对象之后自然就会变为游离态了,这样做不是很好吧我感觉。
evict方法:将某一个对象清除出缓存session,这个方法是很好的实现方式,推荐使用。调用的时候是这样的,session.evict(Object obj);这样就可以了。
知识扩充:深入理解Hibernate三种状态
状态之间如何转换
1. Save和Update:save的时候是将自由态的对象持久化到数据库中,而update的时候一般是将游离态的对象持久化到数据库中去。
2. update、saveOrUpdate、merge:saveOrUpdate的作用是当Session中存在要操作的对象的时候,抛出异常信息,当不存在的时候,如果这个对象没有持久化标识属性,那么将这个对象save,如果这个对象有持久化标识属性的话,那么直接update就可以了。对于单纯的update来说,程序会在一个Session中加载对象,然后将这个对象关闭,然后这个对象发生变化之后,再update的时候就会打开另一个Session将对象持久化到数据库中去。merge的话是当Session中存在要操作的对象的时候,不会抛出异常而是直接将这个对象的数据拷贝到Session中持久态的对象中去,这样对象还是维持着原来的状态。
3.persist和save:都是用来保存临时对象到数据库的,但是persist并不保证对象的主键立即被创建好,有可能推迟到flush的时候。但是save的时候会立即创建好的,所以save的时候一般可以通过对象拿到主键值。
4. flush和update:update的时候对象多是游离态的对象,也就是说数据库中已经存在了,但是并不在Session中的对象通过update会更新到数据库当中去。而flush是将本来持久态的数据更新到数据库中去。
5. lock是将一个没有更改过的托管的对象转为持久态的对象,但是他不能针对那些delete后的托管的对象。
代码实例分析
- User user = new User();
- user.setName("wy");
- //目前还是一个临时对象
- Session session = sessionFactory.openSession();
- Tansaction tx = session.beginTansaction();
- //目前还是一个临时对象
- session.save(user);
- //user处于持久态了
- tx.commit();
- session.close();// 游离态了
(三)
https://www.cnblogs.com/yangy608/p/4073941.html
案例:
TInfCustomer cus = (TInfCustomer) this.baseDao.getOne(helper);
cus.setXXX
cus .setXXX
不调用update也写入数据库
原因:
hibernate在每个session里都会做些处理,比如把查询过的对象缓存起来什么,这个时候这些对象的实例是和数据库保持关联的,hibernate会记录session生命周期内所有缓存对象的操作过程,最后都会反映到数据库去,也就是所谓的托管状态,所以才会有自动更新这种问题。只要每次都把查询到的对象用evict(或clear)清除(记得,是每次),那么就不会有托管状态的entity,也就不会有自动更新,但这不会影响(应该)update(或saveOrUpdate)操作,evict只是清楚实例与数据库的关联而已,不是清楚实例本身。
修改:baseDao.getCurrentSession().evict(arg0)
(四)