程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 在Hibernate中直接操作JDBC接口

在Hibernate中直接操作JDBC接口

編輯:關於JAVA

簡介: Hibernate 在處理多表關聯及分組排序等復雜數據庫查詢操作時,其固有的 O-R 映射機制會 產生大量冗余 SQL 操作,系統性能比傳統的 JDBC 低很多。本文分析了 Hibernate 產生此類問題的原因 ,提出了一個在 Hibernate 框架內直接操作 JDBC 的接口的解決方案,在實際項目中驗證了該解決方案 可以有效提高此類查詢的效率。文中提供的示例代碼可以直接運用於使用 Hibernate 框架的 J2EE 系統 項目。

在 Hibernate 框架中提供直接操作 JDBC 接口的原因

Hibernate 框架在處理復雜查詢方面的 問題

Hibernate 是一個開放源代碼的對象關系映射框架,它對 JDBC 進行了非常輕量級的對象封 裝,使得 Java 程序員可以隨心所欲的使用面向對象編程思維來操縱數據庫。Hibernate 的優勢在於屏蔽 了數據庫細節,對於新增修改刪除的數據層操作,不再需要跟具體的 SQL 語句打交道,簡單的對對象實 例進行增刪改操作即可。

但是,對於多表關聯、分組統計、排序等復雜的查詢功能時,由於 Hibernate 自身的 O-R 映射機制 ,父子表之間關聯取數據會產生大量冗余的查詢操作,性能低下。此類情況下,直接使用 JDBC 的 SQL 語句反而更加靈活和高效。

Hibernate 框架處理復雜查詢問題實例分析

考慮如下數據庫實 體示例,表 A 為主表,表 B 和表 C 為子表,A 與 B、A 與 C 表均為 1 對多關系,在 B 表和 C 表中 以 A_ID 外鍵字段關聯 A 表父記錄。

圖 1. 數據庫實體示例圖

在 Hibernate 框架中,通常采用以下配置方式完成 A 表與 B,C 表父子實體之間的級聯查詢操作, Hibernate 實體配置 xml 如下:

清單 1. hibernate 實體配置 xml

A.hbm.xml:
<hibernate-mapping>
  <class name="XXX.XXX.A" table="A" >
     <id name="id" type="long">
      <column name="ID"/>
       <generator class="assigned">
      </generator>
     </id>
    <set name="children_B" cascade="delete" inverse="true"  lazy="false">
      <key column="A_ID"></key>
       <one-to-many class="XXX.XXX.B"/>
    </set>
    <set  name="children_C" cascade="delete" inverse="true" lazy="false">
       <key column="A_ID"></key>
      <one-to-many  class="XXX.XXX.C"/>
    </set>
  </class>
</hibernate-mapping>

B.hbm.xml:
<hibernate-mapping>
   <class name="XXX.XXX.B" table="B" >
    <id name="id"  type="long">
      <column name="ID"/>
      <generator  class="assigned">
      </generator>
    </id>
     <property name="a_id" type="long">
      <column  name="A_ID">
      </column>
    </property>
   </class>
</hibernate-mapping>

C.hbm.xml
<hibernate- mapping>
  <class name="XXX.XXX.C" table="C" >
    <id  name="id" type="long">
      <column name="ID"/>
       <generator class="assigned">
      </generator>
     </id>
    <property name="a_id" type="long">
       <column name="A_ID">
      </column>
     </property>
  </class>
</hibernate-mapping>

對應 的 Hibernate 領域實體類代碼示例如下:

清單 2. hibernate 實體類示例

A.java:
public class A implements java.io.Serializable,Comparable  {
  private long id;
  private Set children_b = new HashSet<B> ();
  private Set children_c = new HashSet<C>();

  public A (long id) {
    this.id = id;
  }

  public long getId()  {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public Set getChildern_b() {
     return children_b;
  }

  public void setChildren_b (Set  children_b) {
    this.children_b = children_b;
  }

  public  Set getChildern_c() {
    return children_c;
  }

  public  void setChildren_c (Set children_c) {
    this.children_c = children_c;
  }

  public int compareTo(Object other) {
    A otherSubject  = (A)other;
    long curAmount=this.getChildren_b().size()+this.getChildren_c ().size();
    long otherAmount =otherSubject.getChildren_b().size()
   +  otherSubject.getChildren_c().size();
    if(curAmount<otherAmount)
     {
      return -1;
    }
    else if (curAmount>otherAmount)
    {
      return 1;
    }
     else
    {
      return 0;
    }
  }
}

B.java:
public class B implements java.io.Serializable,Comparable {
  private long id;
  private long a_id;

