项目技术框架

请您打开世界上最好使的Java编译器IntelliJ IDEA,建一个SpringBoot项目,保证项目中包含以下依赖:
后端框架:SpringBoot + JUnit
数据库持久化:MySQL + MyBatis

测试数据

请您打开可爱的小海豚SQLyog,运行以下SQL建表:

/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 5.5.62
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;create table `order_jake` (`oid` int (11),`customer` varchar (30),`cost` int (11)
);
insert into `order_jake` (`oid`, `customer`, `cost`) values('1','Jake','1000');
insert into `order_jake` (`oid`, `customer`, `cost`) values('2','Jason','1600');
insert into `order_jake` (`oid`, `customer`, `cost`) values('3','Oliver','800');
insert into `order_jake` (`oid`, `customer`, `cost`) values('4','Jimmy','600');
insert into `order_jake` (`oid`, `customer`, `cost`) values('5','Hui','1200');
insert into `order_jake` (`oid`, `customer`, `cost`) values('6','Jake','600');
insert into `order_jake` (`oid`, `customer`, `cost`) values('7','Jason','200');
insert into `order_jake` (`oid`, `customer`, `cost`) values('8','Jake','100');
insert into `order_jake` (`oid`, `customer`, `cost`) values('9','Hui','500');
insert into `order_jake` (`oid`, `customer`, `cost`) values('10','Oliver','200');
insert into `order_jake` (`oid`, `customer`, `cost`) values('11','Jake','1100');
insert into `order_jake` (`oid`, `customer`, `cost`) values('12','Jake','120');
insert into `order_jake` (`oid`, `customer`, `cost`) values('13','Oliver','750');
insert into `order_jake` (`oid`, `customer`, `cost`) values('14','Jason','900');
insert into `order_jake` (`oid`, `customer`, `cost`) values('15','Jimmy','600');
insert into `order_jake` (`oid`, `customer`, `cost`) values('16','Hui','1200');
insert into `order_jake` (`oid`, `customer`, `cost`) values('17','Jason','500');
insert into `order_jake` (`oid`, `customer`, `cost`) values('18','Jake','1000');
insert into `order_jake` (`oid`, `customer`, `cost`) values('19','Jake','720');
insert into `order_jake` (`oid`, `customer`, `cost`) values('20','Jimmy','1100');
insert into `order_jake` (`oid`, `customer`, `cost`) values('21','Oliver','1800');
insert into `order_jake` (`oid`, `customer`, `cost`) values('22','Hui','1500');
insert into `order_jake` (`oid`, `customer`, `cost`) values('23','Jake','80');
insert into `order_jake` (`oid`, `customer`, `cost`) values('24','Hui','210');
insert into `order_jake` (`oid`, `customer`, `cost`) values('25','Jimmy','60');
insert into `order_jake` (`oid`, `customer`, `cost`) values('26','Jason','1400');
insert into `order_jake` (`oid`, `customer`, `cost`) values('27','Jason','450');
insert into `order_jake` (`oid`, `customer`, `cost`) values('28','Oliver','800');
insert into `order_jake` (`oid`, `customer`, `cost`) values('29','Jimmy','200');
insert into `order_jake` (`oid`, `customer`, `cost`) values('30','Jake','50');

在SQLyog中数据视图如下:

What is the Problem?

SQL和代码准备(可惜SQL是错的)

假设您是一个特例独行的程序员,偶尔想在无聊的编程工作中找找乐子,您可能会脑洞大开地使用having代替where做条件筛选,您可能会写出以下SQL:

-- 查出金额不少于800元的订单
SELECT *
FROMorder_jake
HAVING cost >= 800 ;

查询结果:

-- 查出姓名以字母J开头的客户的订单
SELECT *
FROMorder_jake
HAVING customer LIKE 'J%' ;

查询结果:

您十分满意,在没有GROUP BY的情况下,HAVING和WHERE的作用一样一样的呢。
于是您基于SpringBoot + MyBatis一溜烟写好POJO、service、mapper(java & xml)层代码,再把JUnit单元测试一写,感觉人生已经达到了巅峰,分分钟完成需求:
Order

@Data
public class Order {private Integer oid;private String customer;private Integer cost;
}

