Hibernate个人笔记

精通Hibernate:Java对象持久化技术详解(第二版)  重点内容:
  第7章,读3遍

 编程精华资源汇总:http://blog.csdn.net/ccecwg/article/details/37602977


Hibernate个人笔记


1.Hibernate API? 
 应用程序可以直接通过Hibernate API访问数据库,Hibernate API接口分为以下几类:
 1.提供访问数据的操作(保存,更新,删除和查询对象)的接口 -- 包括:Session,Transaction,和Query接口
 2.用户配置Hibernate的接口  -- Configuration
 3.使应用程序拦截Hibernate内部发生的事件,并作出相关的回应 -- Interceptor,LoadEventListener接口
 4.用于扩展Hibernate的功能接口  -- UserType,IdentifierGenerator接口, 如果需要可以扩展这些接口
 
2.Hibernate的5个核心接口?
 1.Configuration接口:            配置Hibernate,并启动Hibernate,创建SessionFactory对象
 2.SessionFactory接口:         初始化Hibernate,充当数据存储源的代理,创建Session对象
 3.Session接口:                              负责保存、更新、删除、加载和查询对象
 4.Transaction:                          管理事务
 5.Query和Criteria接口:        执行数据库查询
 
  




                                                       第三章                   第一个Hibernate应用


Hibernate内部封装了通过JDBC访问数据库的操作,向上层应用提供面向对象的数据访问API。
使用Hibernate的步骤?
1.创建Hibernate配置文件                  -- classpath中,xml或properties文件,
                            Hibernate从其配置文件中读取和数据库连接有关的信息
2.常见持久化类                                             -- 简单的JavaBean
3.创建对象-关系映射文件  (xml或注解实现)  -- xml配置文件需要和编译后的 .class文件放到同一目录
4.通过Hibernate API编写访问数据库的代码


注意:关于xml映射配置文件? -- 结构
<hibernate-mapping>
<class name="package.Customer" table="customer">
<id name="id" column="ID" type="long">
<generator class="increment">
</id>
<property name="name" column="name" type="string" not-null="true">
</class>
</hibernate-mapping>


说明:1:必须先定义id元素,在定义property元素,否则抛异常 InvalidMappingException 
    2:name -- 持久类属性, type -- Hibernate映射类型
      column -- 表对应的字段   , not-null  -- 该字段是否可以为空,默认false,可为null
    3:id是由Hibernate赋值的,按照xml映射文件指定的生成策略
      


访问数据库? 
private static SessionFactory sessionFactory;
1.加载配置文件  -- 根据默认位置的Hibernate配置文件的配置信息,创建一个Configuration实例
              Configuration config = new Configuration();
2.加载类的对象-关系映射文件
              config.addClass(Customer.class)
3.创建SessionFactory对象  -- 重量级,耗内存,建议单例static形式
              sessionFactory = config.buildSessionFactory();
4.打开Session(创建)进行持久化操作
              Session session = sessionFactory.openSession();
              Transaction  tx = null;
              try{
              tx = session.beginTransaction();
              session.save(customer);
              tx.commit();
              }catch(RuntimeException e){
              if(tx!=null){
              tx.rollback();
              }
              throw e;
              }finally{
              session.close();
              }


总结:     通过Hibernate API访问数据库的一般流程:
首先在应用的启动阶段对Hibernate进行初始化,然后通过Hibernate的Session接口来访问和操作数据库


Hibernate的初始化  -- (上面)前三个步骤:
链式写法:
      sessionFactory =  new Configuration()
                         .addClass(Customer.class)
                         .buildSessionFactory();
                         
访问Hibernate的Session接口? (上面)第四个步骤
Session的常用方法:  save(),update(),delete(),load()和get()


