瞬时状态就是刚new出来一个对象,还没有被保存到数据库中;
持久化状态就是已经被保存到数据库中;
离线状态就是数据库中有,但是session中不存在该对象。
后续将有大量测试用例,操作hibernate对象要用到session,每次都要加载hibernate.cfg.xml文件、创建SessionFactory对象、创建Session对象、关闭session,步骤都是一样的,这里就新建一个HibernateUtil工具类来提高代码的复用性。
HibernateUtil.java
此版本适用于Hibernate5的getCurrentSession,如果要用Hibernate3或Hibernate4或者将getCurrentSession换成openSession,可参考:http://blog.csdn.net/qinshijangshan/article/details/53314729进行配置。
package com.ack.core;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class HibernateUtil {
private static SessionFactory sessionFactory;
static{
try{
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().configure().build();
sessionFactory=new MetadataSources(serviceRegistry).buildMetadata().buildSessionFactory();
}catch(Exception e){
e.printStackTrace();
}
}
//获得开启着的Session
public static Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
public static SessionFactory getSessionFactory(){
return sessionFactory;
}
}
hibernate配置、实体类和类的映射文件就不再给出,可参考:hibernate环境和简单的测试用例http://blog.csdn.net/qinshijangshan/article/details/53312213
1.TestTransient
public void TestTransient(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setUserName("陈大大");
user.setStatus("0");
user.setBirthday(new Date());
//此时user就是一个Transient(瞬时状态),此时user并没有被session进行托管,即在session的缓存中还不存在user这个对象
session.save(user);
//当执行完save方法后,此时user被session托管,并且数据库中存在了该对象
//user就变成了一个Persistent(持久化对象)
session.getTransaction().commit();
}
save方法后立即进行insert操作插入一条数据到数据库,随即瞬时状态对象变成持久化状态对象,数据在事务提交之后才真正存在于数据库(事务不提交只是执行了sql语句,数据库存在该对象,数据还未写入,这是数据库的事务管理,暂且不管);
2.TestPersistent01
public void TestPersistent01(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setUserName("陈大大");
user.setStatus("0");
user.setBirthday(new Date());
//以上u就是Transient(瞬时状态),表示没有被session管理,并且数据库中没有
session.save(user);
//执行save之后,执行insert sql语句,数据库中已经存在user对象;
//瞬时状态对象变为持久化对象,被session所管理;
System.out.println("================= User SAVE =================");
user.setUserName("陈大牙");
System.out.println("================= User UPDATE =================");
//比较set值与当前session中user对象的值userName已经改变,发送update sql语句
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
从方法内容执行顺序以及 insert sql语句在User SAVE之前,User SAVE和User UPDATE上下行紧挨着,update sql语句没在User SAVE和User UPDATE之间执行,在User UPDATE之后(或者说是事务提交时)才执行的update sql语句,为什么不在改变userName值之后。
3.TestPersistent02
public void TestPersistent02(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setUserName("陈大大");
user.setStatus("0");
user.setBirthday(new Date());
//以上u就是Transient(瞬时状态),表示没有被session管理,并且数据库中没有
session.save(user);
//执行save之后,执行insert sql语句,数据库中已经存在user对象;
//瞬时状态对象变为持久化对象,被session所管理;
System.out.println("================= User SAVE =================");
user.setUserName("陈大大");
System.out.println("================= User UPDATE =================");
//如果参照TestPersistent01那么这里也就会执行相应的update sql语句,实际没有,因为userName的值未改变
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
TestPersistent02区别于TestPersistent01为save()后的set值相对于session中的user对象未改变;
TestPersistent02持久化状态时set的值与session中对象的值相同(未改变),所以这就是TestPersistent01在User UPDATE和Transaction之间执行了update sql语句,而TestPersistent02未执行的原因;
到这里我们还未明白为什么update语句为什么不是在User SAVE和User UPDATE之间执行。
猜测:持久化态的对象后面操作会在事务提交时判断session中对象的值是否有改变,决定是否进行后续数据库操作。
4.TestPersistent03
public void TestPersistent03(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setUserName("陈大大");
user.setStatus("0");
user.setBirthday(new Date());
//以上u就是Transient(瞬时状态),表示没有被session管理,并且数据库中没有
//执行save之后,被session所管理,而且,数据库中已经存在,此时就是Persistent状态
session.save(user);
System.out.println("================= User SAVE =================");
//此时u是持久化状态,已经被session所管理,当在提交时,会把session中的对象和目前的对象进行比较
//如果两个对象中的值不一致就会继续发出相应的sql语句
user.setUserName("陈大大");
session.update(user);
System.out.println("================= User UPDATE1 =================");
user.setLoginName("chensan");
session.update(user);
System.out.println("================= User UPDATE2 =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
TestPersistent03在TestPersistent02的基础上增加了User UPDATE1到事务提交前的语句(session中的user对象的loginName值相对于之前改变了)。
结果insert sql语句后,User SAVE、User UPDATE1和User UPDATE2上下行挨着,在User UPDATE2和Transaction COMMIT之间才执行了update sql语句。看来是比较了session中user对象的值前后是否有改变来决定是否需要执行相应sql语句的;
那么对应有修改是不是次次都会执行sql语句呢?继续看下一个例子
5.TestPersistent04
public void TestPersistent04(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setUserName("陈大大");
user.setStatus("0");
user.setBirthday(new Date());
//以上u就是Transient(瞬时状态),表示没有被session管理,并且数据库中没有
session.save(user);
//执行save之后,执行insert sql语句,数据库中已经存在user对象;
//瞬时状态对象变为持久化对象,被session所管理;
System.out.println("================= User SAVE =================");
user.setUserName("陈大牙");
session.update(user);
System.out.println("================= User UPDATE1 =================");
user.setLoginName("chensan");
session.update(user);
System.out.println("================= User UPDATE2 =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
insert之后userName和loginName分别修改提交,可之后只在User UPDATE2和Transaction COMMIT之间发送了一条update sql语句;
我们也就知道了:在对象成了持久化状态对象后,后续操作会在事务提交时根据session中持久化对象的值是否改变来确定是否进行数据库操作,不是只要update一次就会进行数据库操作;
6.TestPersistent05
public void TestPersistent05(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setUserName("陈大大");
user.setStatus("0");
user.setBirthday(new Date());
//以上u就是Transient(瞬时状态),表示没有被session管理,并且数据库中没有
session.save(user);
//执行save之后,执行insert sql语句,数据库中已经存在user对象;
//瞬时状态对象变为持久化对象,被session所管理;
System.out.println("================= User SAVE =================");
user.setUserName("陈大牙");
session.update(user);
System.out.println("================= User UPDATE1 =================");
user.setUserName("陈大大");
session.update(user);
System.out.println("================= User UPDATE2 =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
userName修改后update,再修改再update,实际userName到最后值并未改变,所以可以预见:User UPDATE2和Transaction COMMIT之间也不会去有什么update sql语句了。
结论:如果一个对象是持久化状态了,这个持久化状态的对象将由session托管。此后对该对象进行各种修改,或者调用多次update、save方法 都不会进行数据库操作。只有当事物提交的时候,hibernate才会比较session中的持久化对象的属性最终的值是否有改变来确定是否发送相应的sql语句来进行数据库操作。
瞬时状态对象遇到save、saveOrUpdate、update就会变成持久化对象。
注:
1.持久化对象由session管理,都是同一个对象它是怎么比的。
2.看前面例子有的set属性值并没有update()却实现了update()的作用。
7.TestPersistent06
public void TestPersistent06(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = session.get(User.class, 1);
System.out.println("================= User GET =================");
user.setLoginName("chensan");
System.out.println("================= User UPDATE =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
get()方法获取数据库中id=1的user对象,在User GET之前执行了select sql语句;user对象的属性loginName的值有修改,在User UPDATE和Transaction COMMIT之间执行了update语句;说明get()方法使得离线对象变成了持久化对象;
执行第二次,那么也就不会发送update sql语句了,因为值未变;
当然id=1的数据记录不存在,那么get()就会报错,Done entity load : com.ack.hibernate.User#2,空指针异常;
8.TestPersistent07
public void TestPersistent07(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = session.load(User.class, 1);
System.out.println("================= User GET =================");
user.setLoginName("cs");
System.out.println("================= User UPDATE =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
load()后select sql语句,之后打印User GET,在User UPDATE和Transaction COMMIT之间执行update sql语句。
执行第二次,值未修改,也是未执行update sql语句。
9.TestPersistent08
public void TestPersistent08(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = session.get(User.class, 1);
session.clear();
System.out.println("================= User GET =================");
user.setLoginName("chensan");
System.out.println("================= User UPDATE =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
clear()清空session后,user对象变成了瞬时状态对象;后面loginName改变值,在User UPDATE和Transaction COMMIT之间不发送update sql语句。
结论:get、load方法之后获取的数据库离线对象变成持久化对象。
因从数据库获取的对象,未set值的字段将不会修改;下面示例离线状态setId获取对象,对未set值的字段是采取清空方式。
TestDetached01
public void TestDetached01(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(1);
user.setStatus("1");
//session.save(user);
session.update(user);
System.out.println("================= User UPDATE1 =================");
user.setUserName("陈三");
session.update(user);
System.out.println("================= User UPDATE2 =================");
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
id=1的user对象在数据库中已存在,但此处是new一个对象在session中不存在,所以user对象为离线状态;
save方法根据hibernate主键生成策略,在数据库insert一条语句,而我们希望的是对id=1的user对象进行更新操作。所以离线对象不应该用save()而是update();
第一次update和第二次update都修改了user对象的值,然而只在User UPDATE2和Transaction COMMIT之间执行了一次update sql语句。那么到底什么时候离线状态的user对象持久化了,是在setId之后,还是update(user)的作用?
但值得注意的是,这种方式创建的离线状态对象,因为新建的对象没法获取数据库中的字段属性值,set值的时候需要对需要字段全部赋值,否则值为空。根据数据库默认值来对数值字段赋值为0,hibernate和数据库设置默认值哪个优先级会更高,未测试。
TestDetached02
public void TestDetached02(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(1);
user.setStatus("1");
session.update(user);
System.out.println("================= User UPDATE =================");
user.setId(2);
//session.update(user);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
报异常:identifier of an instance of com.ack.hibernate.User was altered from 1 to 2
User UPDATE处离线对象已转为持久化对象,为持久化对象set主键为另一个值报异常。
TestDetached03
public void TestDetached03(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(1);
session.delete(user);
//user变成了transient对象
System.out.println("================= Transaction DELETE =================");
user.setId(1);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
delete操作后,离线状态的user变为瞬时对象,后续的操作将不进行数据库操作,只发送一条delete的sql语句;
TestDetached04
public void TestDetached04(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(1);
session.delete(user);
//user变成了transient对象
System.out.println("================= Transaction DELETE =================");
user.setId(1);
session.update(user);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
报错:deleted instance passed to update(): [<null entity name>#<null>];
此处delete后离线状态变为瞬时态,不是set id的问题报错,set任何属性都一样会报错;
update用来更新detached对象,更新完成后转为Persistence对象;
update用来更新transient对象则会报错;
TestDetached05
public void TestDetached05(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(1);
session.update(user);
session.delete(user);
//user变成了transient对象
System.out.println("================= Transaction DELETE =================");
user.setId(1);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
先将瞬时态user对象update更新为持久化对象,再对持久化user对象delete;
发送了两条语句,一条update语句,一条delete语句;并且两条语句在Transaction DELETE打印内容后两条sql语句紧挨着执行的;
很意外,应该是update语句是要在事务提交时才执行,后期需要考虑hibernate对象三种状态与语句执行情况的关系;
离线状态的user对象已不再session中,set属性值自然也就没有意义,不会去检测;
TestDetached06
public void TestDetached06(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user = new User();
user.setId(1);
user.setUserName("chensan");
session.saveOrUpdate(user);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
id=1的user对象存在于数据库,则对象为离线状态saveOrUpdate同update执行update操作;发送一条update的sql语句;
id=1的user对象不存在数据库,则会执行save操作,报错: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; 这个异常是由于主键设置为自增长,而在我们插入记录的时候设置了ID的值导致的。
对于设置了自增长主键生成策略的,指定id就不适用了。猜想:应该改变主键生成策略,不让自增长,没有相应记录自然就能save了;
TestDetached07
public void TestDetached07(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user1 = session.load(User.class, 1);
System.out.println("userName = "+user1.getUserName());
user1.setUserName("chensan");
User user2 = new User();
user2.setId(1);
user2.setUserName("cs");
session.saveOrUpdate(user2);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
报错:A different object with the same identifier value was already associated with the session : [com.ack.hibernate.User#1];
只知道是两个对象最终为表示同一个对象,具体的错误先不讨论,继续看看下一个例子:
public void TestDetached08(){
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
User user1 = session.load(User.class, 1);
System.out.println("userName = "+user1.getUserName());
user1.setUserName("chensan");
User user2 = new User();
user2.setId(1);
user2.setUserName("cs");
user2.setStatus("1");
//session.saveOrUpdate(user2);
//merge方法会判断session中是否已经存在同一个对象,如果存在就将两个对象合并
session.merge(user2);
session.getTransaction().commit();
System.out.println("================= Transaction COMMIT =================");
}
merge()将两个对象合并为一个对象,最后发送一条update的sql语句;
报错:A different object with the same identifier value was already associated with the session : [com.ack.hibernate.User#1];参考:http://www.blogjava.net/hrcdg/articles/157724.html基本可以解决问题;