Hibernate事实:如何“断言” SQL语句计数
介紹
Hibernate簡化了CRUD操作,尤其是在處理實體圖時。 但是任何抽象都有其代價,而Hibernate也不例外。 我已經討論了獲取策略和了解Criteria SQL查詢的重要性,但是您可以做更多的事情來統治JPA。 這篇文章是關于控制Hibernate代表您調用SQL語句計數的。
在ORM工具如此流行之前,所有數據庫交互都是通過顯式SQL語句完成的,而優化主要針對慢速查詢。
Hibernate可能會給人一種錯誤的印象,即您不必擔心SQL語句。 這是一個錯誤和危險的假設。 Hibernate應該減輕域模型的持久性,而不是使您擺脫任何SQL交互。
使用Hibernate,您可以管理實體狀態轉換,然后轉換為SQL語句。 生成SQL語句的數量受當前的獲取策略,條件查詢或集合映射影響,您可能并不總是能獲得所需的結果。 忽略SQL語句是有風險的,最終可能會給整個應用程序性能帶來沉重的負擔。
我是同行評審的堅定倡導者,但這并不是發現不良的Hibernate使用情況的“必要條件”。 細微的更改可能會影響SQL語句的計數,并且在檢查過程中不會引起注意。 至少,當“猜測” JPA SQL語句時,我覺得我可以使用任何其他幫助。 我要盡可能地實現自動化,這就是為什么我想出一種用于執行SQL語句計數期望的機制的原因。
首先,我們需要一種方法來攔截所有已執行SQL語句。 我對此主題進行了研究,很幸運能找到這個出色的數據源代理庫。
添加自動驗證器
此保護措施旨在僅在測試階段運行,因此我將其專門添加到“集成測試”彈簧上下文中。 我已經討論過Spring bean別名 ,現在正是使用它的合適時機。
<bean id="testDataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init"destroy-method="close"><property name="className" value="bitronix.tm.resource.jdbc.lrc.LrcXADataSource"/><property name="uniqueName" value="testDataSource"/><property name="minPoolSize" value="0"/><property name="maxPoolSize" value="5"/><property name="allowLocalTransactions" value="false" /><property name="driverProperties"><props><prop key="user">${jdbc.username}</prop><prop key="password">${jdbc.password}</prop><prop key="url">${jdbc.url}</prop><prop key="driverClassName">${jdbc.driverClassName}</prop></props></property> </bean><bean id="proxyDataSource" class="net.ttddyy.dsproxy.support.ProxyDataSource"><property name="dataSource" ref="testDataSource"/><property name="listener"><bean class="net.ttddyy.dsproxy.listener.ChainListener"><property name="listeners"><list><bean class="net.ttddyy.dsproxy.listener.CommonsQueryLoggingListener"><property name="logLevel" value="INFO"/></bean><bean class="net.ttddyy.dsproxy.listener.DataSourceQueryCountListener"/></list></property></bean></property> </bean><alias name="proxyDataSource" alias="dataSource"/>新的代理數據源將裝飾現有數據源,從而攔截所有已執行SQL語句。 該庫可以記錄所有SQL語句以及實際參數值,這與默認的Hibernate記錄不同,該記錄只顯示一個占位符。
驗證器的外觀如下:
public class SQLStatementCountValidator {private SQLStatementCountValidator() {}/*** Reset the statement recorder*/public static void reset() {QueryCountHolder.clear();}/*** Assert select statement count* @param expectedSelectCount expected select statement count*/public static void assertSelectCount(int expectedSelectCount) {QueryCount queryCount = QueryCountHolder.getGrandTotal();int recordedSelectCount = queryCount.getSelect();if(expectedSelectCount != recordedSelectCount) {throw new SQLSelectCountMismatchException(expectedSelectCount, recordedSelectCount);}}/*** Assert insert statement count* @param expectedInsertCount expected insert statement count*/public static void assertInsertCount(int expectedInsertCount) {QueryCount queryCount = QueryCountHolder.getGrandTotal();int recordedInsertCount = queryCount.getInsert();if(expectedInsertCount != recordedInsertCount) {throw new SQLInsertCountMismatchException(expectedInsertCount, recordedInsertCount);}}/*** Assert update statement count* @param expectedUpdateCount expected update statement count*/public static void assertUpdateCount(int expectedUpdateCount) {QueryCount queryCount = QueryCountHolder.getGrandTotal();int recordedUpdateCount = queryCount.getUpdate();if(expectedUpdateCount != recordedUpdateCount) {throw new SQLUpdateCountMismatchException(expectedUpdateCount, recordedUpdateCount);}}/*** Assert delete statement count* @param expectedDeleteCount expected delete statement count*/public static void assertDeleteCount(int expectedDeleteCount) {QueryCount queryCount = QueryCountHolder.getGrandTotal();int recordedDeleteCount = queryCount.getDelete();if(expectedDeleteCount != recordedDeleteCount) {throw new SQLDeleteCountMismatchException(expectedDeleteCount, recordedDeleteCount);}} }該實用程序與JPA和MongoDB樂觀并發控制重試機制一起,是我的db-util項目的一部分。
由于它已經在Maven Central Repository中提供,因此只需將以下依賴項添加到pom.xml中就可以輕松使用它:
<dependency><groupId>com.vladmihalcea</groupId><artifactId>db-util</artifactId><version>0.0.1</version> </dependency>讓我們寫一個測試來檢測臭名昭著的N + 1選擇查詢問題 。
為此,我們將編寫兩種服務方法,其中一種受到上述問題的影響:
@Override @Transactional public List<WarehouseProductInfo> findAllWithNPlusOne() {List<WarehouseProductInfo> warehouseProductInfos = entityManager.createQuery("from WarehouseProductInfo", WarehouseProductInfo.class).getResultList();navigateWarehouseProductInfos(warehouseProductInfos);return warehouseProductInfos; }@Override @Transactional public List<WarehouseProductInfo> findAllWithFetch() {List<WarehouseProductInfo> warehouseProductInfos = entityManager.createQuery("from WarehouseProductInfo wpi " +"join fetch wpi.product p " +"join fetch p.company", WarehouseProductInfo.class).getResultList();navigateWarehouseProductInfos(warehouseProductInfos);return warehouseProductInfos; }private void navigateWarehouseProductInfos(List<WarehouseProductInfo> warehouseProductInfos) {for(WarehouseProductInfo warehouseProductInfo : warehouseProductInfos) {warehouseProductInfo.getProduct();} }單元測試非常簡單,因為它遵循與任何其他JUnit斷言機制相同的編碼風格。
try {SQLStatementCountValidator.reset();warehouseProductInfoService.findAllWithNPlusOne();assertSelectCount(1); } catch (SQLSelectCountMismatchException e) {assertEquals(3, e.getRecorded()); }SQLStatementCountValidator.reset(); warehouseProductInfoService.findAllWithFetch(); assertSelectCount(1);我們的驗證器適用于所有SQL語句類型,因此讓我們檢查以下服務方法正在執行多少個SQL INSERT:
@Override @Transactional public WarehouseProductInfo newWarehouseProductInfo() {LOGGER.info("newWarehouseProductInfo");Company company = entityManager.createQuery("from Company", Company.class).getResultList().get(0);Product product3 = new Product("phoneCode");product3.setName("Phone");product3.setCompany(company);WarehouseProductInfo warehouseProductInfo3 = new WarehouseProductInfo();warehouseProductInfo3.setQuantity(19);product3.addWarehouse(warehouseProductInfo3);entityManager.persist(product3);return warehouseProductInfo3; }驗證器看起來像:
SQLStatementCountValidator.reset(); warehouseProductInfoService.newWarehouseProductInfo(); assertSelectCount(1); assertInsertCount(2);讓我們檢查一下測試日志,以使自己確信其有效性:
INFO [main]: o.v.s.i.WarehouseProductInfoServiceImpl - newWarehouseProductInfo Hibernate: select company0_.id as id1_6_, company0_.name as name2_6_ from Company company0_ INFO [main]: n.t.d.l.CommonsQueryLoggingListener - Name:, Time:1, Num:1, Query:{[select company0_.id as id1_6_, company0_.name as name2_6_ from Company company0_][]} Hibernate: insert into WarehouseProductInfo (id, quantity) values (default, ?) INFO [main]: n.t.d.l.CommonsQueryLoggingListener - Name:, Time:0, Num:1, Query:{[insert into WarehouseProductInfo (id, quantity) values (default, ?)][19]} Hibernate: insert into Product (id, code, company_id, importer_id, name, version) values (default, ?, ?, ?, ?, ?) INFO [main]: n.t.d.l.CommonsQueryLoggingListener - Name:, Time:0, Num:1, Query:{[insert into Product (id, code, company_id, importer_id, name, version) values (default, ?, ?, ?, ?, ?)][phoneCode,1,-5,Phone,0]}結論
代碼審查是一種很好的技術,但是在大規模開發項目中還遠遠不夠。 這就是為什么自動檢查至關重要。 一旦編寫了測試,您可以放心,將來的任何更改都不會破壞您的假設。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2014/02/hibernate-facts-how-to-assert-the-sql-statement-count.html
總結
以上是生活随笔為你收集整理的Hibernate事实:如何“断言” SQL语句计数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 隔行如隔山下一句 下一句是隔行不隔理
- 下一篇: 将应用程序集成为Heroku附加组件