注意:load和get?
get无非就是立刻查询到该对象,如果没有这条数据返回null;
load无非就是延迟加载,在真正使用这个对象的时候才去发送SQL,不能使用null来判断这个id是否在数据库中有对象。
load涉及到session关闭的问题(no session);get/load的对象都是持久化对象,直接修改属性会自动刷新。
所以,如果你确实是要查询这个对象并用这个对象的话,使用get,如果你只是需要这么一个对象,作为关系设置到另一个对象中,
建议直接new这个对象,然后直接设置id值,如果你需要一个对象,但是不确定是否会访问到这个对象的属性,才用load。




关于Session?


Session是一个轻量级的对象,通常将每一个Session实例和一个数据库事务绑定,
即:每执行一个数据库事务,都应该先创建一个新的Session实例。如果事务执行中
出现异常,应该撤销事务。不论事务执行成功与否,最后都应该调用Session的close()
方法,释放掉Session实例占用的内存资源




Hibernate用对象标识符(OID)来区分对象:
Java按内存地址识别同一个类的不同对象,而数据库按主键区分同一个表的不同记录,
Hibernate使用OID来统一两者之间的矛盾  主键  <-- OID --> 地址


Transactoin tx = session.beginTransaction();
Customer c1 = (Customer)session.get(Customer.class,new Logng(1));
Customer c2 = (Customer)session.get(Customer.class,new Logng(1));
Customer c3 = (Customer)session.get(Customer.class,new Logng(3));
System.out.println(c1==c2); //true
System.out.println(c1==c3); //false
tx.commit();


分析:
1.第一次加载OID为1的Customer对象,先从数据库的Customers表中查询ID为1的记录,
  再创建相应的Customer实例,把它保存到Session缓存中,最后把这个对象的引用赋给c1
2.第二次加载OID为1的Customer对象,直接把Session缓存中OID为1的Customer对象的引用赋值给c2,
    最后把这个对象的引用赋给c2, 因此  c1和c2 引用的是同一个Customer对象
3.当加载OID为3的Customer对象时,由于Session缓存中不存在OID为3的对象,Hibernate会从数据库的Customers表中查询ID为3的记录,
  再创建相应的Customer实例,把它保存到Session缓存中,最后把这个对象的引用赋给c3




Hibernate的主键标识符生成器?
<id>
<generator class="注解生成器">
</id>


increment -- 以递增方式为代理主键赋值, 不依赖底层数据库系统,适用于所有数据库系统
<generator class="increment">
注意:Hibernate运行在单个应用服务器上,increment标识符生成器能有效工作,
如果运行在多个应用服务器上(集群环境下),increment工作会失效  -- 不推荐


identity  -- 由底层数据库来负责生成标识符,它要求底层数据库把主键定义为自动增长类型
                                    如:MySQL中 -- 主键定义为 auto_increment
               SQL Server中 -- 主键定义为identity类型


               
sequence  -- 利用底层数据库提供的序列来生成标识符  -- 如Oracle的序列


native    -- 依据底层数据库对自动生成标识符的支持能力,选择使用identity,sequence或hilo标识符生成器
             native能自动判断底层数据库提供的生成标识符的机制


assigned  -- 由应用程序赋值






Hibernate中的各种映射关系?


关系型数据库中,只存在外键参照关系,而且总是由 many 方 参照 one方,这样才能消除数据冗余,
即数据库只支持多对一单向关联






多对一和一对多:


many-to-one


Order -- Customer


<many-to-one
name="customer"
column="customer_id"
class="mypack.Customer"
not-null="true"
lazy="false"
/>




级联保存和更新:cascade="save-update",默认值是"none"
cascade="save-update"  --  保存或更新当前对象时(即执行insert或update语句时),会级联保存或更新与它关联的对象


<many-to-one
name="customer"
column="customer_id"
class="mypack.Customer"
not-null="true"
lazy="false"
cascade="save-update"
/>


当Hibernate持久化一个临时对象时,默认情况下,不会自动持久化所关联的其他临时对象
如果希望Hibernate持久化Order对象时,自动持久化所关联的Customer对象,
可以设置级联保存和更新   cascade="save-update"


一对多双向关联关系:可以方便地从一个对象导航到另一个或一组与它关联的对象。 建立单向还是双向关联,有业务需求决定


