a different object with the same identifier value was already associated with the session

来源:互联网 发布:淘宝的星期四官换机 编辑:程序博客网 时间:2024/06/08 11:19

http://www.cnblogs.com/zj55com/archive/2008/01/24/1052125.html

 

发生这个错误 ,在hibernate中同一个session里面有了两个相同标识但是是不同实体可以用session.merge解决;而在nhibernate没有这个方法,后发现SaveOrUpdateCopy可以解决此问题,特拿出来供参考及分享.
http://www.diybl.com/course/1_web/javascript/jsjs/20090831/173181.html

在使用hibernate的操作数据的过程中如update(),有时候会出现hibernate a different object with the same identifier value was already associated with the session这个异常。

从这个异常的描述中已经可以大致明白是什么原因导致了这个异常的发生:多个不同的对象具有相同identifier(id和实体类型一样,应该是同一个数据库中的记录),进行update等操作时,出现异常。

想想看,如果在一个session里,出现了多个相同的数据库记录(identifier相同的多个entity),hibernate如何判断那个entity才是正确的呢?因此,hibernate抛出这个异常是应该的:)(网上有些人问这是不是hibernate的bug)

问题已经说明白了,就是在一个session中,多个实体对象对应数据库中的同一条记录造成的。

出现这种问题的原因是我们之前通过某个session获取得到了某个实体对象(该对象有hibernate托管了),但是后来又再次构造了一个具有相同identifier(id和实体类型一样,应该是同一个数据库中的记录)的实体 ,然后又在这个session中操作(如update)新构造出来的实体造成的。这里说的再次构造有可能是通过代码手动构造的一个游离态(detached)实体,也可能是通过hibernate的级联操作,加载的实体。

我们通过具体的实例来说明(仅写关键的代码):

实例一,手动构造一个detached实体的情况:

Session sessionA = dao.getSession();  //为便于理解,我们一直是用sessionA这个对象

Persion person1 = (Person) sessionA.load(Person.class,1); //获取id=1的Person数据库记录

...

Person person2 = new Person();

person2.setId(1);  //手动构造一个detached Person实体,id也是1

person2.setName("gaofei");

...

sessionA.update(person2);  //person2和person1的identifier是一样的,但是与person1又不是一个对象,问题产生了

...


    实例二,hibernate级联加载的情况: 假设一个学生Student只能属于一个班级Class,一个教师Teacher也只能属于一个班级Class,Student与Class级联加载保存,Teacher与Class级联加载保存。
Session sessionA = dao.getSession();  //我们需要用到3个session,这是第1个

Student student = (Student) sessionA.load(Student.class,1); //假定其对应的Class id=1,此时Class已经被加载到缓存

...

Session sessionB = dao.getSession();  //我们需要用到3个session,这是第2个

Teacher teachr =  (Teacher) sessionB.load(Teachr.class,1); //假定其对应的Class id=1,此时Class已经被加载到缓存,但是与之前的Class已经不是一个对象,因为不是一个session加载的


Session sessionC = dao.getSession();  //我们需要用到3个session,这是第3个

Transaction tx = sessionC.begionTransaction();

sessionC.save(student);  //级联的Class id=1

sessionC.save(teacher);  //级联的Class id=1,但是和上一行的class不是一个对象,同一个session中出现了两个id相同的class,出错报异常

tx.commit(); 

...

通过上面的例子,我们可以看到,在同一个session中,出现了多个相同identifier的实体,hibernate出现异常。 问题的解决办法是:使用session中的托管实体,由hibernate维护该实体的状态。 对于实例1,可以修改如下:
Session sessionA = dao.getSession(); 

Persion person1 = (Person) sessionA.load(Person.class,1); //获取id=1的Person数据库记录

...

//Person person2 = new Person();

//person2.setId(1);  //手动构造一个detached Person实体,id也是1

person1.setName("gaofei");

...

sessionA.update(person1);  //一直使用hibernate的托管对象

...对于实例2,可以修改如下:
Session sessionA = dao.getSession();  //一直使用sessionA

