hibernate批量處理


批量處理(Batch processing)
使用 Hibernate 將 100,000 條記錄插入到數據庫的一個很天真的做法可能是這樣的:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
}
tx.commit();
session.close();
這段程序大概運行到 50,000 條記錄左右會失敗並拋出內存溢出異常(OutOfMemoryException) 。
這是因為 Hibernate 把所有新插入的客戶(Customer)實例在 session 級別的緩存區進行了緩存
的緣故。
我們會在本章告訴你如何避免此類問題。首先,如果你要執行批量處理並且想要達到一個理想的
性能,那么使用  JDBC  的批量(batching)功能是至關重要。將  JDBC  的批量抓取數量(batch
size)參數設置到一個合適值(比如,10 - 50 之間):
hibernate.jdbc.batch_size 20
注意,假若你使用了 identiy 標識符生成器,Hibernate 在 JDBC 級別透明的關閉插入語句的批
量執行。
你也可能想在執行批量處理時完全關閉二級緩存:
hibernate.cache.use_second_level_cache false
但是,這不是絕對必須的,因為我們可以顯式設置 CacheMode 來關閉與二級緩存的交互。
15.1. 批量插入(Batch inserts)
如果要將很多對象持久化,你必須通過經常的調用 flush() 以及稍后調用 clear() 來控制第一級
緩存的大小。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
   
for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
25315 章 批量處理(Batch processing)
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}
   
tx.commit();
session.close();
15.2. 批量更新(Batch updates)
此方法同樣適用於檢索和更新數據。此外,在進行會返回很多行數據的查詢時,你需要使用
scroll() 方法以便充分利用服務器端游標所帶來的好處。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .setCacheMode(CacheMode.IGNORE)
    .scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    if ( ++count % 20 == 0 ) {
        //flush a batch of updates and release memory:
        session.flush();
        session.clear();
    }
}
   
tx.commit();
session.close();
15.3. StatelessSession(無狀態 session)接口
作為選擇,Hibernate 提供了基於命令的 API,可以用 detached object 的形式把數據以流的方
法加入到數據庫,或從數據庫輸出。StatelessSession  沒有持久化上下文,也不提供多少高層的
生命周期語義。特別是,無狀態 session 不實現第一級 cache,也不和第二級緩存,或者查詢緩
存交互。它不實現事務化寫,也不實現臟數據檢查。用 stateless session 進行的操作甚至不級
聯到關聯實例。stateless session 忽略集合類(Collections)。通過 stateless session 進
行的操作不觸發 Hibernate 的事件模型和攔截器。無狀態 session 對數據的混淆現象免疫,因
為它沒有第一級緩存。無狀態 session 是低層的抽象,和低層 JDBC 相當接近。
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
254

DML(數據操作語言)風格的操作(DML-style operations)
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    session.update(customer);
}
   
tx.commit();
session.close();
注意在上面的例子中,查詢返回的 Customer 實例立即被脫管(detach)。它們與任何持久化上下
文都沒有關系。
StatelessSession 接口定義的 insert(), update() 和 delete() 操作是直接的數據庫行級別操作,
其結果是立刻執行一條 INSERT, UPDATE 或 DELETE 語句。因此,它們的語義和 Session 接口定義
的 save(), saveOrUpdate() 和delete() 操作有很大的不同。
15.4. DML(數據操作語言)風格的操作(DML-style
operations)
就像已經討論的那樣,自動和透明的對象/關系映射(object/relational  mapping)關注於管理
對象的狀態。這就意味着對象的狀態存在於內存,因此直接操作(使用  SQL  Data  Manipulation
Language(DML,數據操作語言)語句 :INSERT ,UPDATE 和 DELETE) 數據庫中的數據將不會影響內
存中的對象狀態和對象數據。不過,Hibernate 提供通過 Hibernate 查詢語言(HQL)來執行大
批量 SQL 風格的 DML 語句的方法。
UPDATE  和  DELETE  語句的偽語法為:(  UPDATE  |  DELETE  )  FROM?  EntityName  (WHERE
where_conditions)?。
要注意的事項:
*在 FROM 子句(from-clause)中,FROM 關鍵字是可選的
*在 FROM 子句(from-clause)中只能有一個實體名,它可以是別名。如果實體名是別名,那么
任何被引用的屬性都必須加上此別名的前綴;如果不是別名,那么任何有前綴的屬性引用都是
非法的。
*不能在大批量 HQL 語句中使用 joins 連接(顯式或者隱式的都不行)。不過在 WHERE 子句中
可以使用子查詢。可以在 where 子句中使用子查詢,子查詢本身可以包含 join。
*整個 WHERE 子句是可選的。
舉個例子,使用 Query.executeUpdate() 方法執行一個 HQL UPDATE語句(方法命名是來源於 JDBC
的 PreparedStatement.executeUpdate()):
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
25515 章 批量處理(Batch processing)
        .executeUpdate();