one方的配置:
Customer   <-- 1 : n --> Order
{
private Set orders = new HashSet();
}


<set 
name="orders"
cascade="save-update">
<key column="customer_id"/>
<one-to-many class="mypack.Order"/>
</set>


说明:
   name     -- 设定待映射的持久化类的属性名,这里为Customer类的orders属性
   cascade  -- 设置为save-update,表示级联保存和更新
   key      -- 设定与所关联的持久化类对应的表的外键
   one-to-many  -- 设定所关联的持久化类
  
Hibernate根据以上映射配置获得以下信息:


<set>元素,表名Customer类的orders属性为java.util.Set集合类型
<one-to-many>子元素,表明orders集合中存放的是一组Order集合
<key>子元素,表明order表通过外键customer_id参照Customer表
cascade属性设置为save-update,表明当保存或更新Customer对象时,会级联保存或更新orders集合中的所有Order对象




关于set的其他常用重要属性:


1.inverse属性: 默认是false,表示关系的两端都来维护关系,这样在更新的时候会发两条SQL,两端都会更新和维护关系




结论:
   
         原则,让多的一方(many)维护和管理关系
   1.在一对多双向关联关系中,应在 one 放把set 元素的inverse属性设置为true,表示仅让many端维护关系,这样可以提高应用的性能
   2.在建立两个对象的双向关联时,应该同时修改关联两端的对象的相应属性。




2.cascade 级联属性: 默认是 "none"


cascade="save-update"  -- 保存或更新当前对象时(即执行insert或update语句时),会级联保存或更新与它关联的对象


 级联删除:   cascade="delete"  -- Hibernate删除Customer对象时,自动删除和Customer关联的Order对象
customer.hbm.xml中
<set 
cascade="delete"
inverse="true"
>






 
                 第八章     通过Hibernate操作对象
                 
                
Session接口是HIbernate向应用程序提供的操纵数据库的最主要接口,它提供了基本的保存、更新、删除和加载Java对象的方法,。
Session具有一个缓存,位于缓存中的对象称为 -- 持久化对象,它和数据库的相关记录对应,Session能在某个时间点,按照缓存中
对象的变化来执行相关的SQL语句,来同步更新数据库,这一过程称为  -- 清理缓存 (flush)

  

load和get区别?

在hibernate中我们知道如果要从数据库中得到一个对象,通常有两种方式,一种是通过session.get()方法,另一种就是通过session.load()方法,然后其实这两种方法在获得一个实体对象时是有区别的,在查询性能上两者是不同的。

一.load加载方式

当使用load方法来得到一个对象时,此时hibernate会使用延迟加载的机制来加载这个对象,即:当我们使用session.load()方法来加载一个对象时,此时并不会发出sql语句,当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的id值,只有当我们要使用这个对象,得到其它属性时,这个时候才会发出sql语句,从数据库中去查询我们的对象

       session = HibernateUtil.openSession();
            /*
             * 通过load的方式加载对象时,会使用延迟加载机制,此时并不会发出sql语句,只有当我们需要使用的时候才会从数据库中去查询
             */
            User user = (User)session.load(User.class, 2);

我们看到,如果我们仅仅是通过load来加载我们的User对象,此时从控制台我们会发现并不会从数据库中查询出该对象,即并不会发出sql语句,但如果我们要使用该对象时:

      session = HibernateUtil.openSession();
      User user = (User)session.load(User.class, 2);
      System.out.println(user);

此时我们看到控制台会发出了sql查询语句,会将该对象从数据库中查询出来:

Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.born as born0_0_ from user user0_ where user0_.id=?
User [id=2, username=aaa, password=111, born=2013-10-16 00:14:24.0]

这个时候我们可能会想,那么既然调用load方法时,并不会发出sql语句去从数据库中查出该对象,那么这个User对象到底是个什么对象呢?

其实这个User对象是我们的一个代理对象,这个代理对象仅仅保存了id这个属性:

复制代码
      session = HibernateUtil.openSession();
            /*
             * 通过load的方式加载对象时,会使用延迟加载机制,此时得到的User对象其实是一个
             * 代理对象,该代理对象里面仅仅只有id这个属性
             */
            User user = (User)session.load(User.class, 2);
            System.out.println(user.getId());

      console:  2
复制代码

我们看到,如果我们只打印出这个user对象的id值时,此时控制台会打印出该id值,但是同样不会发出sql语句去从数据库中去查询。这就印证了我们的这个user对象仅仅是一个保存了id的代理对象,但如果我需要打印出user对象的其他属性值时,这个时候会不会发出sql语句呢?答案是肯定的:

复制代码
            session = HibernateUtil.openSession();
            /*
             * 通过load的方式加载对象时,会使用延迟加载机制,此时得到的User对象其实是一个
             * 代理对象,该代理对象里面仅仅只有id这个属性
             */
            User user = (User)session.load(User.class, 2);
            System.out.println(user.getId());
            // 如果此时要得到user其他属性,则会从数据库中查询
            System.out.println(user.getUsername());            
复制代码

此时我们看控制台的输出:

2
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.born as born0_0_ from user user0_ where user0_.id=?
aaa

相信通过上述的几个例子,大家应该很好的了解了load的这种加载对象的方式了吧。

二、get加载方式

相对于load的延迟加载方式,get就直接的多,当我们使用session.get()方法来得到一个对象时,不管我们使不使用这个对象,此时都会发出sql语句去从数据库中查询出来:

       session = HibernateUtil.openSession();
            /*
             * 通过get方法来加载对象时,不管使不使用该对象,都会发出sql语句,从数据库中查询
             */
            User user = (User)session.get(User.class, 2);

此时我们通过get方式来得到user对象,但是我们并没有使用它,但是我们发现控制台会输出sql的查询语句:

Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.born as born0_0_ from user user0_ where user0_.id=?

因此我们可以看到,使用load的加载方式比get的加载方式性能要好一些,因为load加载时,得到的只是一个代理对象,当真正需要使用这个对象时再去从数据库中查询。

三、使用get和load时的一些小问题

当了解了load和get的加载机制以后,我们此时来看看这两种方式会出现的一些小问题:

①如果使用get方式来加载对象,当我们试图得到一个id不存在的对象时,此时会报NullPointException的异常

        session = HibernateUtil.openSession();
            /*
             * 当通过get方式试图得到一个id不存在的user对象时,此时会报NullPointException异常
             */
            User user = (User)session.get(User.class, 20);
            System.out.println(user.getUsername());

此时我们看控制台的输出信息,会报空指针的异常:

Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.born as born0_0_ from user user0_ where user0_.id=?
java.lang.NullPointerException  .........

这是因为通过get方式我们会去数据库中查询出该对象,但是这个id值不存在,所以此时user对象是null,所以就会报NullPointException的异常了。

②如果使用load方式来加载对象,当我们试图得到一个id不存在的对象时,此时会报ObjectNotFoundException异常

复制代码
      session = HibernateUtil.openSession();
            /*
             * 当通过get方式试图得到一个id不存在的user对象时,此时会报ObjectNotFoundException异常
             */
            User user = (User)session.load(User.class, 20);
            System.out.println(user.getId());
            System.out.println(user.getUsername());
复制代码

我们看看控制台的输出:

20
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.born as born0_0_ from user user0_ where user0_.id=?
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.xiaoluo.bean.User#20]......

为什么使用load的方式和get的方式来得到一个不存在的对象报的异常不同呢??其原因还是因为load的延迟加载机制,使用load时,此时的user对象是一个代理对象,仅仅保存了当前的这个id值,当我们试图得到该对象的username属性时,这个属性其实是不存在的,所以就会报出ObjectNotFoundException这个异常了。

org.hibernate.LazyInitializationException异常

接下来我们再来看一个例子:

