Hibernate 查詢進階

本文包括:

1、Hibernate 的查詢方式

2、HQL (Hibernate Query Language) 查詢

3、HQL 的投影查詢

4、HQL 的聚合函數查詢

5、QBC (Query By Criteria ) 條件查詢

6、QBC 離線條件查詢

7、SQL查詢方式(了解)

8、HQL 多表查詢

9、延遲加載

10、Hibernate 的查詢策略

1、Hibernate 的查詢方式

  1. 根據唯一標識 OID 的檢索方式

    • session.get(XXX.class,OID)

    可利用斷點調試觀察,執行該行代碼時的控制臺輸出為:

    select * from xx where id = ?

  2. 對象的導航的方式

    • Customer.getLinkmans()

    執行該行代碼時,控制臺輸出:

    select * from cst_linkmans where cust_id = ?

  3. HQL 的檢索方式

    • Hibernate Query Language -- Hibernate 的查詢語言
  4. QBC 的檢索方式

    • Query By Criteria -- 條件查詢
  5. SQL 檢索方式(了解)

    • 本地的 SQL 檢索 -- 較麻煩,在 Hibernate 中不推薦使用

2、HQL (Hibernate Query Language) 查詢

  1. HQL 的介紹:

    • HQL(Hibernate Query Language) 是面向對象的查詢語言, 它和 SQL 查詢語言有些相似

    • 在 Hibernate 提供的各種檢索方式中, HQL 是使用最廣的一種檢索方式

  2. HQL 與 SQL 的關系:

    • HQL 查詢語句是面向對象的,Hibernate 負責解析 HQL 查詢語句, 然后根據對象-關系映射文件中的映射信息, 把 HQL 查詢語句翻譯成相應的 SQL 語句.

    • HQL 查詢語句中的主體是域模型中的類及類的屬性

    • SQL 查詢語句是與關系數據庫綁定在一起的。SQL查詢語句中的主體是數據庫表及表的字段

  3. HQL基本的查詢格式:

    • 支持方法鏈的編程,即直接調用 list() 方法

    • 簡單的代碼如下

        session.createQuery("from Customer").list();
      
  4. 使用別名的方式:

    • 可以使用別名的方式

        session.createQuery("from Customer c").list();
        session.createQuery("select c from Customer c").list();
      
  5. 排序查詢:

    • 排序查詢和SQL語句中的排序的語法是一樣的

      • 升序

          session.createQuery("from Customer order by cust_id").list();
        
      • 降序

          session.createQuery("from Customer order by cust_id desc").list();
        
  6. 分頁查詢(不用管 DBMS 是 Oracle 還是 MySQL,Hibernate 自動幫我們完成分頁):

    • Hibernate 框架提供了分頁的方法,咱們可以調用方法來完成分頁

    • 兩個方法如下

      • setFirstResult(a) -- 從哪條記錄開始,如果查詢是從第一條開啟,值是0

      • setMaxResults(b) -- 每頁查詢的記錄條數

    • 演示代碼如下

        List<LinkMan> list = session.createQuery("from LinkMan").setFirstResult(0).setMaxResults().list();
      

    MySQL 對于分頁的實現:

    limit ?,? (注意:參數的值 從 0 開始)

    第一個參數:該頁從第幾個記錄開始,有公式:(currentPage - 1) * pageSize

    第二個參數:每頁顯示多少個記錄,一般取個固定值,如 10

  7. 帶條件的查詢:

    • Query.setString(0,"男");

    • Query.setLong(0,2L);

    • Query.setParameter("?號的位置,默認從0開始","參數的值"); -- 這個方法不用像上面兩個方法一樣考慮參數的具體類型

    • 按名稱綁定參數的條件查詢(HQL語句中的 ? 號換成 :名稱 的方式)

        Query query = session.createQuery("from Customer where name = :aaa and age = :bbb");
        query.setString("aaa", "李健");
        query.setInteger("bbb", 38);
        List<Customer> list = query.list();
      

    對應 JDBC 的代碼:

    PreparedStatement.setString(1,"男");

    注意:JDBC 的查詢從1開始!

