介绍

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上获得 。
参考: Hibernate Fact:如何通过Vlad Mihalcea的Blog博客从我们的JCG合作伙伴 Vlad Mihalcea “断言” SQL语句计数 。

翻译自: https://www.javacodegeeks.com/2014/02/hibernate-facts-how-to-assert-the-sql-statement-count.html

Hibernate事实:如何“断言” SQL语句计数相关推荐

  1. 休眠事实:如何“断言” SQL语句计数

    介绍 Hibernate简化了CRUD操作,尤其是在处理实体图时. 但是任何抽象都有其代价,而Hibernate也不例外. 我已经讨论了获取策略和了解Criteria SQL查询的重要性,但是您可以做 ...

  2. hibernate框架控制台输出sql语句

    第一步:jar包,有如下三个jar包(没有的话,给我留言,我发给你) 第二步,在src包下面新建一个file,命名为log4j.properties 内容如下: # # Hibernate, Rela ...

  3. 灵活控制 Hibernate 的日志或 SQL 输出,以便于诊断

    我们在使用 Hibernate 时一般只会关注是否显示生成的 SQL 语句,不过有些时候还不够.默认时 Hibernate 执行的 SQL 语句是打印在控制台上的,它也可以配置为输出给 Log4J 或 ...

  4. java sql语句格式化_显示和格式化SQL语句

    基础篇https://edu.51cto.com/course/19845.html https://edu.51cto.com/course/19845.html https://edu.51cto ...

  5. 在控制台显示sql语句,类似hibernate show_sql.

    http://www.yihaomen.com/article/java/425.htm 用过Hibernate的人都知道,hibernate 是可以配置 show_sql 显示 自动生成的SQL 语 ...

  6. hibernate oracle驱动,出错场景是升级oracle驱动,将版本从ojdbc14升级到ojdbc6,hibernate执行原生态sql语句会报如下错误...

    出错场景是升级oracle驱动,将版本从ojdbc14升级到ojdbc6,hibernate执行原生态sql语句会报如下错误: org.hibernate.MappingException: No D ...

  7. hibernate防止sql语句注入

    如果在查询字段中输入单引号"'",则会报错,这是因为输入的单引号和其他的sql组合在一起编程了一个新的sql,实际上这就是SQL注入漏洞,后来我在前台和后台都对输入的字符进行了判断 ...

  8. 在hibernate框架中配置显示sql语句

    使用Hibernate的框架开发时,可在Hibernate.cfg.xml中加上 <property name="hibernate.show_sql">true< ...

  9. 使用SQLQuery 在Hibernate中使用sql语句

    对原生SQL查询执行的控制是通过SQLQuery接口进行的,通过执行Session.createSQLQuery()获取这个接口.下面来描述如何使用这个API进行查询. 1.标量查询(Scalar q ...

最新文章

  1. Caused by: org.apache.flink.core.fs.UnsupportedFileSystemSchemeException: Could not find a file syst
  2. 前端一HTML:二十二元素显示方式案例
  3. collection包下Counter类统计list中各个元素出现的次数
  4. boost::mpl::abs相关的测试程序
  5. boost::mpi模块实现传输数据类型的骨架和内容的通信器的测试
  6. 多米诺骨牌v.1MEL语言
  7. Visual studio 2012 ultimate 安装遇到 Prerequisites , 错误的函数 incorrect function
  8. MongoDB的基本概念与操作
  9. 总结一下矩阵的基本操作
  10. VMworld 2016 US带来了哪些惊喜?
  11. 利用Python编程,分别使用梯度下降法和最小二乘法求解多元函数
  12. 拓端tecdat|R语言GJR-GARCH和GARCH波动率预测普尔指数时间序列和Mincer Zarnowitz回归、DM检验、JB检验
  13. plc编程语言是c语言吗,PLC各种编程语言特点你了解多少?
  14. 简洁明了的个人求职简历如何写?
  15. Thoughtworks QA测试一面凉经
  16. 2014年实习生招聘之腾讯实习生招聘面试(一面)—2014/04/01
  17. 用css解决文本折行问题
  18. psCS6图片文件无法直接拖入的解决方法
  19. Python读取和操作Excel(.xlsx)文件
  20. [ROC-RK3568-PC] 手把手教你把出厂的Android系统烧写为Ubuntu系统

热门文章

  1. 2021“MINIEYE杯”中国大学生算法设计超级联赛(2)I love exam(背包)
  2. NOI2021模拟测试赛 解题报告
  3. SpringCloud Zuul(四)之工作原理
  4. 漫画:如何实现大整数相加
  5. 教你如何定位及优化SQL语句的性能问题
  6. Servlet 登录时数据校验
  7. jQuery 表格实现
  8. 2016蓝桥杯省赛---java---B---6(方格填数)
  9. Kafka启动出现Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Pr
  10. java 取随机正整数_Java获取随机数