在使用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进行操作,并且出现了本文提到的异常