3、HQL 的投影查詢

  1. 投影查詢就是想查詢某一字段的值或者某幾個字段的值

  2. 投影查詢的案例

    • 如果查詢多個字段,返回的 list 中每個元素是對象數組(object[])

        List<Object[]> list = session.createQuery("select c.cust_name,c.cust_level from Customer c").list();
        for (Object[] objects : list) {
            System.out.println(Arrays.toString(objects));
        }
      
    • 如果查詢兩個字段,也可以把這兩個字段封裝到對象中

      • 先在持久化類中提供對應字段的構造方法

          public class Customer {
              private Long cust_id;
              private String cust_name;
              private Long cust_user_id;
              private Long cust_create_id;
              private String cust_source;
              private String cust_industry;
              private String cust_level;
              private String cust_linkman;
              private String cust_phone;
              private String cust_mobile;     
        
              // 持久化類中一定要有無參的構造方法,所以需要手動重載
              public Customer(){};
        
              public Customer(cust_name,cust_level){};
        
          }
        
      • 然后使用下面這種 HQL 語句的方式: select new Customer(c.cust_name,c.cust_level) from Customer c

          List<Customer> list = session.createQuery("select new Customer(c.cust_name,c.cust_level) from Customer c").list();
          for (Customer customer : list) {
              System.out.println(customer);
          }
        

4、HQL 的聚合函數查詢

  1. 聚合函數:count(),avg(),max(),min(),sum(),返回的是一個數值

  2. 獲取總的記錄數

     Session session = HibernateUtils.getCurrentSession();
     Transaction tr = session.beginTransaction();
     List<Number> list = session.createQuery("select count(c) from Customer c").list();
     Long count = list.get(0).longValue();
     System.out.println(count);
     tr.commit();
    

    Number 類是 Integer、Float、Double、Long 等等的父類,所以泛型寫 Number 最省事,得到后可以轉化為具體的子類,在上面的代碼中,調用了 Number.longValue(),于是轉為了 Long 類型。

  3. 獲取某一列數據的和

     Session session = HibernateUtils.getCurrentSession();
     Transaction tr = session.beginTransaction();
     List<Number> list = session.createQuery("select sum(c.cust_id) from Customer c").list();
     Long count = list.get(0).longValue();
     System.out.println(count);
     tr.commit();
    

