Hibernate重点知识笔记-必看

级联属性和抓取策略

级联(cascade)属性
1、CascadeType.ALL(包括增、删、改、查,联动操作),其实查不算在内,查Fetch

2、CascadeType.MERGE(合并的join)--不重要

3、CascadeType.PERSIST(保存的时候在级联)

4、CascadeType.REFRESH(刷新说明:比如现在我查询出了数据,另外一个人在我查询数据之后,他对数据做了修改,这是才会级联上,hibernate会自动刷新我查询出来的数据)

5、CascadeType.REMOVE (只要在删除操作时才会级联)

6、我们一般都只设置CascadeType.ALL就OK了,

7、Cascade不是必须的,他的作用只是可以让我们快速的开发,我们也可以通过手动增、删、改、查

Fetch捉取策略
1、FetchType.EAGER(渴望的,希望马上得到)

a) 一对多关系,比如通过get()方法来get出一的一端,他之后会出一条SQL语句,不会自动去查询多的一端,如果设置FetchType.EAGER,会讲他的关联对象查询出来

b) 如果是load的话,他不会发出SQL语句,因为load支持延迟加载,只有真正获取数据时才会发SQL

2、FetchType.LAZY(懒加载)

a) 只有真正获取数据时才发出SQL语句

3、默认是:FetchType.LAZY(一对多)

4、默认是:FetchType.EAGER(多对一)

5、一般使用默认就可以了


1.关于mappedBy?



   1、mappedBy 含义    -- 拥有方能够自动维护跟被拥有方的关系
   2、mappedBy标签一定是定义在 the owned side(被拥有方),它指向 the owning side(拥有方)   -- 即  -- 在多的少(one)的一方配置,让多的一方去维护关系,注意 拥有方必须有 该属性
   3、只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,ManyToOne不存在该属性;


2、在oneToOne中

       注意:在判断到底是谁维护关联关系时,可以通过查看外键,哪个实体类定义了外键,哪个类就负责维护关联关系。  


3、小技巧:通过hibernate来进行插入操作的时候,不管是一对多、一对一还是多对多,都只需要记住一点,
                     在哪个实体类声明了外键,就由哪个类来维护关系,在保存数据时,总是先保存的是没有维护关联关系的那一方的数据,后保存维护了关联关系的那一方的数据,如:


  Person p = new Person();
        p.setName("xiaoluo");
        session.save(p);
        
        IDCard card = new IDCard();
        card.setNo("1111111111");
        card.setPerson(p);
        session.save(card);

4、Hibernate的两种加载方式:get和load

在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已经分析完毕!!!


5、Hibernate注解版一对多级联删除


这里用到的模型是列车与座位的一对多关系
一的一方:
@OneToMany(fetch = FetchType.LAZY, mappedBy = "train", cascade = CascadeType.ALL)

多的一方:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TRAIN_ID")

6、Hibernate注解配置一对多,并且按照列排序

@OneToMany(targetEntity=VoteItem.class,cascade=CascadeType.ALL)

@Fetch(FetchMode.JOIN)
@OrderBy("Index asc")
//updatable=false很关键,如果没有它,在级联删除的时候就会报错(反转的问题)
@JoinColumn(name="Vid",updatable=false)
private Set Items = new HashSet();

7、小总结


重点学习 Hibernate fetch lazy cascade inverse 关键字

 

Hibernate最让人头大的就是对集合的加载形式。
书看了N次了,还是没有真正理解Hibernate。所以下午专门做了下测试,对配置文件的意思加深了认识。

假设有两个表,Photos(一) --- picture(多)Photo包含picture集合

结论1: HQL代码 > fetch(配置) > lazy (配置)
结论2: 默认 lazy="true"
结论3: fetch 和 lazy 主要是用来级联查询的,   而 cascade 和 inverse 主要是用来级联插入和修改的

             fetch参数指定了关联对象抓取的方式是select查询还是join查询,select方式时先查询返回要查询的主体对象(列表),再根据关联外键id,每一个对象发一个select查询,获取关联的对象,形成n+1次查 询; 而join方式,主体对象和关联对象用一句外键关联的sql同时查询出来,不会形成多次查询。 
