解析a different object with the same identifier value was already associated with the session问题

问题描述:hibernate中同一个session里面有了两个相同标识但是是不同实体

早先的发生场景:

几乎所有搞过OrMapping持久化程序的开发者都多多少少碰到过这个异常.
这个异常通常发生在一个session 内对同一个数据库对象生成了多个(经常是load(id)/get(id)一个,又new+setId(id)一个),然后又对其进行了update()或者save() .
从业务逻辑的角度,经常发生在修改/更新的操作中.

早先的发生原因和解决手段:

1.经常是session忘记关闭,或忘记commit(),造成本该分为两个session的,合为了一个session,就出现了此异常.
解决手段也很简单,通过session关闭和事务,把相同的操作对象从session级别或者事务级别上分隔开.

现在问题的新变化:

2.hibernate3.0以后,getCurrentSession()技术的出现,session pool的出现,session不再需要手动open,也不需要手动关闭,反而使问题复杂化
spring TransactionManager的出现,使问题更复杂
各种Manger Bean, DAO bean里打开了spring事务管理, spring事务又定义了各种操作隔离机制,比如:
“一个在事务下的方法,调用另一个方法,则另一个方法不再开事务,而是涵盖在调用它的方法的事务之下”
“一个在事务下的方法,调用另一个方法,另一个方法建立新事务,父方法的事务暂停,待另一个方法执行完毕,才继续事务”.

3.以struts2 web 框架为代表的一些扩展功能,使问题更复杂
典型的比如struts2 的domain Model (域模型)传参. JSP直接向Action的实例变量对象的属性赋值.如果实例属性存在就直接setter,如果实例属性不存在就自动new 实例
如果这个domain Model是一个实体bean,如果你通过JSP页面set了它的id,或者之前就已经将其持久化了,这就更复杂了.

4.如spring OpenSessionInViewFilter的出现,用于解决LAZYInitialization延迟加载问题
把session的周期交给servlet filter来管理,每当有request进来,就打开一个session,response结束之后才关闭它,这样可以让session存在于整个servlet request请求周期中

还有许多技术,都或多或少的产生的影响,上面的1-4是最典型的.

解决方案

  1. session.clean()
public User getUser(String id) {
    User user = getHibernateTemplate().queryForObject( hql,new Object[]{id});
    getHibernateTemplate().clean();
    return user;
}

PS:如果在clean操作后面又进行了saveOrUpdate(object)等改变数据状态的操作,有可能会报出”Found two representations of same collection”异常。
2. session.refresh(object)

public User getUser(String id) {
    User user = getHibernateTemplate().queryForObject( hql,new Object[]{id});
    getHibernateTemplate().refresh(user);
    return user;
}

PS:当object不是数据库中已有数据的对象的时候,不能使用session.refresh(object)因为该方法是从hibernate的session中去重新取object,如果session中没有这个对象,则会报错所以当你使用saveOrUpdate(object)之前还需要判断一下
3. session.merge(object)

public void saveOrUpdate(Object obj) {
    obj = getHibernateTemplate().merge(obj);
    getHibernateTemplate().saveOrUpdate(obj);
}

PS:Hibernate里面自带的方法,推荐使用。
SR-220里对session.merge()方法的描述:
Copy the state of the given object onto the persistent object with the same identifier.
将给定对象的state(状态,即实例属性)拷贝给到具有相同id的持久化对象
If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance.
如果当前没有持久化对象关联到session,当前对象会被加载为持久化对象,并(在update后)返回持久化对象
If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session.
如果给定实例还没存盘,就存一份copy,并且(这份)copy返回作为新的持久化对象.而给定的对象不会再关联到session
简单总结:merge()会用”拷贝状态copy the state”,也就是属性赋值的直接方法,完成相同id对象的更新,实际就是把内容克隆过去.
如果是一个新的对象实例,merge()实际就等同于save()和persist()
但与save()和persist()不同,merge()完成后,其操作的对象是托管态.
4.getHibernateTemplate().evict(object);

public void updateObject(T oldObject,T newObject){
    //将查询出的对象设置为游离状态
    this.ht.evict(oldObject);
    //执行更新新对象的操作
    this.ht.saveOrUpdate(newObject);
}

上述的方式很容易理解,两个对象。查询的就的对象和要更新的新对象。将查询出的对象主动设置为游离状态,那么在执行更新的操作时,session中的id就只对应一个要更新的数据了!
那么action或是controller中的方法可以这样写了
UserInfo oldUser = this.userService.queryUserByUserById(newUser.getId());
newUser.setLockTime(new Timestamp(userInfo.getLockTime().getTime()));
newUser.setUserRole(userInfo.getUserRole());
this.userService.updateUser(oldUser, newUser);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值