Student student = (Student) sessionA.load(Student.class,1); //假定其对应的Class id=1,此时Class已经被加载到缓存

...

Teacher teachr =  (Teacher) sessionA.load(Teachr.class,1); //假定其对应的Class id=1,此时Class仍然是之前加载到session缓存中实体对象

Transaction tx = sessionA.begionTransaction(); 

sessionA.save(student); 

sessionA.save(teacher);  //级联的Class id=1,只有唯一的一个对象

tx.commit(); 

...

总结:可以看出,在数据的整个操作过程中,如果一直使用session中已经托管的实体对象,可以解决本文提到的异常。 如果确实需要跨session进行操作,并且出现了本文提到的异常


文章出处:飞诺网(www.diybl.com):http://www.diybl.com/course/1_web/javascript/jsjs/20090831/173181.html


http://www.diybl.com/course/1_web/webjs/2008515/116661.html

Category是树形结构,以下是实现修改功能的代码,一开始,我用了getHibernateTemplate().update()

结果出现a different object with the same identifier value was already associated with the session

,上网查了查,使用了getSession().flush();
  getSession().clear();

之后,另外一个问题出现了:Found two representations of same collection

于是乎,继续找办法解决,最后发现getHibernateTemplate().merge(category);

可以彻底解决问题,然而原理到底是如何,却是弄不明白。

 

//修改category
    public void updateCategory(Category category) ...{       
       
        //按照传进来的category,先把原有的category读取出来
        Category cc=(Category)getHibernateTemplate()
                    .get(Category.class, category.getId());

        //把原有的孩子拿到
        category.setChildren(cc.getChildren());

        //把原有的父亲拿到
        category.setParent(cc.getParent());
       
        //保存修改
               
        getSession().flush();
        getSession().clear();
        //不要用update()方法
        getHibernateTemplate().merge(category);

    }

=====================

http://www.blogjava.net/TiGERTiAN/archive/2008/10/25/236519.html

Hibernate三种状态的区分,以及save,update,saveOrUpdate,merge等的使用

Hibernate的对象有3种状态,分别为:瞬时态(Transient)、 持久态(Persistent)、脱管态(Detached)。处于持久态的对象也称为PO(Persistence Object),瞬时对象和脱管对象也称为VO(Value Object)。
瞬时态
        由new命令开辟内存空间的java对象,

       eg. Person person = new Person("xxx", "xx");

        如果没有变量对该对象进行引用,它将被java虚拟机回收。

         瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系,在Hibernate中,可通过session的save()或saveOrUpdate()方法将瞬时对象与数据库相关联,并将数据对应的插入数据库中,此时该瞬时对象转变成持久化对象。

持久态
        处于该状态的对象在数据库中具有对应的记录,并拥有一个持久化标识。如果是用hibernate的delete()方法,对应的持久对象就变成瞬时对象,因数据库中的对应数据已被删除,该对象不再与数据库的记录关联。

       当一个session执行close()或clear()、evict()之后,持久对象变成脱管对象,此时持久对象会变成脱管对象,此时该对象虽然具有数据库识别值,但它已不在HIbernate持久层的管理之下。

       持久对象具有如下特点:

        1. 和session实例关联;

       2. 在数据库中有与之关联的记录。

脱管态
        当与某持久对象关联的session被关闭后,该持久对象转变为脱管对象。当脱管对象被重新关联到session上时,并再次转变成持久对象。

       脱管对象拥有数据库的识别值,可通过update()、saveOrUpdate()等方法,转变成持久对象。

       脱管对象具有如下特点:

       1. 本质上与瞬时对象相同,在没有任何变量引用它时,JVM会在适当的时候将它回收;

        2.   比瞬时对象多了一个数据库记录标识值。

 

