休眠事实:如何“断言” 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上获得 。

参考: Hibernate事实:如何通过Vlad Mihalcea的Blog博客从我们的JCG合作伙伴 Vlad Mihalcea “断言” SQL语句计数 。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/364803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件工程(2019)第三次个人作业

目录 软件工程第三次作业问题描述分析并设计程序程序流程图选择覆盖标准并设计测试样例软件工程第三次作业 项目地址 问题描述 题目(1)&#xff1a;最大连续子数组和&#xff08;最大子段和&#xff09; 背景 问题&#xff1a; 给定n个整数&#xff08;可能为负数&#xff09;组…

Flutter - 创建侧滑菜单

侧滑菜单在安卓App里面非常常见&#xff0c;比如Gmail&#xff0c;Google Play&#xff0c;Twitter等。看下图 网上也有很多创建侧滑菜单的教程&#xff0c;我也来记录一下&#xff0c;自己学习创建Drawer的过程。 1. 创建一个空的App import package:flutter/material.dart;cl…

java框架白话_Java NIO框架Netty教程(二) 白话概念

"Hello World"的代码固然简单&#xff0c;不过其中的几个重要概念(类)和 Netty的工作原理还是需要简单明确一下&#xff0c;至少知道其是负责什。方便自己以后更灵活的使用和扩展。声明&#xff0c;笔者一介码农&#xff0c;不会那么多专业的词汇和缩写&#xff0c;只…

js实现字体和容器宽高随窗口改变

用于字体大小和容器的宽高字体和宽高设为rem就可以了 var html document.documentElement;   function fonts(){   var hW html.offsetWidth;   var hS hW / 50;   html.style.fontSize hS "px"; } //浏览器窗口改变自动刷新 $(window).resize…

使用SWTEventHelper清除SWT侦听器通知

为基于SWT的UI编写测试通常需要以编程方式通知小部件侦听器。 不幸的是&#xff0c;用于创建&#xff0c;初始化并最终触发事件的代码有点冗长&#xff0c;并且分散了测试的实际目的。 在几次编写了类似的初始化例程之后&#xff0c;我想出了一个小实用程序类&#xff0c;它避免…

java逆向_Java逆向基础之异常

异常由之前月份处理修改的例子//清单1IncorrectMonthException.javapublic class IncorrectMonthException extends Exception {private int index;public IncorrectMonthException(int index) {this.index index;}public int getIndex() {return index;}}//清单2Month2.javac…

luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并

其实很水的一道题吧.... 题意是&#xff1a;每次给定一个串\(T\)以及\(l, r\)&#xff0c;询问有多少个字符串\(s\)满足&#xff0c;\(s\)是\(T\)的子串&#xff0c;但不是\(S[l .. r]\)的子串 统计\(T\)本质不同的串&#xff0c;建个后缀自动机 然后自然的可以想到&#xff0c…

centos-7.2 node.js免编译安装

cd /usr/local/wget https://npm.taobao.org/mirrors/node/v8.9.3/node-v8.9.3-linux-x64.tar.gz tar -zxvf node-v8.9.3-linux-x64.tar.gz //已编译可以直接运行./bin下面的命令rm -rf node-v8.9.3-linux-x64.tar.gz //解压完删包 // 建立全局快捷方式 ln -s 源命令文件 快…

团队计划会议

跟航哥想了挺多要做什么&#xff0c;要完成什么&#xff0c;以什么为主要功能 提出了几个想法&#xff0c;并做了投票 最后决定一起做一个跑腿软件 最初的任务量&#xff1a; 跟航哥商量两个人一人负责两个模块 航哥负责管理员和下单 我负责接单跟其他琐碎的小功能 呐&#xff…

在基于Spring MVC的应用程序中配置favicon.ico

Favicon是与您的网站相关的图标&#xff08;favicon.ico&#xff09;。 并非每个网站都在使用favicon。 但是大多数浏览器并不关心它&#xff0c;反正他们都要求它。 当图标图标不在适当位置时&#xff0c;服务器将返回不必要的404 Not Found错误。 在典型的Spring MVC应用程序…