  public long getId()  {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public long getA_id() {
     return a_id;
  }

  public void setA_id(long a_id) {
     this.a_id = a_id;
  }

  public B(long id) {
     this.id=id;
  }
}

C.java:
public class C implements  java.io.Serializable,Comparable {
  private long id;
  private long  a_id;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

   public long getA_id() {
    return a_id;
  }

  public  void setA_id(long a_id) {
    this.a_id = a_id;
  }

   public C(long id) {
    this.id=id;
  }
}

假設現在要統 計 A 表中從屬的 B 表和 C 表記錄之和最高的 top10 的 A 表記錄,在 Hibernate 框架下,由於取 A 表對應的數據庫記錄時,已關聯取出了對應的 B、C 表子記錄存放於 A 實體類的 children_a, children_c 的屬性中,因此 top10 的功能可以通過比較每個 A 表實體類中 children_a、children_c 的 Set 的 size 大小並進行排序得到,其代碼示例如下:

清單 3. 排序代碼示例

private ArrayList<A> sortAByAmount(ArrayList<A> all)
{
  for(int i=0;i<all.size();i++)
  {
    for(int j=0;j<all.size ()-i-1;++j)
    {
      if(all.get(j).compareTo(all.get(j+1))<=0)
      {
        A temp = all.get(j);
        all.set (j,all.get(j+1));
        all.set(j+1,temp);
      }
    }
  }
  return all;
}

表面看來很方便,但是由於 Hibernate 是 面向對象的 O-R 映射機制,每一條 A 表記錄的查詢實際都關聯有兩條 B、C 表查詢的 SQL 產生,我們 可以看到 Hibernate 的 SQL 日志中:

清單 4. Hibernate sql 日志示例

Hibernate: select a0_.ID as ID2_ from A a0_ where a0_.ID='1' 
Hibernate: select b0_.ID as ID2_,b0_.A_ID as A_ID2_ from B b0_ where b0_.ID=? 
Hibernate: select c0_.ID as ID2_,c0_.A_ID as A_ID2_ from C c0_ where  c0_.ID=?

由上述 Sql 日志可以看出,每一條 A 表記錄的取出,都伴隨以 A 表 ID 為查 詢條件關聯 B,C 表中 A_ID 外鍵字段的 2 條取子記錄的 sql,這是由 A.hbm.xml 配置中 “lazy=false”決定的。

這種情況下,當 A 和 B、C 表中數據量越來越大時,A 表取 實體的操作開銷將隨著 sql 查詢的增多而增大,並且在緊接著的排序過程中,即使采用業界最快的快速 排序算法,排序時間依然是隨原始排序實體數量的線性關系(O(n lg n)),效率會線性下降,最終無法 滿足客戶的前台查詢的效率要求。

此類情況下如直接采用 JDBC,則只需一條如下的 SQL 語句, 即可完成該功能:

清單 5. 直接 JDBC 操作 sql

select 
tab1.ID,
tab1.sumCol+tab2.sumCol
from 
(
select a.ID,
count(b.ID) sumCol
from A a left join B b on a.ID=b.ID
GROUP BY
a.ID
)tab1,
(
select a.ID,
count(c.ID) sumCol
from A a left join C c on a.ID=c.ID
GROUP BY
a.ID
)tab2 
where tab1.ID=tab2.ID
order by  tab1.sumCol+tab2.sumCol desc

在以上 JDBC 方式下,即使 A、B、C 表的數據量持續增 長,仍然只有 1 條 SQL 的開銷,不會出現 SQL 遞增的情況,因此耗時是在可控制的區間內的。並且讀 者可以注意到上述 SQL 將 3 表關聯拆分成了 2 個子查詢,這樣避免了 3 表做笛卡爾積的數量和,進一 步提高了查詢效率。由此可見,直接操作 JDBC,除 SQL 的開銷可控外,還可以利用數據庫層各種機制, 如上述查詢語句中的 left join、子查詢、索引…,靈活的調整 SQL 語句,以達到最佳的查詢性 能。

由上可實例可看出,在多表關聯、排序等復雜的查詢情況下,Hibernate 框架由於其自身對 象封裝的特殊性,不能像 JDBC 直接操作 SQL 那樣很好的解決查詢中高效性和靈活性方面的需求,且由 於其屏蔽了數據庫的底層,開發人員看到的只是 Hibernate 提供的數據層 API,無法與靈活的使用 SQL 語句等數據庫底層細節。因此,有必要在 Hibernate 框架中提供直接操作 JDBC 的接口。

在 Hibernate 框架中提供操作 JDBC 的接口的解決方案

Hibernate 的 session 機制

我們知 道 Hibernate 框架本身也是建立在 JDBC 之上的數據持久層實現,因此,要在框架本身提供操作 JDBC 的接口,需要切入其對 JDBC 封裝的細節。

通過研究和查閱 Hibernate 的框架源代碼及參考文檔 ,我們發現,Hibernate 的 Session 會話是進行持久化的基礎,所有的持久化操作都是在 Session 的基 礎上進行的,在實現上它是和 JDBC 中的 connection 數據庫連接綁定的,也就是說,Hibernate 的會話 域基於一個實際的 connection 類實例,二者之間的關系如下圖所示:

圖 2. Hibernate Session 機制示意圖

由上可以看到, Hibernate 中的 session 是單線程的,代表了一次會話的過程。實際上是把一個 JDBC Connection 打包 了,每一個 Session 實例和一個數據庫事務綁定。其生命周期是與與之關聯的 connection 實例的生命 周期一致的。

具體解決方案

由上面的 Hibernate 的 Session 機制我們意識到,只要能獲 取到 Hibernate 當前會話中的 Connection,則獲得了 JDBC 的底層數據庫連接實例,剩下就都是 JDBC 的范疇了。再查閱 Hibernate 的 API,發現 HibernateTemplate 類中 SessionFactory 成員的 getCurrentSession() 方法即可獲得 Hibernate 環境下的當前活動的 Session 會話,而 Hibernate 中 Session 實例的 connection() 方法即可獲得該會話中綁定的 Connection 數據庫連接實例。

問 題迎刃而解了,既然可以操作 Connection 實例,那與之關聯的 Statement、ResultSet 等基本 JDBC 類 均在我們控制范圍中了,我們采用接口模式設計一個輕量級解決方案,使其在保持原 Hibernate 的增刪 改操作方式前提下靈活提供操作 JDBC 的接口。設計類圖如下圖所示:

圖 3. 解決方案設計類示 意圖

設計中, AbstractHibernateDao 類作為 DAO 操作的基本類,保留原有 Hibenrate 框架下的新增,修改,刪除等 API。BaseHibernateDao 類繼承 AbstractHibernateDao 類,在此類中增加了直接操作 JDBC 的接口。設 計 getConnection 方法獲取 JDBC 的數據庫連接實例,設計 getObjectsBySql 方法作為對外的主要接口 ,該方法調用 fetchObjects 方法,這是具體的數據庫記錄到領域對象的轉換操作,需要使用者 override 該方法以完成自有領域對象的填充細節。

實際實現的類代碼如下所示:

清單 6. 解決方案實現代碼

AbstractHibernateDao.java:
abstract public class  AbstractHibernateDao extends HibernateDaoSupport {

