Tips
原文作者:Vlad Mihalcea
原文地址:The best way to do batch processing with JPA and Hibernate
在本文中,你將了解什么是批處理,為什么要使用它,以及如何在JPA和Hibernate中正確使用它。
批處理
在編寫企業應用程序時,通常將工作分配在服務於典型OLTP(在線事務處理)傳輸的前端系統,和一個或多個批處理用於ETL(Extract,Transform,Load)操作。
批處理器,顧名思義,將要處理的數據分成幾個塊,因此具有以下優點:
- 每個塊可以通過單獨的工作線程進行處理,因此增加了吞吐量並減少了整個處理時間。
- 每個塊都可以使用自己的數據庫事務,所以如果有一個失敗,就不用丟掉我們所做的所有工作,只是針對當前事務的變化。
JPA 批處理
當使用JPA時,假設要插入50個Post
實體,那么應該這樣做:
int entityCount = 50;
int batchSize = 25;
EntityManager entityManager = null;
EntityTransaction transaction = null;
try {
entityManager = entityManagerFactory()
.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
for ( int i = 0; i < entityCount; ++i ) {
if ( i > 0 && i % batchSize == 0 ) {
entityManager.flush();
entityManager.clear();
transaction.commit();
transaction.begin();
}
Post post = new Post(
String.format( "Post %d", i + 1 )
);
entityManager.persist( post );
}
transaction.commit();
} catch (RuntimeException e) {
if ( transaction != null &&
transaction.isActive()) {
transaction.rollback();
}
throw e;
} finally {
if (entityManager != null) {
entityManager.close();
}
}
每個開始操作都會開啟事務,因為每個實體狀態轉換必須在數據庫事務的范圍內執行。
for循環一次會持久化一個Post
實體。 但是,由於實體狀態轉換僅在執行flush
方法更新數據庫時執行,因此我們可以將多個SQL 插入語句分組到到要給單一PreparedStatement
執行中,該執行需要多個參數。
每次迭代計數器(變量i)已達到batchSize
臨界值的倍數,我們可以刷新EntityManager
並提交數據庫事務。 通過在每次批處理執行后提交數據庫事務,我們獲得以下優點:
- 避免了長期運行的事務,這對MVCC關系數據庫系統是不利的。
- 我們確保如果執行失敗,不會丟失以前成功執行的批處理作業完成的工作。
EntityManager
在每次批量執行后被清除,這樣就不會繼續累積可能導致管理實體的幾個問題:
- 如果要持久化的實體數量龐大,那么存在內存不足的風險。
- 在持久化上下文中累積的實體越多,flush越慢。 所以,最好的做法是確保持久性上下文盡可能的短小。
如果拋出異常,我們必須確保回滾當前正在運行的數據庫事務。 否則可能會導致許多問題,因為數據庫可能仍然認為事務處於打開狀態,鎖可能會被持有,直到事務超時或由DBA結束。
最后,我們需要關閉EntityManager
,以便可以清除上下文並釋放Session
級的資源。
雖然這是使用JPA進行批處理的正確方法,但還沒有完成。 如前所述,也可以從JDBC批量更新中受益。 為此,我們需要提供以下Hibernate配置屬性:
<property
name="hibernate.jdbc.batch_size"
value="25"
/>
<property
name="hibernate.order_inserts"
value="true"
/>
<property
name="hibernate.order_updates"
value="true"
/>
這些屬性允許我們將多個SQL語句批處理為單個PreparedStatement
執行,這需要單個數據庫往返。 選擇值25以匹配EntityManager
批處理作業的臨界值。
執行以前的測試用例時,Hibernate生成2個SQL INSERT語句:
INSERT INTO post (title, id) values (?, ?)"],
Params:[
(Post 1, 1), (Post 2, 2), (Post 3, 3),
(Post 4, 4), (Post 5, 5), (Post 6, 6),
(Post 7, 7), (Post 8, 8), (Post 9, 9),
(Post 10, 10), (Post 11, 11), (Post 12, 12),
(Post 13, 13), (Post 14, 14), (Post 15, 15),
(Post 16, 16), (Post 17, 17), (Post 18, 18),
(Post 19, 19), (Post 20, 20), (Post 21, 21),
(Post 22, 22), (Post 23, 23), (Post 24, 24),
(Post 25, 25)
]
INSERT INTO post (title, id) values (?, ?)"],
Params:[
(Post 26, 26), (Post 27, 27), (Post 28, 28),
(Post 29, 29), (Post 30, 30), (Post 31, 31),
(Post 32, 32), (Post 33, 33), (Post 34, 34),
(Post 35, 35), (Post 36, 36), (Post 37, 37),
(Post 38, 38), (Post 39, 39), (Post 40, 40),
(Post 41, 41), (Post 42, 42), (Post 43, 43),
(Post 44, 44), (Post 45, 45), (Post 46, 46),
(Post 47, 47), (Post 48, 48), (Post 49, 49),
(Post 50, 50)
]
結論
了解如何正確設計批處理作業在開發企業應用程序時非常重要。 幸運的是,使用JPA和Hibernate,這個任務很容易實現。 最重要的方面是考慮到如何讓關系數據庫工作效果最好,這就是驅動數據訪問設計決策的因素。