OrderService

@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;public List<Order> listOrdersByHavingCustomer(String customer) {PageHelper.startPage(1, ParamConsts.PAGE_SIZE);return orderMapper.listOrdersByHavingCustomer(customer);}public List<Order> listOrdersByHavingCost(Integer cost) {PageHelper.startPage(1, ParamConsts.PAGE_SIZE);return orderMapper.listOrdersByHavingCost(cost);}
}

OrderMapper.java

public interface OrderMapper {List<Order> listOrdersByHavingCustomer(String customer);List<Order> listOrdersByHavingCost(Integer cost);
}

OrderMapper.xml

<select id="listOrdersByHavingCustomer" resultType="com.jake.zyt.entity.Order" parameterType="string">SELECT*FROMorder_jakeHAVING customer LIKE CONCAT('%', #{customer}, '%')
</select>
<select id="listOrdersByHavingCost" resultType="com.jake.zyt.entity.Order">SELECT*FROMorder_jakeHAVING cost >= 800
</select>

OrderServiceTest

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTest {@Autowiredprivate OrderService orderService;@Testpublic void listOrdersByHavingCustomer() {List<Order> orders = orderService.listOrdersByHavingCustomer("J");assertThat(orders.size(), Matchers.lessThanOrEqualTo(ParamConsts.PAGE_SIZE));}@Testpublic void listOrdersByHavingCost() {List<Order> orders = orderService.listOrdersByHavingCost(800);assertThat(orders.size(), Matchers.lessThanOrEqualTo(ParamConsts.PAGE_SIZE));}
}

满是ERROR的控制台

理想很丰满,现实很骨感,运行结果无情报错,惨红的ERROR信息提示您,您这SQL不好使了:


把关键错误信息贴一下:

### SQL: SELECT count(0) FROM order_jake HAVING cost >= 800
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'cost' in 'having clause'
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'cost' in 'having clause'
### SQL: SELECT count(0) FROM order_jake HAVING customer LIKE CONCAT('%', ?, '%')
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'customer' in 'having clause'
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'customer' in 'having clause'

您肯定很纳闷,这在SQLyog里运行得飞起的SQL咋到了MyBatis里就不好使了呢?
您看了看报错:Unknown column xxx in ‘having clause’,这是什么鬼?
不明就里的您只好把控制台输出的SQL复制到SQLyog里面运行一下,此时SQLyog也给您来了和MyBatis个一模一样的无情报错:

您彻底懵圈了,但冥冥之中感觉到似乎有某种规律存在于SELECT和HAVING之中,您怀着试一试的心态,运行了这段SQL:

虽然这句SQL毫无意义,但是起码您看出了一点规律,HAVING后面的列必须存在SELECT后面,否则会报错:Unknown column xxx in ‘having clause’。
此时您利用这个规律,尝试着写出有意义的SQL:

这条SQL查出了订单总数,订单平均金额,订单总金额,但您仍然觉得此SQL毫无意义,因为您可以很清楚地看出,HAVING是在SELECT columns FROM table之后才做了筛选。由此,可以总结出只有HAVING关键字的SQL等效于以下的WHERE条件查询SQL:

咳咳,您的思路飘得有点远了,不过发散性思维让您发现了很多实用的规律。让我们回归之前IntellJ IDEA的控制台里那些飘红的ERROR。您肯定灰常灰常纳闷,why the hell控制台里会蹦出一个:
SELECT COUNT(0) FROM xxx ?

罪魁祸首:PageHelper

您对Mapper层的方法单独做了单元测试,没错啊,那个没有GROUP BY只有HAVING的SQL非常好使,和SQLyog里运行得结果一毛一样。好了,此时该把目光聚集到最大的犯罪嫌疑代码来了:PageHelper.startPage(int startNum, int pageSize)
把这行代码注释掉,再跑一波Service层单元测试,咦,那个没有GROUP BY只有HAVING的SQL又好使了,OK,PageHelper的罪名实锤了。就是它,让MyBatis里的SQL在执行前多执行了一句计数查询,因为要查出总数,才好分页嘛。所以,您终于清楚控制台为啥蹦出诸如:

SELECT count(0) FROM order_jake HAVING cost >= 800

这样SQLyog里面都跑不通的SQL了吧。
这里咱就不对PageHelper的原理多做介绍了,有兴趣的小可爱可以去人家的官网瞅瞅看这玩意儿的原理。传送门:MyBatis PageHelper的GitHub源码仓库
咱们只需要明确一点,只要PageHelper.startPage搁你的查询逻辑前面一放,您的SQL查询运行之前就都要先把查询总数给查出来,再去执行您具体的SQL查询,然后再根据查询结果分页,放到List啊,Set啊等集合里边去,注意了,此时集合的Size最多就只有分页查询的每页数量PageSize个了,顺便提一嘴,对这个关系做断言测试的时候可以这么写:

assertThat(list.size(), Matchers.lessThanOrEqualTo(ParamConsts.PAGE_SIZE));

写正确且意义的SQL

为了新奇,写出只有HAVING没有GROUP BY这样六亲不认的SQL之后,您会想,emm…咱怎样才能写出又有HAVING又靠谱的SQL呢,很简单,把GROUP BY加上呗。
来,作为SQL老司机,您很快写出了几个GROUP BY + HAVING,先分组再筛选,一气呵成,还顺道把查询他们总数的SQL给写出来了:

-- 2. 包含GROUP BY的查询
-- 查出每位顾客消费的金额,分组显示。
SELECT customer customer_name,SUM(cost) customer_cost
FROMorder_jake
GROUP BY customer ;
-- 查出以上查询结果的总条数
SELECT COUNT(*) query_count
FROM(SELECT customer customer_name,SUM(cost) customer_cost FROMorder_jake GROUP BY customer) order_count ;-- 查出每位顾客消费的金额,筛选出总消费额度不少于4000元的顾客,分组显示。
SELECT customer customer_name,SUM(cost) customer_cost
FROMorder_jake
GROUP BY customer
HAVING customer_cost >= 4000 ;
-- 查出以上查询结果的总条数
SELECT COUNT(*) query_count
FROM(SELECT customer customer_name,SUM(cost) customer_cost FROMorder_jake GROUP BY customer HAVING customer_cost >= 4000) order_count ;-- 查出每位顾客不少于800元的订单的消费金额,筛选出这些订单的总消费额不少于3500元的顾客,分组显示。
SELECT customer customer_name,SUM(cost) customer_cost
FROMorder_jake
WHERE cost >= 800
GROUP BY customer
HAVING customer_cost >= 3500 ;
-- 查出以上查询结果的总条数
SELECT COUNT(*) query_count
FROM(SELECT customer customer_name,SUM(cost) customer_cost FROMorder_jake WHERE cost >= 800 GROUP BY customer HAVING customer_cost >= 3500) order_count ;

您的小伙伴就纳了闷了,老铁,你查总数干啥总要先查出一个子表,然后在外面再套一层SELECT COUNT(*)呢?俺直接酱紫单刀直入多简单:

SELECT COUNT(*)
FROMorder_jake
GROUP BY customer ;

我说老铁,动动手,把SQL复制到SQLyog里面一试便知了,这查出来的是啥啊:

是啥啊哥,俺还真不知道是啥?
好好好,给你看个清楚点的:

啊,俺知道了,这意思是每个哥们下的订单数是多少?
诶,对了,小机灵鬼~

给正确的SQL做分页

咱给有GROUP BY又有HAVING的SQL们写写service,mapper,MyBatis语句,单元测试呗:
CustomerService

@Service
public class CustomerService {@Autowiredprivate CustomerMapper customerMapper;public List<CustomerVO> groupAndFilterCustomers(Integer cost) {PageHelper.startPage(1, ParamConsts.PAGE_SIZE);return customerMapper.groupAndFilterCustomers(cost);}
}

CustomerMapper.java

public interface CustomerMapper {List<CustomerVO> groupAndFilterCustomers(Integer cost);
}

CustomerMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.jake.zyt.mapper.CustomerMapper"><resultMap id="customerMap" type="com.jake.zyt.vo.CustomerVO"><result property="name" column="customer_name"/><result property="cost" column="customer_cost"/></resultMap><select id="groupAndFilterCustomers" resultMap="customerMap">SELECTcustomer customer_name,SUM(cost) customer_costFROMorder_jakeGROUP BY customerHAVING customer_cost >= #{cost}</select>
</mapper>

CustomerServiceTest

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomerServiceTest {@Autowiredprivate CustomerService customerService;@Testpublic void groupAndFilterCustomers() {PageHelper.startPage(1, ParamConsts.PAGE_SIZE);List<CustomerVO> customerVOS = customerService.groupAndFilterCustomers(1000);assertThat(customerVOS.size(), Matchers.lessThanOrEqualTo(ParamConsts.PAGE_SIZE));}
}

测试之前咱先把配置文件application.yml中service和mapper包下的Log记录级别改成Debug,这样才能在控制台中看到打印的SQL:

logging:level:com.jake.zyt.mapper: debugcom.jake.zyt.service: debug

好了,单元测试走您:

绿了绿了,通过了!喜大普奔!
把控制台的打印信息给您贴出来瞧瞧:

2019-07-01 19:54:32.577  INFO 816 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-07-01 19:54:32.832  INFO 816 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-07-01 19:54:32.923 DEBUG 816 --- [           main] c.j.z.m.C.groupAndFilterCustomers_COUNT  : ==>  Preparing: SELECT count(0) FROM (SELECT customer customer_name, SUM(cost) customer_cost FROM order_jake GROUP BY customer HAVING customer_cost >= ?) table_count
2019-07-01 19:54:32.972 DEBUG 816 --- [           main] c.j.z.m.C.groupAndFilterCustomers_COUNT  : ==> Parameters: 1000(Integer)
2019-07-01 19:54:33.014 DEBUG 816 --- [           main] c.j.z.m.C.groupAndFilterCustomers_COUNT  : <==      Total: 1
2019-07-01 19:54:33.017 DEBUG 816 --- [           main] c.j.z.m.C.groupAndFilterCustomers        : ==>  Preparing: SELECT customer customer_name, SUM(cost) customer_cost FROM order_jake GROUP BY customer HAVING customer_cost >= ? LIMIT ?
2019-07-01 19:54:33.018 DEBUG 816 --- [           main] c.j.z.m.C.groupAndFilterCustomers        : ==> Parameters: 1000(Integer), 10(Integer)
2019-07-01 19:54:33.022 DEBUG 816 --- [           main] c.j.z.m.C.groupAndFilterCustomers        : <==      Total: 5

其中两条SQL扔到SQLyog里面跑一跑:


好使,非常好使~

文末关于WHERE的思考

最后您肯定会想,HAVING的count计数竟如此麻烦,那么WHERE呢?PageHelper.startPage方法对普通的WHERE条件查询时的总数查询语句是怎样的呢?是不是和HAVING一样呢?再上代码-单元测试一条龙服务:
OrderService

public List<Order> listOrdersByCost(Integer cost) {PageHelper.startPage(1, ParamConsts.PAGE_SIZE);return orderMapper.listOrdersByCost(cost);
}

interface OrderMapper.java

List<Order> listOrdersByCost(Integer cost);

OrderMapper.xml

<select id="listOrdersByCost" resultType="com.jake.zyt.entity.Order" parameterType="integer">SELECT*FROMorder_jakeWHERE cost >= #{cost}
</select>

OrderServiceTest

@Test
public void listOrdersByCost() {List<Order> orders = orderService.listOrdersByCost(800);assertThat(orders.size(), Matchers.lessThanOrEqualTo(ParamConsts.PAGE_SIZE));
}

单元测试结果:

计数SQL如下:

SELECT count(0) FROM order_jake WHERE cost >= 800

对于只有WHERE没有HAVING的语句来说,计数方式就简单得多了,因为此时:

SELECT COUNT(*) FROM TABLE WHERE CONDITION

就是查询

SELECT COLS FROM TABLE WHERE CONDITION

的结果的总条数。
整个探索过程就这么结束了,虽然您很头铁走了很多弯路,但在XJB乱试的过程中收获了知识,岂不美哉?

都是MyBatis分页工具PageHelper惹的祸 —— 记一次加入GROUP BY分组查询成功,不分组则失败的奇怪现象Debug过程相关推荐

  1. (转)淘淘商城系列——MyBatis分页插件(PageHelper)的使用以及商品列表展示

    http://blog.csdn.net/yerenyuan_pku/article/details/72774381 上文我们实现了展示后台页面的功能,而本文我们实现的主要功能是展示商品列表,大家要 ...

  2. Mybatis分页功能 pagehelper插件

    Mybatis分页功能 pagehelper插件 创建数据数据 use ssm; create table student (id int auto_incrementprimary key,name ...

  3. 解决使用mybatis分页插件PageHelper的一个报错问题

    解决使用mybatis分页插件PageHelper的一个报错问题 参考文章: (1)解决使用mybatis分页插件PageHelper的一个报错问题 (2)https://www.cnblogs.co ...

  4. Mybatis分页插件PageHelper使用教程(图文详细版)

    Mybatis分页插件PageHelper使用教程(图文详细版) 1.配置 2.后台代码 controller类 html页面 html页面效果图 1.配置 小编的项目是springBoot项目,所以 ...

  5. MyBatis分页插件PageHelper使用练习

    转载自:http://git.oschina.net/free/Mybatis_PageHelper/blob/master/wikis/HowToUse.markdown 1.环境准备: 分页插件p ...

  6. MyBatis学习总结(17)——Mybatis分页插件PageHelper

    2019独角兽企业重金招聘Python工程师标准>>> 如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件. 分页插件支持任何复杂的单表.多表分页,部分特殊 ...

  7. Mybatis分页插件PageHelper简单使用

    转载自:https://www.cnblogs.com/ljdblog/p/6725094.html 引言 对于使用Mybatis时,最头痛的就是写分页,需要先写一个查询count的select语句, ...

  8. 【MyBatis】MyBatis分页插件PageHelper的使用

    转载自 https://www.cnblogs.com/shanheyongmu/p/5864047.html 好多天没写博客了,因为最近在实习,大部分时间在熟悉实习相关的东西,也没有怎么学习新的东西 ...

  9. MyBatis 分页插件 PageHelper:是如何拦截SQL进行分页

    目录 Springboot项目集成 分页插件参数介绍 如何选择配置这些参数 场景一 场景二 场景三 场景四 场景五 PageHelper的使用 PageHelper实现原理1: interceptor ...

最新文章

  1. 利用jQuery实现用户名片小动画
  2. Python培训基础教程都教哪些
  3. 周末了,分享个休闲软件!你懂得!
  4. webloigc12服务启动不了_一键启动知多少!
  5. arm-linux-gcc/ld/objcopy/objdump使用总结[zz]
  6. vue中简单的小插曲
  7. 如何重置 Docker 里的 gitlab root 用户密码
  8. 咨询的真相5:咨询业的“前世今生”
  9. 数据表 高水位 mysql_Oracle中的高水位(HWM)
  10. TypeScript查缺补漏
  11. 过程与结果——独立思考——认真详谈
  12. 直接打印RAW文件到打印机
  13. Vue CLI 预设Presets
  14. 计算机图文报告病理是什么,病理图文报告系统、病理图文工作站、病理报告软件...
  15. 品牌数字化升级,如何借力用户画像实现精准营销? - whale 帷幄
  16. Android的手势操作(Gesture)
  17. (8)打鸡儿教你Vue.js
  18. 人工智能改变教学方式
  19. 海康的摄像机设备接入到萤石云
  20. Error:Execution failed for task ':app:mergeDebugResources'

热门文章

  1. 打造DIY版Echo:树莓派+ Alexa 语音服务
  2. 经常吃不胖的人,可以通过什么方式增重?
  3. 充满活力的布兰德成功计划
  4. 转载同事在国美购物的遭遇
  5. 2021-01-24-攒车
  6. fundebug 和 腾讯MTA
  7. 网站部署到其他服务器但无权修改,web项目部署到服务器遇到的问题
  8. linux 重新安装firefox,centos7重新安装firefox
  9. python中display函数_Python-函数基础总结与内置函数
  10. [ZT]SUN非常有用之 Unix/Linux 单行脚本