复制代码
public class UserDAO
{
    public User loadUser(int id)
    {
        Session session = null;
        Transaction tx = null;
        User user =  null;
        try
        {
            session = HibernateUtil.openSession();
            tx = session.beginTransaction();
            user = (User)session.load(User.class, 1);
            tx.commit();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            tx.rollback();
        }
        finally
        {
            HibernateUtil.close(session);
        }
        return user;
    }
}
复制代码
复制代码
  @Test
    public void testLazy06()
    {
        UserDAO userDAO = new UserDAO();
        User user = userDAO.loadUser(2);
        System.out.println(user);
    }
复制代码

模拟了一个UserDAO这样的对象,然后我们在测试用例里面来通过load加载一个对象,此时我们发现控制台会报LazyInitializationException异常

org.hibernate.LazyInitializationException: could not initialize proxy - no Session  .............

这个异常是什么原因呢??还是因为load的延迟加载机制,当我们通过load()方法来加载一个对象时,此时并没有发出sql语句去从数据库中查询出该对象,当前这个对象仅仅是一个只有id的代理对象,我们还并没有使用该对象,但是此时我们的session已经关闭了,所以当我们在测试用例中使用该对象时就会报LazyInitializationException这个异常了。

所以以后我们只要看到控制台报LazyInitializationException这种异常,就知道是使用了load的方式延迟加载一个对象了,解决这个的方法有两种,一种是将load改成get的方式来得到该对象,另一种是在表示层来开启我们的session和关闭session。

 

至此,hibernate的两种加载方式get和load已经分析完毕!!!


get VS load

get和load方式是根据id取得一个记录
下边详细说一下get和load的不同,因为有些时候为了对比也会把find加进来。

 

1.从返回结果上对比:
load方式检索不到的话会抛出org.hibernate.ObjectNotFoundException异常
get方法检索不到的话会返回null

 

2.从检索执行机制上对比: get方法和find方法都是直接从数据库中检索 而load方法的执行则比较复杂首先查找session的persistent Context中是否有缓存,如果有则直接返回 如果没有则判断是否是lazy,如果不是直接访问数据库检索,查到记录返回,查不到抛出异常 如果是lazy则需要建立代理对象,对象的initialized属性为false,target属性为null 在访问获得的代理对象的属性时,检索数据库,如果找到记录则把该记录的对象复制到代理对象的target上,并将initialized=true,如果找不到就抛出异常。

 

3.根本区别说明
如果你使用load方法,hibernate认为该id对应的对象(数据库记录)在数据库中是一定存在的,所以它可以放心的使用,它可以放心的使用代理来 延迟加载该对象。在用到对象中的其他属性数据时才查询数据库,但是万一数据库中不存在该记录,那没办法,只能抛异常。所说的load方法抛异常是指在使用 该对象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时(注意:这就是由于“延迟加载”在作怪)。

由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理。所以如果你知道该id在数据库中一定有对应记录存在就可以使用load方法来实现延迟加载。

对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查数据库,数据库中没有就返回null。

对于load和get方法返回类型:虽然好多书中都这么说:“get()永远只返回实体类”,但实际上这是不正确的,get方法如果在 session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是 原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是 返回的还是代理对象,只不过已经加载了实体数据。

get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;反而load方法创建时首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库。

 

4.简单总结

总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。



load()和get()区别:


load   --   当使用load方法来得到一个对象时,Hibernate会使用延迟加载机制来加载这个对象:
即:当我们使用session.load()方法来加载一个对象时,此时并不会发出sql语句,
当前得到的这个对象其实是一个代理对象,这个代理对象只保存了实体对象的id值,只有当我们使用这个对象,
得到其它属性时,才会发出sql语句,从数据库中取查询我们需要使用的对象


get    --  Hibernate会确认一下该id对应的数据是否存在,
                                 首先在session缓存中查找,然后在二级缓存中查找,
                                 没有查到就发sql查询数据库,数据库中没有就返回null


load   --  Hibernate认为该id对应的对象(数据库记录)在数据库中是一定存在的,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常
           load方法创建时,首先查询session缓存,没有就创代理,实际使用数据时才查询二级缓存和数据库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值