  protected Log logger  = LogFactory.getLog(getClass());
  protected Class entityClass;

   protected Class getEntityClass() {
    return entityClass;
  }

  public List getAll() {
    return getHibernateTemplate().loadAll (getEntityClass());
  }

  public void save(Object o) {
     getHibernateTemplate().saveOrUpdate(o);
  }

  public void removeById (Serializable id) {
    remove(get(id));
  }

  public void  remove(Object o) {
    getHibernateTemplate().delete(o);
  }

}

BaseHibernateDao.java:
abstract public class BaseHibernateDao extends  AbstractHibernateDao{
  public Connection getConnection()
  {
     try
    {

      Session curSeesion =null;
       Connection con =null;
      curSeesion = super.getHibernateTemplate ().getSessionFactory()
     .getCurrentSession();
      con =  curSeesion.connection();
      return con;
    }
    catch (Exception es)
    {
      System.out.println(es.getMessage());
       return null;
    }

  }

  public  ArrayList<Object> fetchObjects(ResultSet rs)
  {
     ArrayList<Object> ret = new ArrayList<Object>();
    //example:
    //while(rs.next())
    //{
    //Object object = new Object ();
    //rs.getString(1);
    //rs.getString(2);
    //ret.add (object);
    //}
    return ret;
  }