hibernate的各种保存方式的区(save,persist,update,saveOrUpdte,merge,flush,lock)及 对象的三种状态
hibernate的保存
hibernate对于对象的保存提供了太多的方法,他们之间有很多不同,这里细说一下,以便区别。
一、预备知识
对于hibernate,它的对象有三种状态,transient、persistent、detached
下边是常见的翻译办法:
transient:瞬态或者自由态
(new DeptPo(1,”行政部”,20,”行政相关”),该po的实例和session没有关联,该po的实例处于transient)
persistent:持久化状态
(和数据库中记录想影射的Po实例,它的状态是persistent, 通过get和load等得到的对象都是persistent)
detached:脱管状态或者游离态
(1)当通过get或load方法得到的po对象它们都处于persistent,但如果执行delete(po)时(但不能执行事务),该po状态就处于detached, (表示和session脱离关联),因delete而变成游离态可以通过save或saveOrUpdate()变成持久态
(2)当把session关闭时,session缓存中的persistent的po对象也变成detached
因关闭session而变成游离态的可以通过lock、save、update变成持久态
持久态实例可以通过调用 delete()变成脱管状态。
通过get()或load()方法得到的实例都是持久化状态的。
脱管状态的实例可以通过调用lock()或者replicate()进行持久化。

save()和persist()将会引发SQL的INSERT,delete()会引发SQLDELETE,
而update()或merge()会引发SQL UPDATE。对持久化(persistent)实例的修改在刷新提交的时候会被检测到,它也会引起SQL UPDATE。
saveOrUpdate()或者replicate()会引发SQLINSERT或者UPDATE
二、save 和update区别
把这一对放在第一位的原因是因为这一对是最常用的。
save的作用是把一个新的对象保存
update是把一个脱管状态的对象或自由态对象(一定要和一个记录对应)更新到数据库

三、update 和saveOrUpdate区别
这个是比较好理解的,顾名思义,saveOrUpdate基本上就是合成了save和update,而update只是update;引用hibernate reference中的一段话来解释他们的使用场合和区别
通常下面的场景会使用update()或saveOrUpdate():
程序在第一个session中加载对象,接着把session关闭
该对象被传递到表现层
对象发生了一些改动
该对象被返回到业务逻辑层最终到持久层
程序创建第二session调用第二个session的update()方法持久这些改动

