Hibernate的檢索方式、級聯操作、批處理

一、Hibernate的五種檢索方式:

1.HQL(hibernate Query Language) 

是面向對象的查詢語言, 它和 SQL 查詢語言有些相似. 在 Hibernate 提供的各種檢索方式中, HQL 是使用最廣的一種檢索方式. 它有如下功能:

l  在查詢語句中設定各種查詢條件

l  支持投影查詢, 即僅檢索出對象的部分屬性

l  支持分頁查詢

l  支持連接查詢

l  支持分組查詢, 允許使用 HAVING 和 GROUP BY 關鍵字

l  提供內置聚集函數, 如 sum(), min() 和 max()

l  能夠調用 用戶定義的 SQL 函數或標準的 SQL 函數

l  支持子查詢

l  支持動態綁定參數

2.OID 檢索方式

 按照對象的 OID 來檢索對象

1.     提供一個無參的構造器。使Hibernate可以使用Constructor.newInstance() 來實例化持久化類。

2.      提供一個標識屬性(identifierproperty)。通常映射爲數據庫表的主鍵字段。如果沒有該屬性,一些功能將不起作用,如:Session.saveOrUpdate()。

3.      爲類的持久化類的字段聲明訪問方法(get/set)。Hibernate對JavaBeans風格的屬性實行持久化。

4.      使用非final類。在運行時生成代理是Hibernate的一個重要的功能。如果持久化類沒有實現任何接口,Hibnernate使用 CGLIB 生成代理。如果使用的是 final 類,則無法生成CGLIB代理。

5.     重寫eqauls()和hashCode()方法。如果需要把持久化類的實例放到Set中(當需要進行關聯映射時),則應該重寫這兩個方法。


3.QBC 檢索方式

使用 QBC(Query ByCriteria) API 來檢索對象. 這種 API 封裝了基於字符串形式的查詢語句, 提供了更加面向對象的查詢接口.它提供了更加面向對象的操作方式。

4.OGNL檢索方式

就是在HQL中使用.來獲取對象的屬性

5.本地SQL方式

與本地數據庫綁定,但是效率比較高。


二、Hibernate的級聯操作:

 1.簡單的介紹

cascade和inverse (Employee – Department)

l  Casade用來說明當對主對象進行某種操作時是否對其關聯的從對象也作類似的操作,常用的cascade:

         none,all,save-update,delete, lock,refresh,evict,replicate,persist,

         merge,delete-orphan(one-to-many)。一般對many-to-one,many-to-many不設置級聯,在<one-to-one>和<one-to-many>中設置級聯。

l  inverse表“是否放棄維護關聯關係”(在Java裏兩個對象產生關聯時,對數據庫表的影響),在one-to-many和many-to-many的集合定義中使用,inverse=”true”表示該對象不維護關聯關係;該屬性的值一般在使用有序集合時設置成false(注意hibernate的缺省值是false)。

         one-to-many維護關聯關係就是更新外鍵。many-to-many維護關聯關係就是在中間表增減記錄。

         注: 配置成one-to-one的對象不維護關聯關係

2.屬性的解析
class元素的lazy屬性設定爲true,表示延遲加載,如果lazy設爲false,則
表示立即加載。以下對這二點進行說明。
     立即加載:表示Hibernate在從數據庫中取得數據組裝好一個對象(如學生1)後,
            會立即再從數據庫取得數據組裝此對象所關聯的對象(如學生證1)。
     延遲加載:表示Hibernate在從數據庫中取得數據組裝好一個對象(如學生1)後,
            不會立即再從數據庫中取得數據組裝此對象所關聯的對象(如學生1),
            而是等到需要時,纔會從數據庫取得數據組裝此關聯對象。
<one-to-one>元素的cascade屬性表明操作是否從父對象級聯到被關聯的對象,     它
的取得可以是以下幾種:
     none:在保存,刪除或修改當前對象時,不對其附屬對象(關聯對象)進行級聯
          操作。它是默認值。
     save-update:在保存,更新當前對象時,級聯保存,更新附屬對象(臨時對象,
          遊離對象)。
     delete:在刪除當前對象時,級聯刪除附屬對象。
     all:所有情況下均進行級聯操作,即包含save-update和delete操作。
     delete-orphan:刪除和當前對象解除關係的附屬對象。
<one-to-one>元素的fetch屬性的可選值是join和select,默認是select。
當fetch屬性設定爲join時,表示連接抓取(Join fetching):Hibernate通過
在Select語句中使用outer join(外連接)來獲得對象的關聯實例或者關聯集合。
當fetch屬性設定爲select時,表示查詢抓取(Select fetching):需要另外發
送一條Select語句抓取當前對象的關聯實體或集合。

3.代碼練習

<set name="emps" cascade="save-update">
 <key column="depart_id"/>
  <one-to-many class="Employee"/>
</set>

<set name="students" table="taacher_student" inverse="true"><!-- table是用來指定中間表的屬性 -->
<key column="teacher_id"></key><!-- 查找教師id時,鏈接中間表表的teacher_id -->
<many-to-many class="Student" column="student_id"></many-to-many>
</set>


