休眠自动冲洗的黑暗面
介紹
既然我已經描述了JPA和Hibernate刷新策略的基礎知識 ,我就可以繼續闡明Hibernate的AUTO刷新模式的令人驚訝的行為。
并非所有查詢都會觸發會話刷新
許多人會認為Hibernate 總是在執行任何查詢之前先刷新Session。 雖然這可能是一種更直觀的方法,并且可能更接近JPA的AUTO FlushModeType ,但是Hibernate嘗試對其進行優化。 如果當前執行的查詢不會命中未決的SQL INSERT / UPDATE / DELETE語句,則不嚴格要求刷新。
如參考文檔中所述,AUTO刷新策略有時可能在執行查詢之前同步當前持久性上下文。 如果框架作者選擇將其命名為FlushMode.SOMETIMES,那將更加直觀。
JPQL / HQL和SQL
與許多其他ORM解決方案一樣,Hibernate提供了一種非常基于SQL-92語法的有限實體查詢語言( JPQL / HQL )。
當前數據庫方言將實體查詢語言轉換為SQL,因此它必須在不同的數據庫產品中提供相同的功能。 由于大多數數據庫系統都是SQL-92投訴,因此實體查詢語言是最常見的數據庫查詢語法的抽象。
雖然您可以在許多用例(選擇實體,甚至是投影)中使用實體查詢語言,但有時其有限功能與高級查詢請求不匹配。 每當我們想要利用某些特定的查詢技術時,例如:
- 視窗功能
- 數據透視表
- 常用表表達式
我們別無選擇,只能運行本機SQL查詢。
Hibernate是一個持久性框架。 Hibernate從未打算取代SQL。 如果某些查詢在本機查詢中可以更好地表達,那么在數據庫可移植性上犧牲應用程序性能是不值得的。
自動沖洗和HQL / JPQL
首先,我們將測試將要執行HQL查詢時AUTO刷新模式的行為。 為此,我們定義了以下不相關的實體:
該測試將執行以下操作:
- 一個人將被堅持。
- 選擇用戶不應觸發刷新。
- 查詢人員時,AUTO刷新應觸發實體狀態轉換同步(應在執行選擇查詢之前執行人員INSERT)。
提供以下SQL輸出:
[main]: o.h.e.i.AbstractSaveEventListener - Generated identifier: f76f61e2-f3e3-4ea4-8f44-82e9804ceed0, using strategy: org.hibernate.id.UUIDGenerator Query:{[select count(user0_.id) as col_0_0_ from user user0_][]} Query:{[insert into product (color, id) values (?, ?)][12,f76f61e2-f3e3-4ea4-8f44-82e9804ceed0]} Query:{[select product0_.id as col_0_0_ from product product0_][]}如您所見,用戶選擇尚未觸發會話刷新。 這是因為Hibernate會根據掛起的表語句檢查當前查詢空間。 如果當前正在執行的查詢與未刷新的表語句不重疊,則可以安全地忽略刷新。
HQL甚至可以在以下情況下檢測到產品沖洗:
- 子選擇 session.persist(product);
assertEquals(0L, session.createQuery("select count(*) " +"from User u " +"where u.favoriteColor in (select distinct(p.color) from Product p)").uniqueResult());
導致正確的沖洗調用:
Query:{[insert into product (color, id) values (?, ?)][Blue,2d9d1b4f-eaee-45f1-a480-120eb66da9e8]} Query:{[select count(*) as col_0_0_ from user user0_ where user0_.favoriteColor in (select distinct product1_.color from product product1_)][]} - 或theta風格的聯接 session.persist(product);
assertEquals(0L, session.createQuery("select count(*) " +"from User u, Product p " +"where u.favoriteColor = p.color").uniqueResult());
觸發預期的沖洗:
Query:{[insert into product (color, id) values (?, ?)][Blue,4af0b843-da3f-4b38-aa42-1e590db186a9]} Query:{[select count(*) as col_0_0_ from user user0_ cross join product product1_ where user0_.favoriteColor=product1_.color][]}
它起作用的原因是因為實體查詢已被解析并轉換為SQL查詢。 Hibernate無法引用不存在的表,因此它始終知道HQL / JPQL查詢將命中的數據庫表。
因此,Hibernate僅知道我們在HQL查詢中顯式引用的那些表。 如果當前待處理的DML語句暗示數據庫觸發器或數據庫級級聯,則Hibernate將不會意識到這些。 因此,即使對于HQL,“自動”刷新模式也可能導致一致性問題。
自動刷新和本機SQL查詢
當涉及本地SQL查詢時,事情變得越來越復雜。 Hibernate無法解析SQL查詢,因為它僅支持有限的數據庫查詢語法。 許多數據庫系統提供了超越Hibernate Entity Query功能的專有功能。
使用本機SQL查詢查詢Person表不會觸發刷新,從而導致不一致問題:
Product product = new Product(); session.persist(product); assertNull(session.createSQLQuery("select id from product").uniqueResult());DEBUG [main]: o.h.e.i.AbstractSaveEventListener - Generated identifier: 718b84d8-9270-48f3-86ff-0b8da7f9af7c, using strategy: org.hibernate.id.UUIDGenerator Query:{[select id from product][]} Query:{[insert into product (color, id) values (?, ?)][12,718b84d8-9270-48f3-86ff-0b8da7f9af7c]}新保留的產品僅在事務提交期間插入,因為本機SQL查詢未觸發刷新。 這是主要的一致性問題,許多開發人員很難調試甚至無法預見。 這是始終檢查自動生成的SQL語句的另一個原因。
即使對于命名的本機查詢,也會觀察到相同的行為:
@NamedNativeQueries(@NamedNativeQuery(name = "product_ids", query = "select id from product") ) assertNull(session.getNamedQuery("product_ids").uniqueResult());因此,即使SQL查詢已預先加載,Hibernate也不會提取關聯的查詢空間以使其與未決的DML語句匹配。
否決當前的沖洗策略
即使當前會話定義了默認的刷新策略,您也可以始終在查詢基礎上覆蓋它。
查詢刷新模式
ALWAYS模式將在執行任何查詢(HQL或SQL)之前刷新持久性上下文。 這次,Hibernate沒有應用任何優化,所有待處理的實體狀態轉換都將與當前數據庫事務同步。
assertEquals(product.getId(), session.createSQLQuery("select id from product").setFlushMode(FlushMode.ALWAYS).uniqueResult());指示Hibernate應該同步哪些表
您還可以在當前正在執行的SQL查詢上添加同步規則。 然后,Hibernate將知道在執行查詢之前需要同步哪些數據庫表。 這對于二級緩存也很有用。
assertEquals(product.getId(), session.createSQLQuery("select id from product").addSynchronizedEntityClass(Product.class).uniqueResult());結論
自動刷新模式非常棘手,并且在查詢基礎上解決一致性問題是維護人員的噩夢。 如果決定添加數據庫觸發器,則必須檢查所有Hibernate查詢,以確保它們最終不會針對過時的數據運行。
我的建議是使用ALWAYS刷新模式,即使Hibernate作者警告我們:
這種策略幾乎總是不必要且效率低下的。
不一致是一些偶爾過早沖洗的問題。 當混合DML操作和查詢可能會導致不必要的刷新時,這種情況很難緩解。 在會話事務期間,最好在事務開始時(沒有待處理的實體狀態轉換要同步時)和事務結束時(無論如何將刷新當前持久性上下文)執行查詢。
應將實體狀態轉換操作推向事務的結尾,以嘗試避免將它們與查詢操作交錯(因此防止過早的刷新觸發器)。
翻譯自: https://www.javacodegeeks.com/2014/08/the-dark-side-of-hibernate-auto-flush.html
總結
以上是生活随笔為你收集整理的休眠自动冲洗的黑暗面的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单删偷偷加回来对方知道吗 单删偷偷加回来
- 下一篇: 朴三肺是什么意思 朴三肺指什么