saveOrUpdate(po)做下面的事:
如果该po对象已经在本session中持久化了,在本session中执行saveOrUpdate不做任何事
如果savaOrUpdate(新po)与另一个与本session关联的po对象拥有相同的持久化标识(identifier),抛出一个异常
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [org.itfuture.www.po.Xtyhb#5]
saveOrUpdate如果对象没有持久化标识(identifier)属性,对其调用save() ,否则update() 这个对象

四、persist和save区别
这个是最迷离的一对,表面上看起来使用哪个都行,在hibernate reference文档中也没有明确的区分他们.
这里给出一个明确的区分。(可以跟进src看一下,虽然实现步骤类似,但是还是有细微的差别)
主要内容区别:
1,persist把一个瞬态的实例持久化,但是并"不保证"标识符(identifier主键对应的属性)被立刻填入到持久化实例中,标识符的填入可能被推迟到flush的时候。

2,save, 把一个瞬态的实例持久化标识符,及时的产生,它要返回标识符,所以它会立即执行Sql insert

五、saveOrUpdate,merge和update区别
比较update和merge
update的作用上边说了,这里说一下merge的
如果session中存在相同持久化标识(identifier)的实例,用用户给出的对象覆盖session已有的持久实例
(1)当我们使用update的时候,执行完成后,会抛出异常
(2)但当我们使用merge的时候,把处理自由态的po对象A的属性copy到session当中处于持久态的po的属性中,执行完成后原来是持久状态还是持久态,而我们提供的A还是自由态

六、flush和update区别
这两个的区别好理解
update操作的是在自由态或脱管状态(因session的关闭而处于脱管状态)的对象//updateSQL
而flush是操作的在持久状态的对象。
默认情况下,一个持久状态的对象的改动(包含set容器)是不需要update的,只要你更改了对象的值,等待hibernate flush就自动更新或保存到数据库了。hibernate flush发生在以下几种情况中:
1, 调用某些查询的和手动flush(),session的关闭、SessionFactory关闭结合
get()一个对象,把对象的属性进行改变,把资源关闭。
2,transaction commit的时候(包含了flush)

七、lock和update区别
update是把一个已经更改过的脱管状态的对象变成持久状态
lock是把一个没有更改过的脱管状态的对象变成持久状态(针对的是因Session的关闭而处于脱管状态的po对象(2),不能针对因delete而处于脱管状态的po对象)
对应更改一个记录的内容,两个的操作不同:
update的操作步骤是:
(1)属性改动后的脱管的对象的修改->调用update
lock的操作步骤是:
(2)调用lock把未修改的对象从脱管状态变成持久状态-->更改持久状态的对象的内容-->等待flush或者手动flush
八、clear和evcit的区别
clear完整的清除session缓存
evcit(obj)把某个持久化对象从session的缓存中清空。


===

http://blog.csdn.net/xiamizy/article/details/1958794

 hibernate中load,get;find,iterator;merge,saveOrUpdate,lock的区别

转自http://www.blogjava.net/bnlovebn/archive/2007/07/13/130025.html

一、load,get
(1)当记录不存在时候,get方法返回null,load方法产生异常

(2)load方法可以返回实体的代理类,get方法则返回真是的实体类

(3)load方法可以充分利用hibernate的内部缓存和二级缓存中的现有数据,而get方法仅仅在内部缓存中进行数据查找,如果没有发现数据則将越过二级缓存,直接调用SQL查询数据库。
   (4) 也许别人把数据库中的数据修改了,load如何在缓存中找到了数据,则不会再访问数据库,而get则会返回最新数据。
 
二、find,iterator
     (1) iterator首先会获取符合条件的记录的id,再跟据id在本地缓存中查找数据,查找不到的再在数据库中查找,结果再存在缓存中。N+1条SQL。
 (2)find跟据生成的sql语句,直接访问数据库,查到的数据存在缓存中,一条sql。

三、Hibernate生成的DAO类中函数功能说明(merge,saveOrUpdate,lock)

/**
      * 将传入的detached状态的对象的属性复制到持久化对象中,并返回该持久化对象。
      * 如果该session中没有关联的持久化对象,加载一个。
      * 如果传入对象未保存,保存一个副本并作为持久对象返回,传入对象依然保持detached状态。
      */

public Sysuser merge(Sysuser detachedInstance) {
      log.debug("merging Sysuser instance");
      try {
       Sysuser result = (Sysuser) getHibernateTemplate().merge(
         detachedInstance);
       log.debug("merge successful");
       return result;
      } catch (RuntimeException re) {
       log.error("merge failed", re);
       throw re;
      }
}

/**
      * 将传入的对象持久化并保存。 如果对象未保存(Transient状态),调用save方法保存。
      * 如果对象已保存(Detached状态),调用update方法将对象与Session重新关联。
      */
public void attachDirty(Sysuser instance) {
      log.debug("attaching dirty Sysuser instance");
      try {
       getHibernateTemplate().saveOrUpdate(instance);
       log.debug("attach successful");
      } catch (RuntimeException re) {
       log.error("attach failed", re);
       throw re;
      }
}

/**
      * 将传入的对象状态设置为Transient状态
      */

public void attachClean(Sysuser instance) {
      log.debug("attaching clean Sysuser instance");
      try {
       getHibernateTemplate().lock(instance, LockMode.NONE);
       log.debug("attach successful");
      } catch (RuntimeException re) {
       log.error("attach failed", re);
       throw re;
      }
}

===

http://wdmcln.iteye.com/blog/154066

Exception:a different object with the same identifier value was already associated with the session
在session中具有相同标识符的不同对象已经关联

1、手动控制事务时,每次持久化操作后将session清除或关闭(衡量性能);
2、覆写对象的hashCode、equals方法,增加部分字段,避免只比较主键来区分不同的对象(Eclipse插件);
3、手动确保需要持久化的对象主键不重复;

Java代码 复制代码 收藏代码
  1. org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.legion.km.mapping.hibernate.pojo.TRRResrpt#23878]   
  2.     at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:167)   
  3.     at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:121)   
  4.     at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)   
  5.     at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)   
  6.     at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)   
  7.     at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)   
  8.     at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)   
  9.     at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:534)   
  10.     at org.hibernate.impl.SessionImpl.save(SessionImpl.java:522)   
  11.     at org.hibernate.impl.SessionImpl.save(SessionImpl.java:518)   
  12.     at com.legion.km.mapping.kmdb.impl.TargetDBImpl.insertData(TargetDBImpl.java:223)   
  13.     at com.legion.km.mapping.kmdb.impl.KMDBMapping.main(KMDBMapping.java:83)  


   1) 如果用的 hibernate 2, 需要在get/load/query到持久化对象,赋上新的属性值,再 save/update/saveOrupdate.
      对以上代码就是:不能 new 一个session中已存在OID的对象,直接
      detail.setSubjectId(1000L);
      session.save(detail);
      session.save()一个持久化对象时,会转化成update调用。

   2) 使用 hibernate 3 的 merge 方法. session.merge(newDetail)即可,它会在 session 缓存中找到持久化对象,把新对象的属性赋过去,再保存原session中的持久化对象。
      如果在session或数据库中没有的对象,用merge方法的话,它也能够帮你把记录 insert 到表中,相当于 save 方法。