5、QBC (Query By Criteria ) 條件查詢

  1. 簡單查詢,使用的是 Criteria 接口

     List<Customer> list = session.createCriteria(Customer.class).list();
     for (Customer customer : list) {
         System.out.println(customer);
     }
    
  2. 排序查詢

    • 需要使用 addOrder() 的方法來設置參數,參數使用

        org.hibernate.criterion.Order 對象的 asc() or desc() 方法
      

      如:

        criteria.addOrder(Order.desc("lkm_id"));
      
    • 具體代碼如下:

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        Criteria criteria = session.createCriteria(Linkman.class);
        // 設置排序
        criteria.addOrder(Order.desc("lkm_id"));
        List<Linkman> list = criteria.list();
        for (Linkman linkman : list) {
            System.out.println(linkman);
        }
        tr.commit();
      
  3. 分頁查詢

    • QBC 的分頁查詢也是使用兩個方法,與 HQL 一樣

      • setFirstResult();

      • setMaxResults();

    • 代碼如下;

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        Criteria criteria = session.createCriteria(Linkman.class);
        // 設置排序
        criteria.addOrder(Order.desc("lkm_id"));
        criteria.setFirstResult(0);
        criteria.setMaxResults(3);
        List<Linkman> list = criteria.list();
        for (Linkman linkman : list) {
            System.out.println(linkman);
        }
        tr.commit();
      
  4. 條件查詢(Criterion 是查詢條件的接口,Restrictions 類是 Hibernate 框架提供的工具類,使用該工具類來設置查詢條件)

    • 條件查詢使用 Criteria 接口的 add 方法,用來傳入條件。

    • 使用 Restrictions 的添加條件的方法,來添加條件,例如:

      • Restrictions.eq -- 相等

      • Restrictions.gt -- 大于號

      • Restrictions.ge -- 大于等于

      • Restrictions.lt -- 小于

      • Restrictions.le -- 小于等于

      • Restrictions.between -- 在之間,閉區間

      • Restrictions.like -- 模糊查詢

      • Restrictions.in -- 范圍

      • Restrictions.and -- 并且

      • Restrictions.or -- 或者

      • Restrictions.isNull -- 判斷某個字段是否為空
    • Restrictions.in 示例:

      首先看參數類型:

        Restrictions.in(String propertyName, Collection values)
      

      發現,應該在第二個參數傳入 Collection ,于是可以這樣演示:

        Criteria criteria = session.createCriteria(Linkman.class);
        
        List<Long> params = new ArrayList<Long>();
        params.add(1L);
        params.add(2L);
        params.add(7L);
        
        // 使用in 方法查詢
        criteria.add(Restrictions.in("lkm_id", params));
      

      SQL:

        select * from cst_linkman 
        where lkm_id in (1,2,7);
      
    • Restrictions.or 示例:

      首先看參數類型,發現有兩種重載方法:

        Restrictions.or(Criterion lhs, Criterion rhs)
        Restrictions.or(Criterion... predicates)
      

      很明顯,第一個是兩個條件,第二個是可變參數,所以條件數量不定,但是可以發現,參數類型都是 Criterion ,故都應該傳入 Restrictions.XX()

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        Criteria criteria = session.createCriteria(Linkman.class);
        // 設置排序
        criteria.addOrder(Order.desc("lkm_id"));
        // 設置查詢條件
        criteria.add(Restrictions.or(Restrictions.eq("lkm_gender", "男"), Restrictions.gt("lkm_id", 3L)));
        List<Linkman> list = criteria.list();
        for (Linkman linkman : list) {
            System.out.println(linkman);
        }
        tr.commit();
      

      SQL:

        select * from Linkman 
        where lkm_gender='男' or lkm_id=3 
        order by lkm_id desc;
      
  5. 聚合函數查詢(Projection 的聚合函數的接口,而 Projections 是 Hibernate 提供的工具類,使用該工具類設置聚合函數查詢)

    • 使用 QBC 的聚合函數查詢,需要使用 criteria.setProjection() 方法

      如:

        criteria.setProjection(Projections.rowCount());
      

      又如:

        criteria.setProjection(Projections.count("lkm_id"));
      
    • 具體的代碼如下:

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        Criteria criteria = session.createCriteria(Linkman.class);
        criteria.setProjection(Projections.rowCount());
        List<Number> list = criteria.list();
        Long count = list.get(0).longValue();
        System.out.println(count);
        tr.commit();
      
    • 注意,如果想先執行 select count(*) from xxx ,再執行 select * from xxx,不能直接在后面執行編寫 QBC 語句,應該先“恢復” Projection

        // 創建QBC查詢接口
        Criteria criteria = session.createCriteria(Linkman.class);
        // 設置聚合函數的方式  select count(lkm_id) from 表;  5
        criteria.setProjection(Projections.count("lkm_id"));
        List<Number> list = criteria.list();
        Long count = list.get(0).longValue();
        System.out.println(count);
        
        criteria.setProjection(null);
        
        // 繼續查詢所有的聯系人  select * from 表
        List<Linkman> mans = criteria.list();
      

6、QBC 離線條件查詢

  1. 離線條件查詢使用的是 DetachedCriteria 接口進行查詢,離線條件查詢對象在創建的時候不需要使用 Session 對象,在添加條件 時也不需要 Session 對象,只有在查詢的時候使用 Session 對象即可,所以叫做離線條件查詢。

    為什么要有離線條件查詢?

    一般情況下,在業務層開啟 Session 后,在持久層對數據進行操作,而在 web 層需要接收條件查詢的若干條件,所以在 web 層就設置條件會很方便,又因為 Criteria 需要由 Session 創建,所以無法在 web 層設置條件,于是離線條件查詢出現了。

  2. 創建離線條件查詢對象

     DetachedCriteria criteria = DetachedCriteria.forClass(Linkman.class);
    
  3. 具體的代碼如下,注意順序,這樣是可行的

     DetachedCriteria criteria = DetachedCriteria.forClass(Linkman.class);
     // 設置查詢條件
     criteria.add(Restrictions.eq("lkm_gender", "男"));
     
     Session session = HibernateUtils.getCurrentSession();
     Transaction tr = session.beginTransaction();
    
     // 查詢數據
     List<Linkman> list = criteria.getExecutableCriteria(session).list();
     for (Linkman linkman : list) {
         System.out.println(linkman);
     }
     tr.commit();
    

