因为业务需要一个分页查询的列表,但是对数据内容有去重的需求,简单了解了JPA的@Query注解使用方式后就直接用了自定义sql来做,测试通过后上线测了两笔也没问题就没太在意。但是隔了一天运营就反应说功能报错了,而且我去看日志发现报错很奇怪,是sql语法错误,如果是sql语法错误那么是怎么测试通过的呢?报错如下:
Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*) from test a where a.id in (select max(b.id) from test' at line 1
但是我写的sql在结果集那里是没有括号的
select a.* from test a where a.id in (select max(b.id) from test b group by name) order by a.id desc;
这个括号是如何生成的呢,经过反复的测试样本测试之后发现,这个是jpa在做分页需要查询总数量的时候自动加了一个sql
select count(a.*) from test a where a.id in (select max(b.id) from test b group by name) order by a.id desc;
这个语法在高版本的mysql中是不支持的,只能写成
select count(*) from test a where a.id in (select max(b.id) from test b group by name) order by a.id desc;
那么一开始又是怎么测试通过的呢,为什么有的查询(pageSize>count)jpa不会加上count(pageSize<count)而有的又会自动count?
debug到jpa源码之后,才知道jpa在执行分页查询的时候有对记录总数的获取方式做优化,具体实现在org.springframework.data.repository.support.PageableExecutionUtils的getPage方法,jpa就是通过这个方法将结果集封装成page,而其中在计算total的时候会判断当前页偏移量,当前pageSize是否大于查询条件的pageSize,如果能直接计算出来total就不会调用count去数据库查询记录总数
/**
* Constructs a {@link Page} based on the given {@code content}, {@link Pageable} and {@link Supplier} applying
* optimizations. The construction of {@link Page} omits a count query if the total can be determined based on the
* result size and {@link Pageable}.
*
* @param content must not be {@literal null}.
* @param pageable must not be {@literal null}.
* @param totalSupplier must not be {@literal null}.
* @return the {@link Page}.
*/
public static <T> Page<T> getPage(List<T> content, Pageable pageable, LongSupplier totalSupplier) {
Assert.notNull(content, "Content must not be null!");
Assert.notNull(pageable, "Pageable must not be null!");
Assert.notNull(totalSupplier, "TotalSupplier must not be null!");
if (pageable.isUnpaged() || pageable.getOffset() == 0) {
if (pageable.isUnpaged() || pageable.getPageSize() > content.size()) {
return new PageImpl<>(content, pageable, content.size());
}
return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
}
if (content.size() != 0 && pageable.getPageSize() > content.size()) {
return new PageImpl<>(content, pageable, pageable.getOffset() + content.size());
}
return new PageImpl<>(content, pageable, totalSupplier.getAsLong());
}
jpa生成count语句的逻辑也非常简单,就是做了一个正则来替换查询语句,对于我这个查询,count语句就变成了上面那个样子,jpa也考虑到这样生成的sql可能是有问题的,所以@Query注解提供了countQuery属性,查询语句比较复杂的时候可以自己写一个count语句给jpa使用。
网友评论