vue.js java php_准吗?Java程序员喜欢AngularJS,PHP程序员喜欢Vue.js!

编程语言与框架或者库之间有联系是很正常的事情&#xff0c;如果我们告诉你&#xff0c;使用某一种编程语言或技术的开发人员可能更喜欢某个框架&#xff0c;你会作何反应呢&#xff1f;Stack Overflow根据网站内最常访问的标签将开发人员分为多个组&#xff0c;并检查了每组每…

BAJT高级Java面试题

答对这些面试题&#xff0c;PASS 掉 80 % 的竞争者 hashcode相等两个类一定相等吗?equals呢?相反呢? 介绍一下集合框架? hashmap hastable 底层实现什么区别?hashtable和concurrenthashtable呢? hashmap和treemap什么区别?低层数据结构是什么? 线程池用过吗都有什么…

结构化日志:出错时你最想要的好朋友

目录 介绍什么是日志&#xff1f;Grab中日志的状况为什么改变&#xff1f;结构化日志支持不同格式的多写开发中类似生产环境的日志因果顺序但为什么要结构化记日志&#xff1f;原文&#xff1a;Structured Logging: The Best Friend You’ll Want When Things Go Wrong 介绍 在…

在vue项目中添加特殊字体

这里的特殊字体&#xff0c;指的是一般用户电脑未安装到本地的字体&#xff0c;要引入这样的字体&#xff0c;首先需要把字体文件下载下来。 就像上图这样的&#xff0c;ttf格式的&#xff0c;然后在项目里添加它。 然后我们在font.css里用font-face规则引入这个字体文件并命名…

使用Spring WS创建合同优先的Web服务

1引言 本文介绍了如何使用来实现和测试SOAP Web服务 Spring Web Services项目 。 本示例使用JAXB2进行&#xff08;取消&#xff09;编组。 为了开发服务&#xff0c;我将使用合同优先的方法&#xff0c;该方法首先定义服务合同&#xff0c;然后基于该合同实施服务。 本文分为…

java中的常用日期类_Java中的常用日期类说明

日期类常用的有三个&#xff0c;Date类&#xff0c;Calendar(日历)类和日期格式转换类(DateFormat)Date类中的大部分的方法都已经过时&#xff0c;一般只会用到构造方法取得系统当前的时间。public class DateDemo {public static void main(String[] args) {Date date new Da…

转载 Net多线程编程—System.Threading.Tasks.Parallel

.Net多线程编程—System.Threading.Tasks.Parallel System.Threading.Tasks.Parallel类提供了Parallel.Invoke&#xff0c;Parallel.For&#xff0c;Parallel.ForEach这三个静态方法。 1 Parallel.Invoke 尽可能并行执行所提供的每个操作&#xff0c;除非用户取消了操作。 方法…

三方面搞定http协议之“状态码”

当我们向服务器请求数据的时候&#xff0c;服务器会给我们一个反馈&#xff0c;告诉我们对待我们的请求&#xff0c;服务器处理得怎么样了&#xff0c;而这个反馈&#xff0c;是通过数字来传达的&#xff0c;这个数字就叫状态码。 状态码分为以下几种&#xff1a; 1xx&#xf…

哪个更好的选择:克隆或复制构造函数?

这就是我开始撰写本文的方式。 我已经读过很多次这样的声明&#xff1a; “当对象引用可变的最终字段时&#xff0c;克隆变得很困难。” 每次我在Google上搜索它时&#xff0c;都要了解它的确切含义&#xff0c;并且在此过程中也忘了它。 因此以为我会在此撰写博客&#xff0c;…

Fiddler教程--简介

1、开发环境host配置自己修改系统的host来回挺麻烦的 2、前后的接口调试 3、线上bugfix 4、性能分析和优化 5.等等... 工作原理 一个代理服务器地址改为 127.0.0.1:8888流模式边走边返回缓冲模式http请求完成所有的数据之后&#xff0c;才返回 界面功能介绍 1.工具栏 从下图红色…