7、SQL查詢方式(了解)

  1. 基本語法

     Session session = HibernateUtils.getCurrentSession();
     Transaction tr = session.beginTransaction();
     
     SQLQuery sqlQuery = session.createSQLQuery("select * from cst_linkman where lkm_gender = ?");
     sqlQuery.setParameter(0,"男");
     sqlQuery.addEntity(Linkman.class);
     List<Linkman> list = sqlQuery.list();
     System.out.println(list);
     tr.commit();
    

8、HQL 多表查詢

  1. 多表查詢使用 HQL 語句進行查詢,HQL 語句和 SQL 語句的查詢語法比較類似

    • 內連接查詢

      • 顯示內連接

          select * from customers c inner join orders o on c.cid = o.cno;
        
      • 隱式內連接

          select * from customers c,orders o where c.cid = o.cno;
        
    • 外連接查詢

      • 左外連接

          select * from customers c left join orders o on c.cid = o.cno;
        
      • 右外連接

          select * from customers c right join orders o on c.cid = o.cno;
        
  2. HQL 多表查詢時的兩種方式

    • 迫切和非迫切:

      • 非迫切返回結果是 Object[]

      • 迫切連接返回的結果是對象,把客戶的信息封裝到客戶的對象中,把訂單的信息封裝到客戶的 Set 集合中

    • 非迫切內連接使用 inner join ,默認返回的是 Object 數組

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        List<Object[]> list = session.createQuery("from Customer c inner join c.linkmans").list();
        for (Object[] objects : list) {
            System.out.println(Arrays.toString(objects));
        }
        tr.commit();
      
    • 迫切內連接使用 inner join fetch ,返回的是實體對象

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        List<Customer> list = session.createQuery("from Customer c inner join fetch c.linkmans").list();
        Set<Customer> set = new HashSet<Customer>(list);
        for (Customer customer : set) {
            System.out.println(customer);
        }
        tr.commit();
      

      把 List 轉為 Set 集合,可以消除重復的數據

  3. 左外連接查詢

    • 非迫切左外連接:封裝成List<Object[]>

    • 迫切左外連接:

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        List<Customer> list = session.createQuery("from Customer c left join fetch c.linkmans").list();
        Set<Customer> set = new HashSet<Customer>(list);
        for (Customer customer : set) {
            System.out.println(customer);
        }
        tr.commit();
      

