代码改变世界

Hibernate对象状态详解

2014-03-26 19:37  程序猿老张  阅读(2264)  评论(0编辑  收藏  举报

Hibernate是一个完整的ORM解决方法,它帮助开发者屏蔽了底层对于数据库访问的细节,并且提供了对象状态的概念。因此,对于性能要求不高的系统而言,如果使用Hibernate作为持久化的解决方案,开发者可以更多的去思考对象的状态,而不是底层SQL的执行。但是如果当开发者需要去提升系统性能的时候,我们就需要去更多的关注底层SQL的执行。

本文内容旨在讨论Hibernate对象状态(内容完全参考Hibernate4.3官方手册)

 Hibernate object states:

1.Transient :

当一个对象被刚刚new出来的时候并且和Hibernate中的session没有关联,此时它就是一个Transitent状态。Transient状态对象的特点是在数据库中没有记录并且没有oid,同时没有session与其关联。如果该对象不再有引用指向其时,那么该对象就会被GC所回收,从上面的图中可以清楚看到。

 2.Persistent:

一个Persistent状态的对象会在数据库中存在1条记录并且此时该对象就有oid,并且存在一个session与该对象关联。

3.Detached:

Detached状态的对象是在数据库中存在记录并且具有oid,但是不存在与之关联的session。

 

Transitent------>Persistent

我们可以使用session的save或者saveOrUpdate或者persist方法来将瞬时状态对象变成持久化状态对象。

 1     public void testSave01() {
 2         Session session = null;
 3         try {
 4             session = HibernateUtil.openSession();
 5             session.beginTransaction();
 6             
 7             DomesticCat fritz  = new DomesticCat();
 8             fritz.setColor("Red");
 9             fritz.setSex('M');
10             fritz.setName("Fritz");
11             
12             Long id = (Long) session.save(fritz);
13             
14             System.out.println("Generated ID is:"+id);
15             
16             session.getTransaction().commit();
17         } catch (Exception e) {
18             if (session != null) {
19                 session.getTransaction().rollback();
20             }
21         } finally {
22             if (session != null) {
23                 session.close();
24             }
25         }
26     }

save方法存储的特点:当save方法执行完毕之后会立即产生1条insert语句,并且会将对象的oid作为返回值返回。与此同时,即使在事务未开启或者事务已经提交的情况下执行save方法同样会产生insert语句。

persist方法存储的特点:它不会立即为对象分配oid,也不会立即产生insert语句。而是会在session.flush()的时候完成这项操作,通常当提交事务的时候,Hibernate会自动完成session.flush()方法的调用。除此之外,如果当事务未开启或者事务已经提交的情况下,执行persist方法,也不会产生任何的sql语句。

除了使用以上方法可以将对象的状态变成持久化之外,还可以使用load方法或者query方法得到一个persistent状态的对象。对于persistent状态的对象最大的特点就是当session.flush的时候(事务提交的时候),Hibernate会自动地去检测当前持久化对象与session中维护的对象的状态是否存在不同,如果不同,Hibernate会自动的完成更新操作,而不用开发者手动地去执行update语句。

    @Test
    public void persistent1(){
        Session session = null;
        try {
            session = HibernateUtil.openSession();
            session.beginTransaction();
            //此时对象的状态为持久化状态
            DomesticCat domesticCat =  (DomesticCat) session.load(DomesticCat.class, new Long(1));
            domesticCat.setName("WangWang");
            //此时不会发送任何的sql
            session.update(domesticCat);
            //因为此时该对象已经是持久化状态的对象,所以也不会发送任何的sql语句
            session.save(domesticCat);
            //因为持久化对象的状态已经发生了改变,所以会生成update语句
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if(session != null){
                session.getTransaction().rollback();
            }
        }finally{
            if(session != null){
                session.close();
            }
        }
    }

使用load方法的注意事项:

load方法的注意事项:load方法查询一个不存在的对象时,首先会使用 GGLIB 去返回一个代理对象,此时Hibernate不会与数据库交互,当调用真实对象的具体方法时,Hibernate才会发送sql到数据库,如果此时没有数据,Hibernate则会抛出ObjectNotFoundException

使用load获取到代理对象之后,如果在sesion关闭之后才去调用对象的方法,那么由于返回的代理对象还未初始化,此时会引发LazyInitationlException。(后面会在Hibernate的延迟加载分析中结合源码来具体分析其实现)

如果使用get方法进行查询,那么Hibernate会立即发送select语句完成查询,并且不会在返回代理对象,而是返回真实对象,如果查询不到,则返回一个null。

 

除了使用load方法可以将detached状态转变成persistent状态,当我们需要查询多个对象的时候,通常会采用Hiberante为我们Query对象来完成列表的查询。然后采用HQL替代传统  的SQL语句。使用Query所查询  到的对象同样也是持久化对象。

    /**
     * 简单查询,查询列表
     */
    @SuppressWarnings("unchecked")
    @Test
    public void query1() {
        Session session = null;
        try {
            session = HibernateUtil.openSession();
            session.beginTransaction();
            // HQL是基于对象的查询方式,此时查询到所有对象都是持久化状态
            List<DomesticCat> cats = (List<DomesticCat>) session
                    .createQuery("from DomesticCat cat where cat.id > ?")
                    .setParameter(0, new Long(2)).list();
            for (DomesticCat cat : cats) {
                cat.setSex('M');
            }
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }

以上对N个对象修改了状态之后,当事务提交的时候,就会产生N条update语句。

 

Detached ---->Persistent

还可以将detached状态的对象修改成persitent状态:这里我们借助文档中的例子给出一个典型的场景应用

the application loads an object in the first session(应用第一次使用一个session加载完成对象,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(此时会使用一个新的session完调用update方法完成对象状态的改变,同时会立即发送1条sql语句到数据库)

使用update方法注意事项:

1.更新一个瞬时状态的对象

@Test
    public void update1(){
        Session session = null;
        try {
            session = HibernateUtil.openSession();
            session.beginTransaction();
            DomesticCat cat = new DomesticCat();
            cat.setName("haha");
            cat.setSex('M');
            cat.setColor("Black");
            //直接update一个瞬时对象,Hibernate会抛出异常
            session.update(cat);
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if(session != null){
                session.getTransaction().rollback();
            }
        }finally{
            if(session != null){
                session.close();
            }
        }
    }

此时会产生org.hibernate.TransientObjectException: The given object has a null identifier: org.plx.hibernate.domain.DomesticCat

由此可见:Hibernateupdate的时候会去检测当前对象的oid是否存在,如果不存在,Hibernate就认为这个对象是瞬时状态的对象,所以会对外抛出异常。

2.更新一个数据库中没有的对象,但是该对象具有oid

@Test
    public void update2(){
        Session session = null;
        try {
            session = HibernateUtil.openSession();
            session.beginTransaction();
            DomesticCat cat = new DomesticCat();
            cat.setId(new Long(100));
            cat.setName("haha");
            cat.setSex('M');
            cat.setColor("Black");
            /*
             * 此时数据库里其实并没有id为100的记录,但是会发送sql语句
             * Hibernate会根据返回的结果去做出判断,如果返回的结果是0
             * 即表示不存在这条记录,Hiberante同样会抛出异常
             */
            session.update(cat);
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if(session != null){
                session.getTransaction().rollback();
            }
        }finally{
            if(session != null){
                session.close();
            }
        }
    }

org.hibernate.StaleStateException:Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1   

底层肯定是通过使用Jdbc中的executeUpdate()方法来得到影响数据库中的记录数,此时为0,Hibernate也会给我们抛出异常。

3.在session中存在两个相同标识的对象

    @Test
    public void update3(){
        Session session = null;
        try {
            session = HibernateUtil.openSession();
            session.beginTransaction();
            DomesticCat cat = new DomesticCat();
            cat.setName("haha");
            cat.setSex('M');
            cat.setColor("Black");
            //此时会产生一条sql语句
            session.saveOrUpdate(cat);
            Long id = cat.getId();
            //创建一个与session中具有相同id的对象
            DomesticCat cat2 = new DomesticCat();
            cat2.setId(id);
            /*
             * 执行时session会去检测该对象的id,
             * 当发现该对象的id与session中的对象的id值相同,
             * 此时Hiberante也会抛出异常
             */
            session.update(cat2);
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if(session != null){
                session.getTransaction().rollback();
            }
        }finally{
            if(session != null){
                session.close();
            }
        }
    }
    

但是如果Session中已经一个持久化对象,如果此时再次创建一个新的对象,调用update方法的时候就会抛出异常,Hibernate不允许在session中存在两个具有相同id的同类型的对象。

因此对于saveOrUpdate判断对象状态的本质就是看对象的oid是否存在。如果存在,就调用update方法,如果不存在就调用save方法。

Hibernate中还为我们提供了一个merge方法:

    @Test
    public void merge1(){
        Session session = null;
        try {
            session = HibernateUtil.openSession();
            session.beginTransaction();
            DomesticCat cat = new DomesticCat();
            cat.setName("haha");
            cat.setSex('M');
            cat.setColor("Black");
            //此时会产生一条sql语句
            session.save(cat);
            Long id = cat.getId();
            //创建一个与session中具有相同id的对象
            DomesticCat cat2 = new DomesticCat();
            cat2.setId(id);
            cat2.setName("mimi");
            /*
             * 此时会将cat2的状态拷贝到cat对象中
             */
            session.merge(cat2);
            //此时cat的name已经变成了mimi,不再是haha
            System.out.println(cat.getName());
            session.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if(session != null){
                session.getTransaction().rollback();
            }
        }finally{
            if(session != null){
                session.close();
            }
        }
    }

session调用merge方法首先会去判断session中是否存在与给定对象相同id的对象,如果存在,就将给定对象的状态拷贝至与给定对象相同id的持久化对象,但是给定对象此时依旧是游离状态,它的状态不会发生任何的改变。如果session中不存在与给定对象就有相同id值的对象,那么Hibernate会去数据库中加载或者重新创建出一个新的对象。

Persistent ---->Detached

当session被关闭或者session的一级缓存被清空时(即调用clear()方法),那么对象状态就会从persist转变成detached。注意的是:session.flush()方法只是将持久化对象的状态与数据库中的数据同步而已,因此会发送sql语句,但是session中依旧会保存中数据,不会被清空,因此不会去改变持久化对象的状态。

Persistent ---->Transitent

当调用session.delete方法之后,一个持久化对象就会变成瞬时对象,此时就会产生1条delete的sql语句,并且对象也不会被session所管理。