三、Hibernate的批處理:

我們在用數據庫時,如果可以批量插入或者更新數據,可以很大地提高使用數據庫的性能。

1.撰寫代碼

首先你必須要撰寫或者修改你的批處理代碼。比如你的目標是通過Hibernate一次保存50個Customer對象,那你必須每save滿50個對象,就flush一次,如下:

Transaction tx = session.beginTransaction();

for(Customer customer : customers) {
  session.save(customer );

  if(++i % 50 == 0) {
    session.flush();
    session.clear();
  }
}
tx.commit();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

這是因爲Hibernate會把新插入的數據都保存在緩存(persistence context cache)中。因爲緩存大小的限制,所以隔一定數量就要flush,否則可能會碰上OutMemorException.

2.修改配置文件

Hibernate官方指導文檔建議,爲了最好的性能,應該把hibernate.jdbc.batch_size設置成和代碼中procedure batch一樣的size,在hibernate.cfg.xml中加上:

<property name="hibernate.jdbc.batch_size">50</property>
  • 1
  • 1

這裏還有一點要注意的是,記得關掉batch操作的二級緩存。否則的話,每個對象被保存時,都會同時保存到二級緩存,這是一個不必要的浪費。

3.修改標識符生成器(id generator)

上面兩個步驟看起來應該大功告成了,其實離成功還得遠。首先,Hibernate明確說明了這樣一個事實:

Hibernate silently disables JDBC batch inserts if your entity is mapped with an identity identifier generator; many JDBC drivers don’t support batching in that case
  • 1
  • 1

也就是說,如果你用的是identity的標識符生成器,那Hibernate的批處理就不靈了,所以我們必須得換一個,下面告訴你如何用MultipleHiLoPerTableGenerator。這個標識符生成器由Hibernate按照HiLo算法來生成id,它從數據庫的特定表的字段長獲取high值。至於什麼是Hilo算法,可以參考這篇文章(https://vladmihalcea.com/2014/06/23/the-hilo-algorithm/)。在你的Customer.hbm.xml裏,加上下面的代碼:

<id name="id" type="java.lang.Integer">
      <column name="id"/>
      <generator class="org.hibernate.id.MultipleHiLoPerTableGenerator">
        <param name="table">identityGenerator</param>
        <param name="primary_key_column">sequence_name</param>
        <param name="value_column">next_hi_value</param>
        <param name="primary_key_value">TableCustomer</param>
        <param name="max_lo">10000</param>
      </generator>
    </id>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

hi值存放在identityGenerator表的next_hi_value字段中:

CREATE TABLE IdentityGenerator (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    sequence_name VARCHAR(36) NOT NULL,
    next_hi_value INTEGER NOT NULL
)ENGINE= InnoDB DEFAULT CHARSET=latin1;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

4.批處理到塊處理

到這一步位置,batch操作已經完成了,但是你可能發現性能沒有太多的提高,如果你看一眼MySQL的log,你會發現數據實際上還是一條條插入數據庫的。事實上,我們之前做的事情,只是把50條insert語句打包成了一個batch,發給了數據庫,而數據庫並沒有把這50條insert變成下面這個我們期望的樣子:

INSERT INTO Customer () VALUES (), (), (), (), ()
  • 1
  • 1

這被稱作bulk insert的操作通常依賴於數據庫connector provider。Hibernate依賴的mysql-connector-Java提供了rewriteBatchedStatements這個配置參數來幫助我們把batch inserts轉成bulk inserts。再次修改你的hibernate.cfg.xml如下:

<property name="hibernate.connection.url">jdbc:mysql://local:3306/database?rewriteBatchedStatements=true</property>
  • 1
  • 1

5.查看數據庫log

最後,你還可以查看下mysql的log,來確定這會是不是真的bulk insert了

sudo tail -f  /var/log/mysql/mysql.log
  • 1
  • 1

mysqllog的打開方法如下:

mysql> show global variables like '%general%';     
mysql> SET GLOBAL general_log = 'ON';   // 打開     
mysql> SET GLOBAL general_log = 'OFF'; // 關閉  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

6.一個常見的flush時拋出的異常

如果你跟我一樣不幸運,session.flush()時可能會看到以下異常:

org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 10; expected: 1
  • 1
  • 1

我在stackoverflow上找到了兩種解決方法:

  • 檢查你的mysql-connector-java版本,把mysql-connector-java降級到5.1.24,或者升級到更新的版本(參考:http://stackoverflow.com/questions/23663557/hibernate-mysql-rewritebatchedstatements-true)
  • 在org.hibernate.jdbc這個源碼包中,拷貝Exceptations.java這個文件到你的工程裏(不要改變包名和相對路徑),然後在你拷貝的這個Exceptions.java中,註釋掉checkBatched方法中拋出BatchedTooManyRowsAffectedException的相關代碼

    我是用第二種方法解決的。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章