在Hibernate中,对象的存在状态有三种,分别是:Transient(瞬时状态)、Persistent(持久化状态)、Detached(脱管/游离状态)。这三种状态的区别在于:
1.Transient:处于瞬时状态时,对象只存在于JVM内存中,并没有和Hibernate中的Session关联,没有纳入到Hibernate的缓存管理中去,在数据库中也没有与对象对应的记录。如:新建一个对象时,该对象就处于瞬时状态。 2.Persistent:处于持久化状态时,对象不仅在内存中占有空间,Hibernate缓存Session中也存在该对象,并且数据库表中有与该对象对应的记录,主键值确定。 3.Detached:处于游离状态时,对象在内存中存在,在数据库中有与之对应的记录,但是不存在于Session缓存中 示例如下:
// 新建一个User类对象,此时user处于瞬时状态(Transient) User user=new User(); user.setId(1); user.setName("Luvjuin"); // 获取Session Configuration cfg=new Configuration().configure(); SessionFactory sf=cfg.buildSessionFactory(); Session session=sf.openSession(); //开启事务 session.beginTransaction(); //保存user对象到数据库 //事务提交后,对象将会处于持久化状态(Persistent) session.save(user); //事务提交 session.getTransaction().commit(); //关闭Session和SessionFactroy //此时user对象将处于游离状态(Detached) session.close(); sf.close();4.三种状态之间的转换
transient:是由new 命令开辟的内存对象,意义仅是携带信息的载体,不和数据库中的数据有任何的关联。persistent:持久的实例在数据库中有对应的记录,拥有一个持久化标识。在执行commit之后,才在数据据库中真正运行SQL进行变更detached:与持久对象关联的Session被关闭后,对象变为托管的。对托管对象的引用依然有效,对象可以继续被修改。托管对象如果重新关联到某个新的Session上,会再次转变为持久化对象。托管期间的改动将会被持久化到数据库。在了解了Hibernate的三种状态之后,我们就可以对其常用更新数据方法进行测试。(Hibernate基础配置和类已经写好)
Persistent状态下对象没变化则不会发出SQL语句(即使手动调用update方法也不会发出update语句),有变化则自动发出update语句更新所有字段。不需要手动调用update()方法
1.对象没有发生变化时: 控制台输出的SQL语句只有get()方法发出的select语句,说明对象属性没变,即使调用update()方法,事务提交后也不会发出update语句。
Session session = sessionFactory.openSession(); session.beginTransaction(); //获取user ,处于持久化(Persistent)状态 User user=session.get(User.class, 1); //更新 session.update(user); //提交事务 session.getTransaction().commit(); session.close(); sessionFactory.close();控制台输出:
2.对象发生变化时: 控制台输出的SQL语句不仅有get()方法发出的select语句,还有一条update语句,说明在持久化状态下的对象的属性一旦变化,事务提交后,同时会自动(代码中没有手动调用update()方法)更新数据库中的记录。所有字段都将更新。
Session session = sessionFactory.openSession(); session.beginTransaction(); //获取user ,处于持久化(Persistent)状态 User user=session.get(User.class, 1); //此时修改user的属性 user.setPassword("123456"); //提交事务 session.getTransaction().commit(); session.close(); sessionFactory.close();控制台输出了update语句:
根据以上知:Persistent状态下对象没变化则不会发出SQL语句,有变化则自动发出update语句更新所有字段。这种机制在某个表的字段非常多的情况下,程序执行效率将会大大降低,因为一旦改动某个字段,将会update所有字段值,虽然其他字段值没变。
Persistent状态下,修改了对象的属性,但只想要update修改过的字段值,做法如下 如上要求,只需要修改xxx.hbm.xml配置文件即可。 在对应的对象映射配置文件中的class标签上增加一个属性 dynamic-update=“true”。表示开启动态更新。这样,处于持久化状态下的对象变化时,update语句更新的只是变化的字段,不会更新所有字段。
<class name="bean.User" table="TB_USER" schema="YP" dynamic-update="true">依旧是上面的代码,将user.setPassword(“123456”)改为user.setPassword(“123”)(因为上面程序执行过一次,password值为123456,如果不改,值没变),运行程序,控制台输出:update 只更新了password,并没有更新name。
Detached状态下不管JVM内存中对象的属性值和数据库表中的值是否一致,调用update方法后一定会发出一条update语句,这和Persistent状态下是不一致的
运行以下代码,从数据库中get()到的user属性没有变过,关闭前一个Session后,对象处于游离状态。开启另外一个Session,手动调用update(),控制台输出update语句。
Session session = sessionFactory.openSession(); session.beginTransaction(); //获取user ,此时处于持久化(Persistent)状态 User user=session.get(User.class, 1); //提交事务 session.getTransaction().commit(); session.close(); //session关闭之后处于游离(Detached)状态 //开启另外一个Session Session session2=sessionFactory.getCurrentSession(); session2.beginTransaction(); session2.update(user);//处于持久化状态 session2.getTransaction().commit(); session2.close();//游离状态 sessionFactory.close();例子说明:Detached状态下不管JVM内存中对象的属性值和数据库表中的值是否一致,调用update方法后一定会发出一条update语句,而Persistent状态下只有对象变化了才会发出update语句。
Detached状态下使用merge()方法更新数据
把上面代码中的session2.update(user)更改为session2.merge(user)。运行程序,会发现控制台输出了两条select语句,并没有像上述一样:游离状态下不管对象有没有变,都会发出update语句。
Session session = sessionFactory.openSession(); session.beginTransaction(); //获取user ,此时处于持久化(Persistent)状态 User user=session.get(User.class, 1); //提交事务 session.getTransaction().commit(); session.close(); //session关闭之后处于游离(Detached)状态 //开启另外一个Session Session session2=sessionFactory.getCurrentSession(); session2.beginTransaction(); session2.merge(user);//处于持久化状态 session2.getTransaction().commit(); session2.close();//游离状态 sessionFactory.close();这是因为游离状态下merge()方法总会发出一条select语句(所以控制台打印了两条select),然后将select到的数据与内存中的对象进行对比,如果不一致,则发出update语句,且只会更新不一样的字段,不会更新所有字段,之后进入持久化状态。如果内存中的对象与select到的数据一致,则不会发出update()。
Transient状态下更新数据要指定主键,否则不能更新
Transient状态下,对象只存在于内存中。此时如果调用update()方法去更新数据库,则该对象对应数据库中主键的属性值必须要有,不能为空。如果为空,则会报错。
Session session = sessionFactory.openSession(); session.beginTransaction(); //新建一个对象 瞬时状态 User user = new User(); user.setName("jkl"); user.setPassword("123456"); // 主键值确定 user.setId(1); //update session.update(user); // 提交事务 持久化状态 session.getTransaction().commit(); session.close(); //游离状态 sessionFactory.close();在User 类中,id为数据库中的主键。如果将以上代码中 的user.setId(1) 去掉,则运行程序的时候报错:The given object has a null identifier:bean.User。(user对象的id属性为空,但该值在数据库表中应该为主键)
在调用这个方法前,我们要将xxx.hbm.xml配置文件中的主键生成策略改为native。
调用这个方法时,如果对象的对应主键值确定的话,那么事务提交之后,会发出update()语句更新所有字段;如果对象的对应主键值不确定时,会发出select语句,保存一条新记录
Session session = sessionFactory.openSession(); session.beginTransaction(); //新建一个对象 瞬时状态 User user = new User(); user.setName("uiooj"); user.setPassword("123456"); // 主键值确定 user.setId(1); //update session.saveOrUpdate(user); // 提交事务 持久化状态 session.getTransaction().commit(); session.close(); //游离状态 sessionFactory.close();如果将user.setId(1)注释掉,那么主键值不确定,运行程序后会发出insert 语句,插入一条新纪录。
由以上看出:saveOrUpdate()方法调用时,如果 对象对应的主键值确定:update; 对象对应的主键值不确定:insert;
flush()方法指的是强制缓冲数据与数据库数据保持一致; clear()方法指的是清空缓存。
详解参考如下:
王康的技术博客—flush()和clear()方法详解