如果你的关联对象是延迟加载的,它当然不会去查询关联对象。 另外,在hql查询中配置文件中设置的join方式是不起作用的(而在所有其他查询方式如get、criteria或再关联获取等等都是有效的),会使用select方式,除非你在hql中指定join fetch某个关联对象。fetch策略用于定义 get/load一个对象时,如何获取非lazy的对象/集合。 这些参数在Query中无效。

在某种特殊的场合下,fetch在hql中还是起作用的。 
例如 
现有message(回帖)-->topic(主贴)-->forum(版块) 的多级many-to-one结构: 
第一级:message-->topic many-to-one配置lazy="false" fetch="join" 
第二级:topic-->forum many-to-one配置lazy="false" fetch="join" 
这时如果"from message",则第二级:topic-->forum中的fetch策略会起作用 

查询抓取(默认的)在N+1查询的情况下是极其脆弱的,因此我们可能会要求在映射文档中定义使用连接抓取: 

fetch="join"> 
 
 
 
在映射文档中定义的抓取策略将会有产生以下影响: 

通过get()或load()方法取得数据。 

只有在关联之间进行导航时,才会隐式的取得数据(延迟抓取)。 

条件查询 

在映射文档中显式的声明 连接抓取做为抓取策略并不会影响到随后的HQL查询。 

通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用HQL的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。 在条件查询 API中,应该调用 setFetchMode(FetchMode.JOIN)语句。 


其实这并不能说明hql能够按照配置文件设置的join进行抓取,这时 第二级:topic-->forum 的抓取其实已经和hql没有关系了,因为前面已经产生了另一个select方式的抓取语句。 
而是对象的关联获取,假如查询message时topic是设置为延迟加载的,那么在后面获取message.topic时,如topic.forum不延迟加载,那么topic-->forum会实现配置的join方式的抓取,这个显然和hql查询没有关系。