9、延遲加載

  1. 原理:先獲取到代理對象,當真正使用到該對象中的屬性的時候,才會發送 SQL 語句,是 Hibernate 框架提升性能的方式

  2. 類級別的延遲加載

    • Session 對象的 load 方法默認就是延遲加載,例如:

        Customer c = session.load(Customer.class, 1L);
      

      斷點調試,發現,執行該行代碼時,控制臺輸出為空,即沒有發送 SQL 語句,再執行下面這行代碼:

        System.out.println(c.getCust_name);
      

      此時發現,控制臺輸出對應的 SQL 語句。

      Session.get(Class,id) 沒有延遲加載

    • 使類級別的延遲加載失效(一般情況下使用默認的延遲加載)

      • 在映射配置文件的 <class> 標簽上配置 lazy="false"

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE hibernate-mapping PUBLIC 
              "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
              "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
          <hibernate-mapping>
              
              <class name="com.itheima.domain.Customer" table="cst_customer" lazy="false">
                  <id name="cust_id" column="cust_id">
                      <generator class="native"/>
                  </id>
                  
                  <property name="cust_name" column="cust_name"/>
                  <property name="cust_user_id" column="cust_user_id"/>
                  <property name="cust_create_id" column="cust_create_id"/>
                  <property name="cust_source" column="cust_source"/>
                  <property name="cust_industry" column="cust_industry"/>
                  <property name="cust_level" column="cust_level"/>
                  <property name="cust_linkman" column="cust_linkman"/>
                  <property name="cust_phone" column="cust_phone"/>
                  <property name="cust_mobile" column="cust_mobile"/>
                  
                  <!-- 配置一方 -->
                  <!--
                      set標簽name屬性:表示集合的名稱
                  -->
                  <set name="linkmans" inverse="true">
                      <!-- 需要出現子標簽 -->
                      <!-- 外鍵的字段 -->
                      <key column="lkm_cust_id"/>
                      <one-to-many class="com.itheima.domain.Linkman"/>
                  </set>
                  
              </class>
              
          </hibernate-mapping>    
        
      • Hibernate.initialize(Object proxy);

  3. 關聯級別的延遲加載(查詢某個客戶,當查看該客戶下的所有聯系人是是否是延遲加載)

    • 代碼:

        Session session = HibernateUtils.getCurrentSession();
        Transaction tr = session.beginTransaction();
        Customer c = session.get(Customer.class, 1L);
        System.out.println("=============");
        System.out.println(c.getLinkmans().size());
        tr.commit();
      

      最后在控制臺上發現,在執行第二個輸出語句時,控制臺才會輸出如下類似語句,所以關聯級別的延遲加載也是默認的

         select * from Linkmans where cust_id = ?
      

10、Hibernate 的查詢策略

  1. 查詢策略:使用 Hibernate 查詢一個對象的關聯對象時,應該如何查詢,這是 Hibernate 的一種優化手段!!!

  2. Hibernate 查詢策略要解決的問題:

    • 查詢的時機

        Customer c1 = (Customer) session.get(Customer.class, 1);
        System.out.println(c1.getLinkmans().size());
      

      lazy 屬性解決查詢的時機的問題,配置是否該持久化類是否采用延遲加載

    • 查詢的語句形式

        List<Customer> list = session.createQuery("from Customer").list();
        for(Customer c : list){
            System.out.println(c.getLinkmans());
        }
      

      fetch 屬性就可以解決查詢語句的形式的問題,具體見下文

<set> 標簽上配置策略

  1. <set> 標簽上使用 fetch 和 lazy 屬性

    • fetch 的取值 -- 控制 SQL 語句生成的格式

      • select -- 默認值,發送普通的查詢語句

      • join -- 連接查詢,發送的是一條迫切左外連接,配置了join,lazy 無論取哪種值都是同一個效果

      • subselect -- 子查詢,發送一條子查詢查詢其關聯對象(需要使用 list() 方法進行測試)

    • lazy 的取值 -- 查找關聯對象的時候是否采用延遲

      • true -- 默認值,延遲加載

      • false -- 不延遲

      • extra -- 極其懶惰

  2. <set> 標簽上的默認值是 fetch="select"lazy="true"

<many-to-one> 標簽上配置策略

  1. 在<many-to-one>標簽上使用fetch和lazy屬性

    • fetch 的取值 -- 控制SQL的格式

      • select -- 默認值,發送基本的查詢查詢

      • join -- 發送迫切左外連接查詢

    • lazy 的取值 -- 控制加載關聯對象是否采用延遲

      • false -- 不采用延遲加載.

      • proxy -- 默認值,代理,現在是否采用延遲加載,由另一端(即一對多的一方)的 <class> 上的 lazy 確定

        • 如果一方的 <class> 上的lazy="true",proxy 的值就是 true (延遲加載)
        • 如果一方的 <class> 上的 lazy="false",proxy的值就是 false (不采用延遲.)
  2. <many-to-one> 標簽上的默認值是 fetch="select"lazy="proxy"

總結:Hibernate 框架都采用了默認值,開發中基本上使用的都是默認值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容