Hibernate 存取及批量更新删除

            Hibernate 存取及批量更新删除

作者:冰莲如水

Hibernate是介于JAVA应用逻辑层与数据库层之间的一类开源的ORM中间件。ORM(即Object-Relation Mapping)从字面意义上讲就是对象-关系映射,ORM模式指的是在单个组件中负责所有实体域对象的持久化。其重点就在于把实体域对象通过一定规则上的映射机制,转化为数据库中所对应的记录,即持久化。为了更好的理解持久化,我们可以回想一下,以前所用到的通过JDBC API来对实体域对象实现的持久化。例如:

    Connetion con = null;

    PreparedStatement stmt = null;

     try{

        con = getConnection();

        con.setAutoCommit(false);

        stmt = con.prepareStatement(“insert into customers (ID, NAME, AGE) values (?,?,?)”);

        stmt.setLong(1, new Long(1));

        stmt.setString(2, new String(“ping”));

        stmt.setInt(3, new Integer(20));

        stmt.execute();

       }catch(SQLException sqlex){

           con.rollback();

         }catch(Exception e){

         …… }

        finally{

           try{

              stmt.close();

              con.close();

           }catch(Exception ex){

             …….

            }

          }

        }

Author: lian

努力奋斗中...

11 thoughts on “Hibernate 存取及批量更新删除”

  1.      在上面的JDBC持久化模式中,业务逻辑层的过程域对象中业务逻辑和数据访问代码是混杂在一起的,并未能完全实现面向对象的编程思路,且程序结构不清晰,当关系数据模型发生变更时,软件的维护难度会大幅增加。而且这种模式中的SQL语句如果存在语法错误,在编译时是不能检查出来的,增加了调试程序时的难度。

        Hibernate的出现,将在业务逻辑层与数据库层之间再次构建出一个持久化层。Hibernate作为ORM中间件,封装了数据访问细节,我们只要通过Hibernate提供的对象-关系映射服务 ,便可在程序中实现域对象到关系数据的保存,或从数据库提取业务数据并转化为实体对象形式。

        对于要被持久化的JAVA对象,它在内存中的生命周期是从new语句创建了这个对象时开始,当不再有任何引用变量引用它时,这个对象的生命中期就将结束,所占用的内存将被JVM的垃圾回收器(GC)回收。

  2.   对于Hibernate所要操作的持久化对象,在它的生命周期中,可处于三种状态:

    ? 临时状态(transient): 用new语句新建的对象,还没有被持久化,此时它不处于任何的Hibernate 的Session之中。处于临时状态的JAVA对象被称为临时对象。

    ? 持久化状态(persistent):这类对象已经被持久化,已处于(被加入)Session的缓存中。处于持久化状态的JAVA对象被称为持久华对象。

    ? 游离状态(detached):已经被持久化,但不再处于Session(被扔出或是Session自行关闭了)的缓存当中。处于游离状态的JAVA对象被称为游离对象。

    Session接口是Hibernate向应用程序提供的操作数据库的最主要的接口,它提供了基本的面向对象的保存,更新,删除和查询方法。它由SessionFactory工厂类创建。

  3. * Session 的save()方法:

        save()方法使一个临时对象转变为持久化对象。例如:

             Customer customer = new Customer();

             customer.setId( new Long(9));

             customer.setName(“Tom”);

             Session session = sessionFactory.openSession();

             Transaction tx = session.beginTransaction();

             Session.save(customer);

             tx.commit();

             session.close();

      Session 的save()方法所要注意的问题,1.在实际的对象持久化保存中,新建对象的ID应在此对象的类的映射文件中设置其生成方式,无需在程序中再对ID进行赋值。

    例如以上的Customer类的映射文件Customer.hbm.xml中对ID生成方式的设置:

          <id name=”id” column=”ID”>

             <generator class=”increment”/>

          </id>

    如果希望由应用程序来为新的对象指定ID,可以调用save()的另一个重载方法:

          save( customer, new Long(1));

       但此种方法在程序中不推荐使用。

    2.在应用程序中不应该把持久化对象或游离对象传给save()方法。

  4. * Session 的update()方法

         update()方法使一个游离对象转变为持久化对象。以下代码在session1中保存一个 Customer 对象,然后在session2中更新这个 Customer对象:

         Customer customer = new Customer();

         customer.setName(“Tom”);

         Session session1 = sessionFactory.openSession();

         Transaction tx1 = session1.beginTransaction();

         session1.save(customer);

         tx1.commit();

         session1.close(); // 此时Customer对象变为游离对象,因为session1已经关闭。

         

         Session session2 = sessionFactory.openSession();

         Transaction tx2 = session2.beginTransaction();

         costomer.setName(“Linda”); //在与session2关联之前修改Customer对象的属性,

                                //这不是必须的一步,你也可以不修改,或修改更多属性。

         session2.update(customer); //在session2中加载 customer游离对象,并使其再次转为

                               //持久化对象。

         customer.setName(“Jack”); //在与session2关联之后再次修改了Customer对象的属性,

         tx2.commit();

         session2.close();

  5. 在上面的例子中,customer先是被session1持久化后,session1关闭,customer变成

    游离对象,再次与session2关联后,customer又变成持久对象。其中我们不厌其烦的修改Customer对象的属性,为了要证明当Customer对象与新的session2关联以后,修改了其属性,并不需要再次执行session2.save(customer),Session在清理缓存时,会根据最近一次的更新组装成update语句并执行数据库更新。

    * Session的saveOrUpdate()方法

      saveOrUpdate()方法同时包含了save() 和update()方法的功能。如果传入的参数是临时对象,就调用save()方法;如果传入的是游离对象,就调用update()方法;如果传入的参数是持久化对象,就直接返回。以下的示例程序中,customer起初为游离对象, anotherCustomer起初为临时对象,session2的saveOrUpdate()方法分别将它们变为持久化对象:

         ……

    Session session2 = sessionFactory.openSession();

    Transaction tx2 = session2.beginTransaction();

    Customer anotherCustomer = new Customer(); // anotherCustomer为临时对象

    anotherCustomer.setName(“Tom”);

    session2.saveOrUpdate(customer); //使customer游离对象被session2关联

    session2.saveOrUpdate(anotherCustomer); //使anotherCustomer临时对象被session2关联

    tx2.commit();

    session2.close();

  6. * Session 的load()和 get()方法

        load()和 get()方法都能根据给定的OID从数据库中加载一个持久化对象,这两个方法的区别在于:当数据库中不存在与对象的OID相同的记录时,load()方法抛出net.sf.hibernate.ObjectNotFoundException异常,而get()方法返回null。示例如下:

        Session session1 = sessionFactory.openSession();

        Transaction tx1 = session1.beginTransaction();

        Customer a = (Customer)session1.load(Customer.class, new Long(1));

        Customer b= (Customer)session1.get(Customer.class. new Long(2));

      

        tx1.commit();

        session1.close();

        当执行了session1.load(Customer.class, new Long(1))这样的代码以后,就把OID为1的Customer对象载到session1的缓存里。我们可以在tx1.commit()执行之前对加载后的Customer对象a进行属性的修改。这些修改并不会马上被执行,而是在Session清理缓存时,会根据持久化对象的属性变化来同步更新数据库。

  7. *Session 的delete()方法

       delete()方法用于从数据库中删除与Java对象对应的记录。如果传入的参数是持久化对象,Session就计划执行一个delete语句。如果传入的参数是游离对象,先使游离对象被Session关联,使它变为持久化对象,然后计划执行一个delete语句。而真正的删除语句的执行是在Session清理缓存时。示例代码如下,首先加载了一个持久化对象,然后通过delete()方法将它删除。

         Session session1 = sessionFactory.openSession();

         Transaction tx1 = session1.beginTransaction();

         //加载一个持久化对象,因为Session 的get()和load()方法返回的永远是持久化对象。

         Customer customer = (Customer)session1.get(Customer.class,  new Long(1));

         session1.delete(customer);

    session1.close();

    如果customer是游离对象时,下面的代码显示将此游离对象删除:

    Session session2 = sessionFactory.openSession();

    Transaction tx2 = session2.beginTransaction();

    //在删除前,此游离对象customer先与session2关联,成为持久对象,然后将计划

    //执行对其删除的SQL语句

    session2.delete(customer);

    tx2.commit();

    session2.close();

  8. 关于Hibernate的批量检索,批量更新和批量删除,所执行的操作并不尽遵循相同的规律。这一点使目前的Hibernate2.0还显得有些不够规格化和强健。但以后的版本中,期望其可以突破这几个屏障。

       首先说明批量查询,我们已经了解到,Session 的load(), get() , find() 方法都可实现对业务数据的查询,其中load()会因所要查询的对象类的映射文件(比如Customer.hbm.xml文件)中设置的检索策略,而实现立即检索或延迟检索。get()方法和find()方法却不受这种控制,它们总是会执行立即检索。那么象load()这样的方法,当其被设置为使用延迟检索时,它会返回什么样的实例呢?当使用延迟检索时返回为这一对象的代理类实例,代理类实例也有一个标志性的OID, 但没有被实例化,其所有属性为默认初始值或为空。关于延迟检索和对象类的映射方式,这里不做多余解释。提到检索,load() 和get()方法都是用指定了OID的方式检索并返回一个与数据库中记录相对应的实例对象。如Customer customer = (Customer)session.get(Customer.class, new Long(1)) 将会返回一个Customer实例对象customer. 而 find()可以实现批量的检索,其返回类型为List的对象集合。例如:

         List customerList = session.find( “from Customer as  c”);

    注意,其中的”from Customer as c” 为HQL查询语句, Customer 并不是象普通的SQL语句中对表名的引用那样可以对大小写不敏感,这里的查询是基于对象类的,Customer为待查询的对象类,你要写正确你的类名,c是你为这个类在这里所取的别名。Hibernate是通过你对Customer这个对象类的映射文件Customer.hbm.xml中的设置得知此对象类与哪一个表对应。它可能正是对应了CUSTOMERS这个表。那么Hibernate又如何知道你所用的对象类与表的映射,到底是哪一个数据库中的表呢?这个对数据库名的映射是在Hibernate的配置文件hibernate.cfg.xml文件中给出的。相关的一些问题请查阅Hibernate的配置和映射章节。

  9.     以上的session.find( “from Customer as  c”)方法,Hibernate最终会执行这样的SQL语句执行数据库的查询(立即检索方式):

        select * from CUSTOMERS  ;

    或者你也可以按如下的方式进行查询:

        List customerList = session.find(“from Customer as c where c.age > 24”);

    最终Hibernate所执行的SQL语句为:

         select * from CUSTOMERS where AGE > 24 ;

    如果担心一次检出Customer类对象的量会过大,可以在Customer.hbm.xml文件中设置一次批量检索的检出量batch-size:

        <class name=”mypack.Customer” table=”CUSTOMERS” batch-size=”4”>

    对于一对一,一对多的关联也存在着集合(set. list ,bag, map)的延迟检索或立即检索,对于这种关联集合的检索,也存在着批量检索的设置,如在类的映射文件中的一对多的关联集合set中设置延迟检索时的批量检索尺寸:

       <class name=”mypack.customer” table=”CUSTOMERS”>

          ……..

          <set name=”topics” inverse=”true” lazy=”true” batch-size=”3”>

           …..

          </set>

       </class>

    对于这类检索本文不做过多解释。有兴趣的读者可参阅Hibernate的关联映射与检索类型。

    关于批量删除,可以采用Session 的delete()方法的重载形式,以HQL语句作为参数执行批量的删除:

        session.delete(“from Customer c where c.age > 24”) ;

      但是Hibernate最终并不会为我们构建这样的SQL并执行:

          select * from CUSTOMERS where AGE > 24 ;

      Session 的delete()方法首先通过以下select 语句把所有附合查询条件的Customer对象加载到内存中:select * from CUSTOMERS where AGE > 24 ;

      然后再逐一执行对这些对象的删除工作:

            delect from CUSTOMERS where ID = 1;

            delect from CUSTOMERS where ID = 2;

            delect from CUSTOMERS where ID = 3;

            …….

            delect from CUSTOMERS where ID = 10000;

  10.  这有可能造成对一万条数据的加载和逐一删除。如此将会严重消耗内存,影响系统性能。此时最佳的方案又回到了使用JDBC API执行批量的删除工作。在此我们暂不举其实例。

    下面探讨在Hibernate中的批量更新。Hibernate中Session的update()方法的各种重载形式,一次都只能更新一个对象,那么我们可不可以使用Session 的find()方法先检索出要更新的所有的对象,再用轮询的方法逐一对其进更新呢?比如:

         Session session = sessionFactory.openSession();

         Transaction tx = session.beginTransaction();

         Iterator customers = session.find(“from Customer c where c.age >24”) .iterator() ;

         while(customers.hasNext()){

            Customer customer = (Customer)customers.next();

            customer.setAge(customer.getAge() + 1);

            session.flush();

            session.evict(customer);

         }

        tx.commit();

        session.close();

     以上的程序只是实现了批量检索并更新对象的目的,依旧没有注意内存的占用及系统的性能问题。我们还是回到原始却又是最有效的方法,使用JDBC API来实现业务数据的批量更新。幸好Hibernate只是对JDBC进行了轻量的封装,我们在程序中依旧可以引用JDBC API:

        …….

        tx = session.beginTransaction();

        Connection con = session.connection(); //取得当前session使用的数据库连接

        PreparedStatement stmt = con.prepareStatement(“update CUSTOMERS set AGE=AGE + 1 ” + “where AGE >24”);

        stmt.executeUpdate();

        tx.commit();

  11. 如果你使用的底层的数据库(如Oracle)支持存储过程,也可以通过存储过程来执行批量更新。Oracle中的存储过程的设置参考如下:

           create or replace procedure batchUpdateCustomer(p_age in number) as

             begin

                update CUSTOMERS set AGE = AGE + 1 where AGE > p_age ;

             end;

       上面的存储过程中的参数p_age代表一个数值型的传入参数,表示客户的年龄。我想你用它甚至可以查出一个上千年的老妖 :)

        在我们的应用程序中可以对这个存储过程进行如下的调用:

             ……..

    tx = session.beginTransaction();

             Connection con = session.connection();

             String procedure = “ {call batchUpdateCustomer(?)}”;

             CallableStatement cstmt = con.prepareCall(procedure);

             cstmt.setInt(1, 24);

             cstmt.executeUpdate();

             tx.commit();

    与此类同,Hibernate的批量删除也可以采用程序中调用JDBC API的方式,进行操作。你可以把上面的存储过程改一下,试着去执行它。

    总结:Hibernate让我们在面对复杂多变的业务数据时,真正的体验到了面向对象编程的快意高效,但同时它自已也知道自身的不足之处在哪里,给我们留下了充分的空间去选择最适合的开发模式,而Hibernate为我们提供了对这些常规开发模式的支持。也许有一天,国人也能参与到这样经典的大型开源组件的设计当中去,做出我们引领时带的开源品牌,一同努力吧!这的确需要大部分人的努力和奉献。

Comments are closed.