结论4: 如果你是用spring来帮你管理你的session, 并且是自动提交,延迟加载就等于没加载~_~(当然
                除非你手动重新打开session然后手动Hibernate.initialize(set);然后关闭session.
结论5:      cascade主要是简化了在代码中的级联更新和删除。
j结论6:老爸可以有多个孩子,一个孩子不能有多个老爸,而且老爸说的算, 孩子围着老爸转。
                所以Photos老爸要有权力所以 cascade 这个关键子都是送给老爸的, 也就是级联更新,
               老爸改姓了,儿子也得跟着改,呵呵。“不然,就没有零花钱咯”。
                而Picture儿子整体挨骂,但是还是要维护父子之间良好的关系,对老爸百依百顺,所
               以老爸就说,儿子,“关系,由你来维护(inverse="true") ,不然就不给零花钱。呵。”。
              
                   
                      
                   

                
            

               
测试代码:

    Photos p = ps.getById(1);
   Set set = p.getPictures();
   for(Picture pic : set){
    System.out.println(pic.getId());
   }

配置文件的一部分:
      
           
               
           

           
       


测试过程会对配置文件不断修改:并且从来不曾手动重新打开session


8、hibernate hql关联和连接操作


Hibernate 提供了强大的查询系统,使用Hibernate有多种查询方法可以选择:可以使用HibernateHQL查询,也可以使用条件查询,甚至可以使用原生的SQL查询语句。其中HQL查询时Hibernate配置的功能强大的查询语句。HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承、多态 和关联之类的概念。

       下面我们来看一下Hibernate中的关联查询的基本知识。

一、连接概念

外连接:把舍弃的元组也保存在结果关系中,而在其他属性上填写空值。

左外连接:返回那些存在于左表而右表中却没有的行,再加上内连接的行。

左连接特点:显示全部左边表中的所有项目,即使其中有些项中的数据未填写完全。

在使用的时候主要根据自己的需要选择时要保留join关键字右边的表中的属性还是要保留左边的表的属性

      内连接:内连接也叫连接,是最早的一种连接。还可以被称为普通连接或者自然连接,内连接是从结果表中删除与其他被连接表中没有匹配行的所有行,所以内连接可能会丢失信息。

当程序需要从多个数据表中获取数据时,Hibernate使用关联映射来处理底层数据表之间的连接,一旦我们提供了正确的关联映射后,当程序通过Hibernate进行持久化访问时,将可利用Hibernate的关联来进行连接。

         HQL支持两种关联join的形式:implicit(隐式) 与explicit(显式)

 

         显式form子句中明确给出了join关键字,而隐式使用英文点号(.)来连接关联实体。

         受支持的连接类型是从ANSI SQL中借鉴来的。

         inner join(内连接)

         left outer join(左外连接,outer 可以省略)

         right outer join(右外连接,outer 可以省略

         full join (全连接,并不常用)

         使用显式连接,可以通过with或者on关键字来提供额外的join条件

二、连接操作

下面来看我的表的结构:

user表

store表

Hibernate重点知识笔记-必看_第1张图片

goods表

Hibernate重点知识笔记-必看_第2张图片

order表

Hibernate重点知识笔记-必看_第3张图片

ordergoods表

表对应的model

[java]  view plain copy print ?
  1. public class User {  
  2.    private int userId;//用户ID  
  3.    private String userName;//用户名  
  4.    private String userPassword;//用户密码  
  5.    private Store store;  
  6.   private Set orderSet = new HashSet();  
  7. <//get和set方法省略  
  8. }  

[java]  view plain copy print ?
  1. public class Store {  
  2.     private int storeId;// 主键  
  3.     private User user;// user的id是Store的外码  
  4.     private String storeName;  
  5.     private String storeAdress;  
  6.     private String storePhone;  
  7.         private Set goodsSet=new HashSet();  
  8.        //get和set方法省略  
  9. }  

[java]  view plain copy print ?
  1. public class Orders {  
  2.     private int orderId;// 主键  
  3.     private User user;// User的主键作为Orders的外键。  
  4.     private String orderCount;  
  5.     private String totalPrice;  
  6.         private Set goodsSet = new HashSet();  
  7.   
  8. }  

[java]  view plain copy print ?
  1. public class Goods {  
  2.       
  3.     private int goodsId;// 主键  
  4.     private Store store;// ID外键,来自Store  
  5.     private String goodsName;  
  6.     private String goodsPrice;  
  7.     private Set orderSet = new HashSet();  
  8. }  

xml文件就省略了

1、user表和store表连接查询store表中的用户名为id的store表中的一行数据。使用的是内连接,当然也可使用其他连接方式。

[java]  view plain copy print ?
  1. //store.user 连接操作用的是Store对象中的user,只能查找出指定userId的数据,内连接只会保存两张表中  
  2. //有相同字段的数据。  
  3. String hql="select store from Store store inner join store.user user on user.userId=:id";  


2、左外连接 user表和store表连接查询store表中的用户名为id的store表中的一行数据。

[java]  view plain copy print ?
  1. //hql1会查询出Store中的全部数据,因为使用的是左外连接,所以该连接将Store表所有的记录都保留了。  
  2. String hql1="select store from Store store left join store.user user with user.userId=:id";  

3、右外连接user和store表

[java]  view plain copy print ?
  1. //但是如果循环打印store对象中的数据可能会抛出空引用的异常,因为查询出来的结果包含了user表中的所有数据,但是store的可能只有一条。  
  2. String hql2="select store from Store store right join store.user user with user.userId=:id";  

4、使用左外连接user和store表,查询某一个用户的订单,User类中有一个Orders的set集合对象,使用fetch将满足条件的订单读取到oederSet集合中,将该订单

对应的商品信息读取到goodSet中。

[java]  view plain copy print ?
  1. //hql3中的where user.userId=:id是将order表中的外码userId=id的选择出来连接。  
  2. //返回的User的对象中的属性,使用fetch连接抓取order对象中的的属性到SetOrder集合中  
  3. //同时也会去抓取order对象中关联的goodSet中的属性。  
  4. //把该订单中的商品信息保存在goodSet集合中。  
  5. String hql3="select distinct user from User user left join fetch user.orderSet where user.userId=:id";  

你可能感兴趣的:(Hibernate)