1.错误案例重现
首先来看博主想做什么:博主想group一张表,且对分组后的条目进行分页
Page<Specification> page = repository.findAll(MatrixModel.createSpecification(model), MatrixModel.createSort(pageNum, pageSize, model.getSort()));
ok,主要还是用的repository的finalAll方法,鉴于本人是java鉴定的OOP拥护者,所以很少写sql,那么我其中的一个方法MatrixModel.createSpecification(model),其实就是创建了一堆spec,后面的createSort不用看就知道了创建了pageAble对象,符合findAll方法的参数需求。
楼主在确认参数没有任何问题的情况下,发现了这样一个现象:
这是第一页的数据,发现分组之后居然有1662个Elements,这显然是不对的。
Then,当我把单页的条目数扩展到555之后(至于为什么是这个数,看到后面就知道了)
OK当页条目数是554,总条目数也是554,相当于我一页取完了所有的分组之后的对象。
那么在任何调用都没有问题的情况下,为什么会出现total计算的偏差呢?下面是分析过程。
2.分析sql
博主的当页数据是没有问题的,所以楼主重点想看JPA中的count语句。我按照上面顺序复现了并且打印了hql
顺序如下
2.1 单页数目为10
Hibernate: select specificat0_.id as id1_27_, specificat0_.create_time as create_t2_27_, specificat0_.update_time as update_t3_27_, specificat0_.head_img as head_img4_27_, specificat0_.intro as intro5_27_, specificat0_.name as name6_27_, specificat0_.specifications_id as specific7_27_, specificat0_.specifications_name as specific8_27_, specificat0_.status as status9_27_, specificat0_.weight as weight10_27_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id order by specificat0_.weight desc limit ?
Hibernate: select count(specificat0_.id) as col_0_0_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id
2.2 单页数目为555(刚好大于分组后所有元素的总和)
Hibernate: select specificat0_.id as id1_27_, specificat0_.create_time as create_t2_27_, specificat0_.update_time as update_t3_27_, specificat0_.head_img as head_img4_27_, specificat0_.intro as intro5_27_, specificat0_.name as name6_27_, specificat0_.specifications_id as specific7_27_, specificat0_.specifications_name as specific8_27_, specificat0_.status as status9_27_, specificat0_.weight as weight10_27_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id order by specificat0_.weight desc limit ?
2.2 比较结果
敲他大爷,为什么第一种结果回去多查询一次Hibernate: select count(specificat0_.id) as col_0_0_ from _specification specificat0_ where specificat0_.status=0 group by specificat0_.specifications_id
而且这样的查询是sql中典型的group + count(*)的错误查询
如图所示,这样会查询出每个group之后的条目数。而我们需要的是总的group数目
所以正确查询应该嵌套查询:
如图所示554 才应该是group之后的条目数。
所以我有疑问:难道TM的SPRING JPA 没有对group的判定?(敲他吗,还真没有)
3.不说了,看源码
先去找到findAll方法的实现
继续前进
ok,来到spring JPA实现,可以看到那句return是一个重要的action,已知我们的pageale不为null,那么继续
进入方法readPage
getPage一定干了一些坏事,进去瞅瞅(还有个匿名内部类的实现哟,等会比较重要)
仔细检查了逻辑,没有任何问题(主要是对查询出来的当页数量和设定的页面数量做比较,这是常规操作,spring也不会犯这样的问题),最后的猫腻在哪里呢?看到那个get方法没有,结果前面的情况二,总数的计算在这里有问题---》这里回到上面那个匿名内部内的实现,total是采用的:SimpleJpaRepository.executeCountQuery(SimpleJpaRepository.this.getCountQuery(spec, domainClass)).longValue();
咋们去看看这个executeCountQuery方法:
破案了,曹,totals是我们需要的总条目数554,但是spring把所有的数目累加了,相当于分组之前的总数了,不知道这算不算issue,但是目前是不符合我们的要求的。
4.解决方案
我的构想是能拿到那个条目数,并且能完美使用specRepo(OOP),所以不能使用sql来实现。
package com.swb.dreamweaver.dao; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.util.Assert; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.Collections; import java.util.List; /** * 特殊情况下数量统计异常处理(分页+分组,切仅用于此种情况) * 此类将返回正确的count数量 * Author: Liao Ke */ public abstract class JPASpecialCountErrorHandler { /** * 获取EntityManager实现 * @return */ protected abstract EntityManager getEm(); /** * 获取分页数据 * @param content 当前页面条目数 * @param pageable 分页信息 * @param count 真实总数 * @return */ protected <S> PageImpl getPage(List<S> content, Pageable pageable,int count){ return new PageImpl(content, pageable, (long)count); } /** * 获取正确的count计数 * @param spec * @param domainClass * @param <S> * @return */ protected <S> int getCount(org.springframework.data.jpa.domain.Specification<S> spec, Class<S> domainClass) { return getCountQuery(spec, domainClass).getResultList().size(); } /** * 来自源码,获取TypedQuery * org\springframework\data\jpa\repository\support\SimpleJpaRepository.class * @param spec * @param domainClass * @param <S> * @return */ protected <S> TypedQuery<Long> getCountQuery(org.springframework.data.jpa.domain.Specification<S> spec, Class<S> domainClass) { CriteriaBuilder builder = getEm().getCriteriaBuilder(); CriteriaQuery<Long> query = builder.createQuery(Long.class); Root<S> root = this.applySpecificationToCriteria(spec, domainClass, (CriteriaQuery<S>) query); if (query.isDistinct()) { query.select(builder.countDistinct(root)); } else { query.select(builder.count(root)); } query.orderBy(Collections.emptyList()); return getEm().createQuery(query); } /** * 来自源码,翻译spec * org\springframework\data\jpa\repository\support\SimpleJpaRepository.class * @param spec * @param domainClass * @param query * @param <S> * @return */ private <S> Root<S> applySpecificationToCriteria(org.springframework.data.jpa.domain.Specification<S> spec, Class<S> domainClass, CriteriaQuery<S> query) { Assert.notNull(domainClass, "Domain class must not be null!"); Assert.notNull(query, "CriteriaQuery must not be null!"); Root<S> root = query.from(domainClass); if (spec == null) { return root; } else { CriteriaBuilder builder = getEm().getCriteriaBuilder(); Predicate predicate = spec.toPredicate(root, query, builder); if (predicate != null) { query.where(predicate); } return root; } } }
写了个抽象类,然后仿写了源码。直接使用使用list.size()可以获取到我想要的分组后的group条目数(开心)。
最后附上使用的代码:
1.继承上面的抽象类
public class SpecificationService extends JPASpecialCountErrorHandler
2.注入em
@PersistenceContext private EntityManager entityManager;
3.重写了getEm方法
@Override protected EntityManager getEm() { return entityManager; }
4.简单的group判断及对pageView的重写
@Override public <S extends EntityCURD> Code.ViewPage<S> gets(MatrixModel model, Integer pageNum, Integer pageSize) { Page<Specification> page = repository.findAll(MatrixModel.createSpecification(model), MatrixModel.createSort(pageNum, pageSize, model.getSort())); //JPA ISSUE -- 分组+分页,总数显示不正确 if (model.getGroup() != null && model.getGroup().size() != 0) { return (Code.ViewPage<S>) code.JpaPageToViewPage(getPage(page.getContent(), MatrixModel.createSort(pageNum, pageSize, model.getSort()), getCount(MatrixModel.createSpecification(model), Specification.class))); } return (Code.ViewPage<S>) code.JpaPageToViewPage(page); }
OK 结果确认:
"last": false, "totalElements": 554, "totalPages": 51, "number": 0, "size": 11, "first": true, "numberOfElements": 11
没有问题
相关推荐
【标题】"spring boot+jpa+redis集群"的实现与应用 在现代互联网开发中,Spring Boot、JPA(Java Persistence API)以及Redis已经成为构建高效、可扩展的应用程序的常用技术栈。本项目结合这三个核心组件,旨在创建...
持久层框架:Spring boot Jpa 安全框架:Spring Security 缓存框架:Redis 日志打印:logback+log4jdbc 接口文档 swagger2 其他:fastjson,aop,MapStruct等。 页面框架:Vue 前端源码:eladmin-qt 后端源码:el...
本项目基于"Spring Boot+Maven+Spring Data JPA+apache Shiro+Easyui",这些技术栈的选择旨在简化开发过程,提供强大的功能,并确保系统的安全性和用户体验。 1. **Spring Boot**: Spring Boot是Spring框架的简化版...
【资源说明】 1、该资源包括项目的全部源码,下载可以直接使用! 2、本项目适合作为计算机、数学、电子信息等专业的课程设计、...基于LayUI+Spring Boot+MySQL+JPA+Shiro的科研信息管理系统源码+项目说明+数据库.zip
基于SpringBoot+Spring Data JPA+mybatis的仓库管理系统 基于SpringBoot+Spring Data JPA+mybatis的仓库管理系统 基于SpringBoot+Spring Data JPA+mybatis的仓库管理系统 基于SpringBoot+Spring Data JPA+mybatis的...
Spring Data JPA是Spring框架的一个扩展,它简化了对Java Persistence API (JPA) 的使用,使数据库操作更加便捷。通过Spring Data JPA,我们可以直接通过接口定义来实现对数据库的CRUD操作,无需编写大量的DAO层代码...
1、基于SpringBoot+Spring Data JPA+mybatis的仓库管理系统源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考...
综合上述信息,我们可以创建一个Spring Boot应用,使用Spring Data JPA进行数据持久化,Spring Security负责应用的安全管理,达梦数据库作为后端数据存储,FreeMarker处理前端展示,最后通过Assembly插件将整个项目...
这是整合SpringMVC+Spring+SpringDataJPA+Hibernate简单的实现登录的功能,用的是mysql数据库,这是一个web Project 如果你用的是JavaEE6那么你要注意bean-validator.jar和weld-osgi-bundle.jar与slf4j的jar包冲突。...
基于springboot+thymeleaf+spring data jpa+druid+bootstrap+layui等技术的JavaWeb电商项目(项目包含前后台,分为前台商城系统及后台管理系统。前台商城系统包含首页门户
本项目以"管理系统系列--基于spring boot脚手架项目,spring data jpa+Spring Boot2+bo"为主题,旨在深入探讨如何利用Spring Boot、Spring Data JPA以及BO(Business Object)模式来构建高效、易维护的企业级管理...
在本项目中,"spring boot3+jpa+lombok+mapstruct实现的restful api例子"是一个集成多种技术的示例,旨在展示如何高效地构建RESTful API服务。下面将详细介绍这些关键技术及其相互间的配合。 1. **Spring Boot 3**:...
项目描述 前台:商品查看(分类查看,...spring boot+mysql+springdata jpa+thymeleaf+bootstrap2.3.1+jquery+layui 注意事项1.没有实现真正的支付功能,点击支付直接就成功 2.功能都测试通过,图片上传,访问也都正常
该项目的架构搭建使用的是maven,后台是使用的是spring boot框架,数据库的CRUD使用的是注解的方式,权限管理使用的是shiro框架,前端使用的框架是jquery,bootstrap,highcharts4插件,主要有以下功能:用户管理、...
总结来说,"Spring Boot + JPA + SQL Server + Bootstrap"的组合为开发者提供了一个强大而便捷的开发环境,从后端数据处理到前端界面展示,都提供了完善的解决方案。这种整合方式在现代Web开发中广泛应用,帮助...
这个应用的核心技术包括Spring、Spring Boot、JPA(Java Persistence API)、Thymeleaf模板引擎、Bootstrap前端框架以及Mysql数据库。下面将详细解释这些技术及其在项目中的作用。 **Spring** 是一个开源的应用程序...
Spring Boot实战(连接MySQL数据库+使用Spring Data Jpa+配置拦截器及实现跨域访问+整合Redis使用Docker部署Spring Boot项目+使用AOP的正确姿势+整合Mybatis并完成CRUD操作+整合 thymeleaf+整合 MyBatis (XML 版))
SpringBoot + Data JPA + ...通过这个DEMO,开发者可以了解到如何将Spring Boot、Data JPA和Thymeleaf结合使用,快速构建一个具备用户管理功能的Web应用。同时,这也是学习和理解Spring全家桶中关键组件交互的好例子。
在"spring boot+jpa+jsp(web)"的组合中,JSP用于展现由Spring Boot和JPA处理后的数据。 整合这些技术,我们可以创建一个完整的Web应用流程:用户通过JSP页面发送请求,Spring Boot接收并路由请求,然后通过JPA和...
"SpringMVC+Spring Data JPA+Shiro+EasyUI 简单demo"是一个整合了上述四个技术的Web应用示例。这个项目可能包含了以下组件: 1. **配置文件**:如`applicationContext.xml`用于配置SpringMVC和Spring Data JPA,...