如何使用 JPA 实现分页查询并返回 VO 对象

JPA分页踩坑指南

1.原生sql查询返回vo类包含主键id,无法自动映射,需要用到投影ResultTransformer,所以我定义了一个投影工具类

JpaCommonService

2.异步调用原生查询方法的时候,需要用

NativeQuery<?> query = entityManager.createNativeQuery(sql).unwrap(NativeQuery.class);,不能用NativeQueryImpl<?> query = entityManager.createNativeQuery(sql).unwrap(NativeQueryImpl.class);

3.用jpql查询entityManager.createQuery()分页查询,如分页会涉及到子查询是不支持的,只能用原生sql, 不支持select count(1) from (select * from User) t


以下是分页返回vo的具体实现

分页返回vo

下面是用到的一些类和方法

分页参数
package com.example.springbootjpadruid.domain.common;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BasePage {private int page = 1;private int size = 10;/*** 是否需要分页*/private boolean needPage = false;public int getPage() {if (page < 1) {return 0;} else {return page - 1;}}public int getOffset() {return getPage() * size;}
}
分页返回结果
package com.example.springbootjpadruid.domain.common;import cn.hutool.core.bean.BeanUtil;
import lombok.Data;
import org.springframework.data.domain.Page;import java.util.List;@Data
public class PageVO<T> {private int page = 1;private int size = 10;private long total;private int totalPage;private List<T> list;public static <S, T> PageVO<T> of(Page<S> page, Class<T> clazz) {PageVO<T> pageVO = new PageVO<>();pageVO.setPage(page.getNumber() + 1);pageVO.setSize(page.getSize());pageVO.setTotal(page.getTotalElements());List<S> content = page.getContent();pageVO.setList(BeanUtil.copyToList(content, clazz));pageVO.setTotalPage(page.getTotalPages());return pageVO;}public static <T> PageVO<T> of(Page<T> page) {PageVO<T> pageVO = new PageVO<>();pageVO.setPage(page.getNumber() + 1);pageVO.setSize(page.getSize());pageVO.setTotal(page.getTotalElements());pageVO.setList(page.getContent());pageVO.setTotalPage(page.getTotalPages());return pageVO;}
}
原生sql查询通用方法
package com.example.springbootjpadruid.service;import com.alibaba.fastjson.JSON;
import com.example.springbootjpadruid.domain.common.BasePage;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.ResultTransformer;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.stereotype.Service;import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;/*** @author Jonny* @description 原生sql查询,自动将下划线转驼峰*/
@Service
public class JpaCommonService {private static final SimpleTypeConverter CONVERTER = new SimpleTypeConverter();@PersistenceContextprivate EntityManager entityManager;/*** 查询数据集合** @param sql    查询sql,参数用:name格式* @param params 查询参数map格式,key对应参数中的:name* @param clazz  实体类型,为空则直接转换为map格式* @return list*/@SuppressWarnings("unchecked")public <P extends BasePage> List<?> queryList(String sql, P params, Class<?> clazz) {String jsonParam = JSON.toJSONString(params);Map<String, Object> mapParams = JSON.parseObject(jsonParam, Map.class);// 提取 SQL 中的参数并过滤无效参数Set<String> sqlParams = extractSqlParams(sql);Map<String, Object> filteredParams = filterParams(sqlParams, mapParams);return queryList(sql, mapParams, filteredParams, clazz);}/*** 总记录数** @param sql    查询sql,参数用:name格式* @param params 查询参数map格式,key对应参数中的:name* @return list*/@SuppressWarnings("unchecked")public <P extends BasePage> long count(String sql, P params) {String jsonParam = JSON.toJSONString(params);Map<String, Object> mapParams = JSON.parseObject(jsonParam, Map.class);// 提取 SQL 中的参数并过滤无效参数Set<String> sqlParams = extractSqlParams(sql);Map<String, Object> filteredParams = filterParams(sqlParams, mapParams);return count(sql, mapParams, filteredParams, Long.class);}/*** 查询数据集合** @param sql            查询sql,参数用:name格式* @param params         查询参数map格式,key对应参数中的:name* @param filteredParams sql中使用的参数* @param clazz          实体类型,为空则直接转换为map格式* @return list*/@SuppressWarnings("all")public List<?> queryList(String sql, Map<String, Object> params, Map<String, Object> extractSqlParams, Class<?> clazz) {try {NativeQuery<?> query = entityManager.createNativeQuery(sql).unwrap(NativeQuery.class);if (Objects.nonNull(extractSqlParams)) {extractSqlParams.forEach((k, v) -> query.setParameter(k, v));}needPage(params, query);if (Objects.isNull(clazz)) {query.unwrap(NativeQuery.class).setResultTransformer(new AliasToEntityMapResultTransformer());return query.getResultList();} else {query.unwrap(NativeQuery.class).setResultTransformer(new AliasToBeanResultTransformer(clazz));return query.getResultList();}} catch (Exception e) {throw new RuntimeException("查询或转换数据时出错", e);}}@SuppressWarnings("all")public long count(String countSql, Map<String, Object> params, Map<String, Object> filteredParams, Class<?> clazz) {long total;try {Query countQuery = entityManager.createNativeQuery(countSql);if (Objects.nonNull(filteredParams)) {filteredParams.forEach(countQuery::setParameter);}total = Long.parseLong(countQuery.getSingleResult().toString());} catch (Exception e) {throw new RuntimeException("查询记录数出错", e);}return total;}/*** 判断是否需要分页* 当需要分页时,需要设置setNeedPage(true),并设置offset和size** @param params 查询参数* @param query  查询对象*/private void needPage(Map<String, Object> params, Query query) {if (Boolean.TRUE.equals(params.get("needPage"))) {int offset = (int) params.get("offset");int size = (int) params.get("size");query.setFirstResult(offset);query.setMaxResults(size);}}/*** 从 SQL 中提取所有命名参数** @param sql SQL 查询* @return 参数名集合*/private static Set<String> extractSqlParams(String sql) {Pattern pattern = Pattern.compile(":(\\w+)");Matcher matcher = pattern.matcher(sql);Set<String> params = new HashSet<>();while (matcher.find()) {params.add(matcher.group(1));}return params;}/*** 根据 SQL 中的参数过滤多余的 Map 参数** @param sqlParams SQL 中使用的参数* @param params    原始参数* @return 过滤后的参数*/private static Map<String, Object> filterParams(Set<String> sqlParams, Map<String, Object> params) {if (params == null || params.isEmpty()) {return Collections.emptyMap();}return params.entrySet().stream().filter(entry -> sqlParams.contains(entry.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));}private static class AliasToEntityMapResultTransformer implements ResultTransformer {@Overridepublic Object transformTuple(Object[] tuple, String[] aliases) {Map<String, Object> result = new HashMap<>();for (int i = 0; i < aliases.length; i++) {if (aliases[i] != null) {result.put(aliases[i].toLowerCase(), tuple[i]);}}return result;}@Overridepublic List transformList(List collection) {return collection;}}private static class AliasToBeanResultTransformer implements ResultTransformer {private final Class<?> resultClass;public AliasToBeanResultTransformer(Class<?> resultClass) {this.resultClass = resultClass;}/*** 转换结果集 自定将下划线转驼峰** @param tuple   结果集* @param aliases 字段名* @return 实体对象*/@Overridepublic Object transformTuple(Object[] tuple, String[] aliases) {try {Object result = resultClass.getDeclaredConstructor().newInstance();PropertyDescriptor[] props = Introspector.getBeanInfo(resultClass).getPropertyDescriptors();Map<String, Method> writeMethodMap = Arrays.stream(props).filter(p -> Objects.nonNull(p.getWriteMethod())).collect(Collectors.toMap(p -> p.getName().toLowerCase(), PropertyDescriptor::getWriteMethod));for (int i = 0; i < aliases.length; i++) {if (aliases[i] == null) {continue;}String fieldName = aliases[i].toLowerCase().replace("_", "");Method writeMethod = writeMethodMap.get(fieldName);if (writeMethod != null) {Object value = CONVERTER.convertIfNecessary(tuple[i], writeMethod.getParameterTypes()[0]);writeMethod.invoke(result, value);}}return result;} catch (Exception e) {throw new RuntimeException("实体映射错误: " + resultClass.getName(), e);}}@Overridepublic List transformList(List collection) {return collection;}}
}
使用方法
package com.example.springbootjpadruid.service.impl;import com.example.springbootjpadruid.domain.common.PageVO;
import com.example.springbootjpadruid.domain.entity.primary.User;
import com.example.springbootjpadruid.domain.query.UserQuery;
import com.example.springbootjpadruid.domain.vo.UserVO;
import com.example.springbootjpadruid.repository.primary.UserRepository;
import com.example.springbootjpadruid.service.JpaCommonService;
import com.example.springbootjpadruid.service.UserService;
import com.github.wenhao.jpa.PredicateBuilder;
import com.github.wenhao.jpa.Specifications;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.ResultTransformer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.List;@Service
public class UserServiceImpl implements UserService {@Resourceprivate UserRepository repository;@PersistenceContextprivate EntityManager entityManager;@Resourceprivate JpaCommonService jpaCommonService;@Overridepublic String init(List<User> users) {repository.saveAll(users);return "ok";}@Overridepublic User getUser() {PredicateBuilder<User> where = Specifications.and();where.eq("username", "zhangsan");where.eq("age", 20);return repository.findOne(where.build()).orElse(null);}/*** 分页1** @param param* @return*/@Overridepublic PageVO<UserVO> listPage(UserQuery param) {PageRequest pageable = PageRequest.of(param.getPage(), param.getSize());Page<User> page = repository.findAll(pageable);return PageVO.of(page, UserVO.class);}/*** 分页2** @param param 查询参数* @return 分页数据*/@SuppressWarnings("all")@Overridepublic PageVO<UserVO> nativeListPage(UserQuery param) {StringBuffer sql = new StringBuffer("select id as id, username as username, age as age, address as address from sys_user where 1 = 1");buildWhere(param, sql);StringBuffer countSql = new StringBuffer("select count(1) from sys_user where 1 = 1");buildWhere(param, countSql);PageRequest pageable = PageRequest.of(param.getPage(), param.getSize());NativeQuery query = entityManager.createNativeQuery(sql.toString()).unwrap(NativeQuery.class);query.setFirstResult(param.getOffset());query.setMaxResults(param.getSize());// 主键id无法直接映射,需要手动设置// query.setResultTransformer(Transformers.aliasToBean(UserVO.class));// 由于返回数据包含主键id,无法直接映射,只能借助投影手动映射query.setResultTransformer(new ResultTransformer() {@Overridepublic Object transformTuple(Object[] objects, String[] strings) {UserVO userVO = new UserVO();userVO.setId(Long.parseLong(objects[0].toString()));userVO.setUsername(objects[1].toString());userVO.setAge(Integer.parseInt(objects[2].toString()));userVO.setAddress(objects[3].toString());return userVO;}@Overridepublic List transformList(List list) {return list;}});buildParam(param, query);Query countQuery = entityManager.createNativeQuery(countSql.toString());buildParam(param, countQuery);long total = Long.parseLong(countQuery.getSingleResult().toString());PageImpl<UserVO> pageImpl = new PageImpl<>(query.getResultList(), pageable, total);return PageVO.of(pageImpl);}/*** 分页3* 原生sql自定转驼峰,带下划线不管大小自动转标准驼峰,全大写会转小写然后转标准驼峰* 解决分页方法2的原生sql如果没用as别名,无法自动转驼峰的问题** @param param* @return*/@SuppressWarnings("all")@Overridepublic PageVO<UserVO> listPageByJpaUtil(UserQuery param) {// StringBuffer sql = new StringBuffer("select id, username, age, address, user_role as USER_ROLE from sys_user where 1 = 1");StringBuffer sql = new StringBuffer("select * from sys_user where 1 = 1");buildWhere(param, sql);StringBuffer countSql = new StringBuffer("select count(1) from sys_user where 1 = 1");buildWhere(param, countSql);PageRequest pageable = PageRequest.of(param.getPage(), param.getSize());param.setNeedPage(Boolean.TRUE);List<UserVO> userList = (List<UserVO>) jpaCommonService.queryList(sql.toString(), param, UserVO.class);long total = jpaCommonService.count(countSql.toString(), param);PageImpl<UserVO> pageImpl = new PageImpl<>(userList, pageable, total);return PageVO.of(pageImpl);}private void buildParam(UserQuery param, Query query) {if (StringUtils.hasText(param.getUsername())) {query.setParameter("username", param.getUsername());}}private void buildWhere(UserQuery param, StringBuffer sql) {if (StringUtils.hasText(param.getUsername())) {sql.append(" and username = :username");}}}

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

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

相关文章

机器学习算法基础知识1:决策树

机器学习算法基础知识1&#xff1a;决策树 一、本文内容与前置知识点1. 本文内容2. 前置知识点 二、场景描述三、决策树的训练1. 决策树训练方式&#xff08;1&#xff09;分类原则-Gini&#xff08;2&#xff09;分类原则-entropy&#xff08;3&#xff09;加权系数-样本量&am…

_使用CLion的Vcpkg安装SDL2,添加至CMakelists时报错,编译报错

语言&#xff1a;C20 编译器&#xff1a;gcc 14.2 摘要&#xff1a;初次使用Vcpkg添加SDL2&#xff0c;出现CMakelists找不到错误、编译缺失main错误、运行失败错误。 CMakelists缺失错误&#xff1a; 使用CLion的Vcpkg安装SDL2时&#xff0c;按照指示把对应代码添加至CMakel…

Lumos学习王佩丰Excel第二十二讲:制作甘特图与动态甘特图

一、制作双向条形图 1. 分离坐标轴 2. 自定义坐标轴数字格式&#xff1a;加分号加正常数字 3. 修改图表背景 修改图片艺术效果&#xff1a;虚化图片 二、制作甘特图 1、甘特图定义 甘特图&#xff08;Gantt chart&#xff09;又称为横道图、条状图&#xff08;Bar chart&…

el-pagination 为什么只能展示 10 条数据(element-ui@2.15.13)

好的&#xff0c;我来帮你分析前端为什么只能展示 10 条数据&#xff0c;以及如何解决这个问题。 问题分析&#xff1a; pageSize 的值&#xff1a; 你的 el-pagination 组件中&#xff0c;pageSize 的值被设置为 10&#xff1a;<el-pagination:current-page"current…

【网络安全实验室】SQL注入实战详情

如果额头终将刻上皱纹&#xff0c;你只能做到&#xff0c;不让皱纹刻在你的心上 1.最简单的SQL注入 查看源代码&#xff0c;登录名为admin 最简单的SQL注入&#xff0c;登录名写入一个常规的注入语句&#xff1a; 密码随便填&#xff0c;验证码填正确的&#xff0c;点击登录…

ruoyi 多租户 开启后针对某一条sql不适用多租户; 若依多租户sql规则修改

文章参考&#xff1a;多租户功能 | Ruoyi-TDesign 忽略租户​ 1.如果需要指定单独 SQL 不开启过滤&#xff0c;可在对应的 Mapper 接口添加如下忽略注解&#xff1a; InterceptorIgnore(tenantLine "true", dataPermission "false") 此处注意事项 使…

一文理解条件竞争漏洞

视频教程在我主页简介或专栏里 目录&#xff1a; 理解竞争条件的基本概念 限制超越型竞争条件 使用 Burp Repeater 检测和利用限制超限竞态条件 方法论 1 — 预测潜在的冲突 2 — 线索 3 — 概念验证 如何防止竞态条件漏洞 理解竞争条件的基本概念 竞争条件(也就是条件竞…

一种基于动态部分重构的FPGA自修复控制器

1.FPGA动态部分重构技术 动态部分重构技术指在FPGA运行时&#xff0c;通过加载部分位流文件来修改FPGA可重构区域中的逻辑设计&#xff0c;修改过程中其余逻辑功能不受影响整个系统也能够持续运行。 下图为FPGA动态部分重构的基本原理图。通过下载A1.bit、A2.bit、A3.bit 或A4.…

计算机网络体系结构基础知识

一、计算机网络的两个目标&#xff1a; ①两台计算机之间通信 ②两台计算机之间的资源共享 二、计算机网络概述 1.定义&#xff1a;利用通信线路将地理上分散的、具有独立功能的计算机系统和通信设备按不同 的形式连接起来&#xff0c;以功能完善的网络软件及协…

云计算学习架构篇之HTTP协议、Nginx常用模块与Nginx服务实战

一.HTTP协议讲解 1.1rsync服务重构 bash 部署服务端: 1.安装服务 [rootbackup ~]# yum -y install rsync 2.配置服务 [rootbackup ~]# vim /etc/rsyncd.conf uid rsync gid rsync port 873 fake super yes use chroot no max connections 200 timeout 600 ignore erro…

合合信息亮相CSIG AI可信论坛,全面拆解AI视觉内容安全的“终极防线”

合合信息亮相CSIG AI可信论坛&#xff0c;全面拆解视觉内容安全的“终极防线”&#xff01; &#x1f42f; AI伪造泛滥&#xff0c;我们还能相信“眼见为实”吗&#xff1f; 近期&#xff0c;由中国图象图形学学会主办的CSIG青年科学家会议 AI可信论坛在杭州成功举办。本次论…

AI 智能助手对话系统

一个基于 React 和 Tailwind CSS 构建的现代化 AI 对话系统&#xff0c;提供流畅的用户体验和丰富的交互功能。 项目链接&#xff1a;即将开放… 功能特点 &#x1f916; 智能对话&#xff1a;支持与 AI 助手实时对话&#xff0c;流式输出回答&#x1f4c1; 文件处理&#xff…

经验证:将数据从索尼传输到Android的 4 种方法

概括 像Android Galaxy S20 这样的新型Android智能手机很酷&#xff0c;但除了将数据从索尼传输到Android之外。众所周知&#xff0c;旧的索尼手机上存储着大量的文件&#xff0c;因此将数据从旧的索尼手机传输到新的Android手机非常重要。为了解决这个问题&#xff0c;我们做…

IDEA 搭建 SpringBoot 项目之配置 Maven

目录 1?配置 Maven 1.1?打开 settings.xml 文件1.2?配置本地仓库路径1.3?配置中央仓库路径1.4?配置 JDK 版本1.5?重新下载项目依赖 2?配置 idea 2.1?在启动页打开设置2.2?配置 Java Compiler2.3?配置 File Encodings2.4?配置 Maven2.5?配置 Auto Import2.6?配置 C…

走方格(蓝桥杯2020年试题H)

【问题描述】在平面上有一些二维点阵。这些点的编号就像二维数组的编号一样&#xff0c;从上到下依次为第1~n行&#xff0c;从左到右依次为第1~m列&#xff0c;每个点可以用行号和列号表示。 现在有个人站在第1行第1列&#xff0c;他要走到第n行第m列&#xff0c;只能向右或者向…

python opencv的orb特征检测(Oriented FAST and Rotated BRIEF)

官方文档&#xff1a;https://docs.opencv.org/4.10.0/d1/d89/tutorial_py_orb.html SIFT/SURF/ORB对比 https://www.bilibili.com/video/BV1Yw411S7hH?spm_id_from333.788.player.switch&vd_source26bb43d70f463acac2b0cce092be2eaa&p80 ORB代码 import numpy a…

全面解析 Node-RED:功能、Docker 部署与实战示例

言简意赅的讲解Node-RED解决的痛点 Node-RED 是一个基于流的编程工具&#xff0c;专为物联网&#xff08;IoT&#xff09;应用而设计。它通过可视化的编程界面&#xff0c;使开发者能够轻松地连接各种硬件设备、API 以及在线服务&#xff0c;构建复杂的应用流程。本文将详细介…

使用 CSS 的 `::selection` 伪元素来改变 HTML 文本选中时的背景颜色

定义 ::selection 伪元素&#xff1a; 在你的 CSS 文件中&#xff0c;添加 ::selection 伪元素&#xff0c;并设置 background-color 属性来改变选中文本的背景颜色。 示例代码&#xff1a; ::selection {background-color: yellow; /* 你可以根据需要更改颜色 */color: black…

电商项目-数据同步解决方案(四)商品下架同步更新ES索引库数据

商品下架索引库删除数据 一、 需求分析和业务逻辑 商品下架后将商品从索引库中移除。 主要应用技术有&#xff1a; 消息队列-RabbitMQ &#xff0c;分布式搜索引擎-ElasticSearch&#xff0c;Eureka&#xff0c;Canal&#xff0c;Feign远程调用 &#xff08;1&#xff09;在…

HTML5 标签输入框(Tag Input)详解

HTML5 标签输入框&#xff08;Tag Input&#xff09;详解 标签输入框&#xff08;Tag Input&#xff09;是一种用户界面元素&#xff0c;允许用户输入多个标签或关键词&#xff0c;通常用于表单、搜索框或内容分类等场景。以下是实现标签输入框的详细讲解。 1. 任务概述 标…