上面是一个简单的例子,实际业务中可能是经过一番复杂的操作后自己也很难搞清楚 new 的一个新对象在 session/数据库中是否已存在。所以第一种方法你需要清楚你的每一个对象状态,第二种方法在 hibernate 3 中就比较通用一些。

附 hibernate javadoc 对 session.merge() 方法的注释:
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. 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. This operation cascades to associated instances if the association is mapped with cascade="merge".

The semantics of this method are defined by JSR-220.

 

 

解决方法evict()

不管何时你传递一个对象给save(), update()或者 saveOrUpdate() ,或者不管何时你使用load(), find(), iterate()或者filter()取得一个对象的时候,该对象被加入到Session的内部缓存中。当后继的flush()被调用时,对象的状态会和数据库进行同步。如果你在处理大量对象并且需要有效的管理内存的时候,你可能不希望发生这种同步,evict()方法可以从缓存中去掉对象和它的集合。

Iterator cats = sess.iterate("from eg.Cat as cat"); //a huge result set
while ( cats.hasNext() ) {
    Cat cat = (Cat) iter.next();
    doSomethingWithACat(cat);
    sess.evict(cat);
}
Session也提供了一个contains()方法来判断是否一个实例处于这个session的缓存中。

要把所有的对象从session缓存中完全清除,请调用Session.clear()。

For the JVM-level JCS cache, there are methods defined on SessionFactory for evicting the cached state of an instance, entire class, collection instance or entire collection role.

对于第二层缓存来说,在SessionFactory中定义了一些方法来从缓存中清除一个实例、整个类、集合实例或者整个集合。

 ===========================================================

10.7. Automatic state detection

The usage and semantics of saveOrUpdate() seems to be confusing for new users. Firstly, so long as you are not trying to use instances from one session in another new session, you should not need to use update(), saveOrUpdate(), or merge(). Some whole applications will never use either of these methods.

Usually update() or saveOrUpdate() are used in the following scenario:

the application loads an object in the first session

the object is passed up to the UI tier

some modifications are made to the object

the object is passed back down to the business logic tier

the application persists these modifications by calling update() in a second session

saveOrUpdate() does the following:

if the object is already persistent in this session, do nothing

if another object associated with the session has the same identifier, throw an exception

if the object has no identifier property, save() it

if the object's identifier has the value assigned to a newly instantiated object, save() it

if the object is versioned (by a <version> or <timestamp>), and the version property value is the same value assigned to a newly instantiated object, save() it

otherwise update() the object

and merge() is very different:

if there is a persistent instance with the same identifier currently associated with the session, copy the state of the given object onto the persistent instance

if there is no persistent instance currently associated with the session, try to load it from the database, or create a new persistent instance

the persistent instance is returned

the given instance does not become associated with the session, it remains detached