  public  ArrayList<Object> getObjectsBySql(String pureSql)
  {
    Connection  con = curSeesion.connection();
    ps  =  con.prepareStatement (sqlbuf.toString());
    rs = ps.executeQuery();
    try
     {
      return this.fetchObjects(rs);

    }
    catch (Exception es)
    {
      System.out.println(es.getMessage());
       return null;
    }
    finally
    {
       try
      {
        ps.close();
        rs.close();
        con.close();
      }
      catch (SQLException e)  {
        // TODO Auto-generated catch block
         e.printStackTrace();
      }

    }
  }
}

使用該解決方案時,只需要將代碼包解壓至項目源代碼目錄,在想要直接操作 JDBC 接口的 DAO 模塊繼 承 BaseHibernateDao 類,然後重寫 fetchObjects 方法填入從自身數據庫表字段填充到領域對象的操作 , 即可輕松調用 getObjectsBySql 傳入原生 SQL 語句,返回具體的領域對象實體集合,當然使用者也可 以通過 getConnection 獲得 JDBC 的 Connection 實例來進行自己需要的特定的 JDBC 底層操作。

仍然以上文中的 A、B、C 表為例,采用該解決方案完成 top10 取數的代碼示例如下:

清 單 7. 使用解決方案示例

public class testDAO extends BaseHibernateDao{

  private String sqlQuery = " select tab1.ID,tab1.sumCol+tab2.sumCol"+
    " from(select a.ID, count(b.ID) sumCol"+
    "   from A a left  join B b on a.ID=b.ID"+
    "   GROUP BY a.ID)tab1, "+
    "    (select a.ID,count(c.ID) sumCol"+
    "   from A a left join C c on  a.ID=c.ID"+
    "   GROUP BY a.ID)tab2"+
    " where  tab1.ID=tab2.ID"+
    " order by tab1.sumCol+tab2.sumCol desc";

   @override
  public ArrayList<A> fetchObjects(ResultSet rs)
  {
    ArrayList<A> ret = new ArrayList<A>();
    int count=1;
    while(rs.next())
    {
      A a = new A();
       a.setId(rs.getLong(1));
      System.out.println("top"+(count++)+"  amount:"+rs.getLong(2));
      ret.add(object);
    }
     return ret;
  }

}

解決方案驗證

在實際 mySql 數據庫環境 中,以 A 表數據量 1000 條,B 表數據量 3W 多條,C 表數據量 2000 條情況下進行上文中提到的 top10 的操作,采用 Hibernate 的耗時和用 JDBC 接口解決方案的效率比較如下:

表 1. Hibernate 框架方式與 JDBC 接口方式效率比較 1:

  Hibernate 框架方式 采用 JDBC 接口解決方案 查詢耗時 1475ms 1096ms 排序耗時 1035ms 0ms 總計: 2110ms 1096ms

A 表數據量 2000 條,B 表數據量 6W,C 表數據量 4000 條情況下,采用 Hibernate 的耗時和用 JDBC 接口解決方 案的效率比較:

表 2. Hibernate 框架方式與 JDBC 接口方式效率比較 2:

  Hibernate 框架方式 采用 JDBC 接口解決方案 查詢耗時 2836ms 1657ms 排序耗時 1568ms 0ms 總計: 4404ms 1657ms

由以上結果可以看出:在數據量遞增的情況下,采用 Hibernate 方式下效率 與庫表數據呈線性增長,且排序的操作的效率也是一樣,而直接采用 JDBC 接口解決方案下效率遠遠高於 Hibernate 方式,且在數據量增長的情況下耗時的增長速度處於合理的區間內。

總結

本文 分析了 Hibernate 框架在處理復雜查詢功能上的效率問題,提出並實現了一個在 Hibernate 框架內提供 直接 JDBC 操作接口的解決方案,並實際驗證了該解決方案的有效性,文中的源代碼可以直接運用於選擇 Hibenrate 框架作為數據持久層實現的 J2EE 項目,使之具備操作底層 JDBC 的功能。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved