Hiberate框架进行更新时,报错 :a different object with the same identifier value was already ..

问题探究

场景描述:保存前先去数据库中取了一次数据(放session缓存里),然后再新建一个主键相同对象,保存更新时报错。

问题:这个著名的托管态update更新异常org.hibernate.NonUniqueObjectException : a different object with the same identifier value was already associated withthe session

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

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


  • 现在问题的新变化:

       1、hibernate3.0以后,getCurrentSession()技术的出现,session pool的出现,session不再需要手动open,也不需要手动关闭,反而使问题复杂化
       2、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是最典型的.
  •   a different object with the same identifier value was already associated withthe session    问题的解决:
           其实我没有特别好的解决办法,问题本身也是见仁见智的,需要针对不同场景不同业务逻辑随机应变.本文只是探讨性的文章,属于开放性质的.
            从我的经验来说,我倾向于从两方面解决:
           1. session方面
            从程序的角度,多分析问题,找到session的开始和结束点. 不过说句实话,这真的很难,因为现在的代码越来越框架化,模块化,封装得越来越深,session根本不暴露在业务层之外, 操作经常要上溯到很高层的父类,尤其当和事务挂钩时,更是复杂. 而且现在session的底层操作也越来越晦涩,基于池操作的,基于ThreadLocal操作的,基于JTA的.
           2.从update()方面
             目前比较普遍的观点,用merge()方法解决是一个比较"傻瓜"的解决办法.JSR-220里对session.merge()方法的描述:
Copy the state of the given object onto the persistent object with the same identifier. 将给定对象的state(状态,即实例属性)拷贝给到具有相同id的持久化对象

    简单总结:
      merge()会用"拷贝状态copy the state",也就是属性赋值的直接方法,完成相同id对象的更新,实际就是把内容克隆过去.
      如果是一个新的对象实例,merge()实际就等同于save()和persist()
      但与save()和persist()不同,merge()完成后,其操作的对象是托管态.

下面分析Hibernate merge、update与saveOrUpdate方法的区别:
       通常下面的场景会使用update()或saveOrUpdate():

  • 程序在第一个session中加载对象
  • 该对象被传递到表现层
  • 对象发生了一些改动
  • 该对象被返回到业务逻辑层
  • 程序调用第二个session的update()方法持久这些改动

        saveOrUpdate()做下面的事:

  • 如果对象已经在本session中持久化了,不做任何事
  • 如果另一个与本session关联的对象拥有相同的持久化标识(identifier),抛出一个异常
  • 如果对象没有持久化标识(identifier)属性,对其调用save()
  • 如果对象的持久标识(identifier)表明其是一个新实例化的对象,对其调用save()
  • 如果对象是附带版本信息的(通过<version>或<timestamp>) 并且版本属性的值表明其是一个新实例化的对象,save()它。
  • 否则update() 这个对象


        merge()可非常不同:

  • 如果session中存在相同持久化标识(identifier)的实例,用用户给出的对象的状态覆盖旧有的持久实例
  • 如果session没有相应的持久实例,则尝试从数据库中加载,或创建新的持久化实例
  • 最后返回该持久实例
  • 用户给出的这个对象没有被关联到session上,它依旧是脱管的 


       update和merge方法,例子
      1. 数据库记录已存在,更改person的name为一个新的name。  

       merge方法打印出的日志如下:
       Hibernate: select person0_.id as id0_0_, person0_.name as name0_0_ from person person0_ where person0_.id=?  
       Hibernate: update person set name=? where id=?

       update方法打印出的日志如下:
       Hibernate: update person set name=? where id=?

      2. 数据库记录已存在,更改person的name和数据库里对应id记录的name一样的值。  

      merge方法打印出的日志如下:  
      Hibernate: select person0_.id as id0_0_, person0_.name as name0_0_ from person person0_ where person0_.id=?
      此处相对于第一种情形少了update的动作

      update方法打印出的日志如下:
      Hibernate: update person set name=? where id=?

     3. 数据库记录不存在时,也就是你传的实体bean的ID在数据库没有对应的记录。

     merge方法打印出的日志如下:
     Hibernate: select person0_.id as id0_0_, person0_.name as name0_0_ from person person0_ where person0_.id=?
     Hibernate: insert into person (name) values (?)
     如果没有对应的记录,merge会把该记录当作新的记录来插入。此处我很疑惑,因为我传得person实体对象里写明了id值的,它为什么还会做插入的动作呢?

    update方法打印出的日志如下:
    Hibernate: update person set name=? where id=? 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值