复习 - Hibernate中将detached状态对象重新与持久化环境关联的一些注意问题
首先来看一下这个例子:
package com.yxy.test; import java.util.Date; import java.util.List; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.stat.Statistics; import com.yxy.bean.Student; public class HibernateDetachedTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Statistics stat = sessionFactory.getStatistics(); /* * --------------------- Session 1 --------------------------- */ Session session = sessionFactory.openSession(); session.getTransaction().begin(); Student student = (Student)session.load(Student.class, 1); session.getTransaction().commit(); session.close(); /* * --------------------- Session 2 --------------------------- * student is in detached status. */ student.setName("Another Hibernate Session"); Session anotherSession = sessionFactory.openSession(); anotherSession.getTransaction().begin(); anotherSession.update(student); student.setName("Another Session"); anotherSession.getTransaction().commit(); anotherSession.close(); } }
student在第一个session中被加载,随后该session关闭,如果试图去更新student中的值. 就会发生LazyInitializationException,原因是在第一个session中student并没有真正被加载,持久化环境中仅有他的一个分身(Proxy),于是在detached状态时调用去initialize这个proxy,肯定就会有异常了. 如下所示
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
我们可以在session1中将真正的student实体给加载上来,就可以解决这个问题,如下所示
Student student = (Student)session.load(Student.class, 1); student.setName("Now need load the real entity, not proxy");
可见在将detached对象重新同持久化环境进行关联的时候确实有很多需要注意的地方,稍有不慎可能就会出现一些意想不到的事情. 对于重新关联的一些类型,归纳如下:
(1). 如果我们很确定对象在进入持久化环境之前做过修改 ------------------> 使用update()
如上例中所示,我们在session2中使用
anotherSession.update(student);
将改变同步到数据库中. 当然在这个时候并没有发出update语句,而是要等到事务提交的时候才发出update语句.
(2). 如果我们不是很确定对象是否被修改 -------------------> 配置select-before-update
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.yxy.bean"> <class name="Student" table="student" select-before-update="true"> <id name="id" type="int"> <generator class="native" /> </id> <property name="name" type="string" column="name"/> <property name="sex" type="string" column="sex"/> <property name="registerDate" type="date" column="register_date"/> </class> </hibernate-mapping>
这样可以让Hibernate在更新(调用update() api)之前先将对象select出来,检查是否同detached对象存在数据上差异,从而决定是否需要更新.这些都是Hibernate自动做的,呵呵.
来个例子:
package com.yxy.test; import java.util.Date; import org.hibernate.Session; import org.hibernate.SessionFactory; import com.yxy.bean.Student; public class HibernateSelectBeforeUpdateTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); /* * --------------------- Session 1 --------------------------- */ Session session = sessionFactory.openSession(); session.getTransaction().begin(); Student s = new Student(); s.setName("Select before update"); s.setRegisterDate(new Date(System.currentTimeMillis())); s.setSex("Female"); session.save(s); session.getTransaction().commit(); session.close(); /* * --------------------- Session 2 --------------------------- */ session = sessionFactory.openSession(); session.getTransaction().begin(); System.out.println("----------- select, not update -----------"); session.update(s); session.getTransaction().commit(); session.close(); /* * --------------------- Session 3 --------------------------- * s is in detached status. */ s.setName("now update"); session = sessionFactory.openSession(); session.getTransaction().begin(); System.out.println("----------- update -------------"); session.update(s); session.getTransaction().commit(); session.close(); } }
在第一个session之前中并没有做任何改动,所以Hibernate在调用update()之前将对象取出来和持久化对象中的s进行比较时,没有发现差异,故不会调用update语句,而在第二个session之前我们对s做了修改,所以当用同样的方法进行比较时,Hiberante发现两者有区别,所以会调用update语句将变化同步到数据库中.
console中打印出来的信息如下:
Hibernate: select hibernate_sequence.nextval from dual Hibernate: /* insert com.yxy.bean.Student */ insert into student (name, sex, register_date, id) values (?, ?, ?, ?) ----------- select, not update ----------- Hibernate: /* get current state com.yxy.bean.Student */ select student_.id, student_.name as name0_, student_.sex as sex0_, student_.register_date as register4_0_ from student student_ where student_.id=? ----------- update ------------- Hibernate: /* get current state com.yxy.bean.Student */ select student_.id, student_.name as name0_, student_.sex as sex0_, student_.register_date as register4_0_ from student student_ where student_.id=? Hibernate: /* update com.yxy.bean.Student */ update student set name=?, sex=?, register_date=? where id=?
(3). 我们不想让Hibernate为被修改了的detached对象生成update语句 -------------------> session.lock(***, LockMode.***);
关于LockMode的几种形式,在后面复习Hibernate事务的相关文章中再做总结了,这里需要记住的是. LockMode.NONE的特性是只有当cache里面没有相关的数据时才会去数据库中取,而且它并不会比较持久化环境和数据库中数据的差异,所以当然不会生成update语句
例子:
package com.yxy.test; import java.util.Date; import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import com.yxy.bean.Student; public class HibernateLockModeTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); /* * --------------------- Session 1 --------------------------- */ Session session = sessionFactory.openSession(); session.getTransaction().begin(); Student s = new Student(); s.setName("Select before update"); s.setRegisterDate(new Date(System.currentTimeMillis())); s.setSex("Female"); session.save(s); session.getTransaction().commit(); session.close(); /* * --------------------- Session 2 --------------------------- * s is in detached status. */ s.setName("Test LockMode NONE"); session = sessionFactory.openSession(); session.getTransaction().begin(); session.lock(s, LockMode.NONE); session.update(s); session.getTransaction().commit(); session.close(); } }
这样设置的话,可以在console中看到不会生成update语句,仅有一个在session1中生成的insert语句,这里就不打印出来了.
(4). detached对象和持久化状态中存在的某个对象代表数据库中同一条记录的情况 -------------------> merge()
对于这种情况,因为会违反一个Persistent Context中只能同时存在数据库某条记录的唯一一个引用,所以Hiberante将不能区别到底该操作那个对象,故而抛出异常,
例子:
package com.yxy.test; import java.util.Date; import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import com.yxy.bean.Student; public class HibernateMergeTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); /* * --------------------- Session 1 --------------------------- */ Session session = sessionFactory.openSession(); session.getTransaction().begin(); Student s = new Student(); s.setName("merge test"); s.setRegisterDate(new Date(System.currentTimeMillis())); s.setSex("Female"); session.save(s); session.getTransaction().commit(); session.close(); /* * --------------------- Session 2 --------------------------- * s is in detached status. */ s.setName("merge test 2"); session = sessionFactory.openSession(); session.getTransaction().begin(); Student s1 = (Student)session.load(Student.class, 90); session.update(s); session.getTransaction().commit(); session.close(); } }
出现的异常:
Exception in thread "main" org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.yxy.bean.Student#90]
这个时候可以使用merge()方法来进行解决,顾名思义,merge应该就是合并的意思.
merge会将detached对象的属性赋值给持久化环境中的那个对象,并且创建一个新的对象,这个对象的引用和持久化对象中新加载的那个对象的引用相同.
程序如下:
package com.yxy.test; import java.util.Date; import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import com.yxy.bean.Student; public class HibernateMergeTest { public static void main(String[] args){ SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); /* * --------------------- Session 1 --------------------------- */ Session session = sessionFactory.openSession(); session.getTransaction().begin(); Student s = new Student(); s.setName("merge test"); s.setRegisterDate(new Date(System.currentTimeMillis())); s.setSex("Female"); session.save(s); session.getTransaction().commit(); session.close(); /* * --------------------- Session 2 --------------------------- * s is in detached status. */ s.setName("merge test 91"); session = sessionFactory.openSession(); session.getTransaction().begin(); /* * both s1 and mergeStudent reference to the same record. */ Student s1 = (Student)session.load(Student.class, 91); Student mergeStudent = (Student)session.merge(s); session.getTransaction().commit(); session.close(); } }
从console中输出可以看出,生成了一条update语句,检查数据库中的数据,可以看到生成的91号Student的name已经更新成功.