tx.commit();
session.close();
HQL UPDATE 語句,默認不會影響更新實體的 version 或 the timestamp 屬性值。這和 EJB3 規
范是一致的。但是,通過使用 versioned update,你可以強制 Hibernate 正確的重置version 或者
timestamp 屬性值。這通過在 UPDATE 關鍵字后面增加 VERSIONED 關鍵字來實現的。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();
注意,自定義的版本類型(org.hibernate.usertype.UserVersionType)不允許和  update  versioned
語句聯用。
執行一個 HQL DELETE,同樣使用 Query.executeUpdate() 方法:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();
由  Query.executeUpdate()  方法返回的整型值表明了受此操作影響的記錄數量。注意這個數值可
能與數據庫中被(最后一條  SQL  語句)影響了的“行”數有關,也可能沒有。一個大批量  HQL
操作可能導致多條實際的SQL語句被執行,舉個例子,對  joined-subclass  映射方式的類進行的
此類操作。這個返回值代表了實際被語句影響了的記錄數量。在那個  joined-subclass  的例子
中, 對一個子類的刪除實際上可能不僅僅會刪除子類映射到的表而且會影響“根”表,還有可能
影響與之有繼承關系的 joined-subclass 映射方式的子類的表。
INSERT 語句的偽碼是:INSERT INTO EntityName properties_list select_statement。要注意的是:
*只支持 INSERT INTO ... SELECT ... 形式,不支持 INSERT INTO ... VALUES ... 形式。
properties_list 和 SQL INSERT 語句中的字段定義(column speficiation)類似。對參與繼承樹
映射的實體而言,只有直接定義在給定的類級別的屬性才能直接在 properties_list 中使用。
超類的屬性不被支持;子類的屬性無意義。換句話說,INSERT 天生不支持多態性。
256

DML(數據操作語言)風格的操作(DML-style operations)
*select_statement 可以是任何合法的 HQL 選擇查詢,不過要保證返回類型必須和要插入的類型
完全匹配。目前,這一檢查是在查詢編譯的時候進行的,而不是把它交給數據庫。注意,在
HibernateType  間如果只是等價(equivalent)而非相等(equal),會導致問題。定義為
org.hibernate.type.DateType 和 org.hibernate.type.TimestampType 的兩個屬性可能會產生類型
不匹配錯誤,雖然數據庫級可能不加區分或者可以處理這種轉換。
*對  id  屬性來說,insert  語句給你兩個選擇。你可以明確地在  properties_list
表中指定  id  屬性(這樣它的值是從對應的  select  表達式中獲得),或者在
properties_list  中省略它(此時使用生成指)。后一種選擇只有當使用在數據庫中生成值的
id  產生器時才能使用;如果是“內存”中計算的類型生成器,在解析時會拋出一個異常。注
意,為了說明這一問題,數據庫產生值的生成器是  org.hibernate.id.SequenceGenerator(和它
的子類),以及任何 org.hibernate.id.PostInsertIdentifierGenerator 接口的實現。這兒最值得
注意的意外是  org.hibernate.id.TableHiLoGenerator,它不能在此使用,因為它沒有得到其值的
途徑。*對映射為  version  或  timestamp  的屬性來說,insert  語句也給你兩個選擇,你可以
在  properties_list  表中指定(此時其值從對應的  select  表達式中獲得),或者在
properties_list  中省略它(此時,使用在  org.hibernate.type.VersionType  中定義的  seed
value(種子值))。
下面是一個執行 HQL INSERT 語句的例子:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer
 c where ...";
int createdEntities = s.createQuery( hqlInsert )
        .executeUpdate();
tx.commit();
session.close();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM