惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
Security Latest
Security Latest
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
A
Arctic Wolf
T
Tor Project blog
T
Threatpost
NISL@THU
NISL@THU
I
InfoQ
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Apple Machine Learning Research
Apple Machine Learning Research
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
K
Kaspersky official blog
W
WeLiveSecurity
L
LINUX DO - 热门话题
小众软件
小众软件
Recorded Future
Recorded Future
B
Blog RSS Feed
H
Help Net Security
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
L
Lohrmann on Cybersecurity
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
Blog — PlanetScale
Blog — PlanetScale
雷峰网
雷峰网
爱范儿
爱范儿
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Hacker News: Front Page
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
N
News and Events Feed by Topic
云风的 BLOG
云风的 BLOG
Scott Helme
Scott Helme
V2EX - 技术
V2EX - 技术

小松鼠的博客

记录一次线上k8s工作节点无法创建容器的问题排查思路与解决办法 记一次线上GoLang项目OOM排查过程 从LastPass转向拥抱开源KeePass的心路历程 故障定位与 AI 结合前后端编码实践 FileBeat收集nginx-ingress-controller日志 K8s云原生环境下文件描述符占用过高查询思路 2024年最新关闭火绒安全工具的开机自启方法 Kubernetes任务调度实践-Go语言实现Job和CronJob对比分析 离线更新k8s环境下的trivy漏洞库方法 使用Go语言接入Choerodon实现基于OAuth2的统一身份认证登录 在Vue2中自定义Switch组件并实现父子组件双向数据绑定 关于docker jdk1.8镜像中的GB18030-2022标准支持及验证 Go框架gin中的session存储gin-contrib-sessions和go-session 关于修改node_module中的源码问题记录 docker-compose网络和内网服务IP冲突问题 慎用存储过程:一条语句引发的数据库存储100%占用 Spring Boot中4种文件下载方法的实现 避坑-不能将specific类型的gitlab-runner改变为share类型 Docker compose中的MySQL主从复制模式和percona-toolkit工具使用 在minio中开启https访问以及使用rclone备份minio桶 在多机Docker环境下部署Choerodon的解决方案 Prometheus中Monitor添加对SpringBoot Actuator的Basic认证 在Nginx的容器镜像中隐藏Nginx的Server响应头 K8s中的两种nginx-ingress-controller及其区别 两个docker工具:runlike和whaler Grafana中的邮件报警和截图插件grafana-image-enderer K8s中externalName-service和services-without-selectors maven配置文件settings.xml中的一些概念总结 K8s中flexvolume插件驱动的安装 K8s中的coredns无法解析svc问题排查 K8s中使用Ingress访问请求体过大问题解决 关于k8s中对于SpringBoot应用TCP类型的就绪探针不准确的问题发现 K8s中的环境变量与应用程序的对应关系与操作 SpringMVC4升级为SpringBoot2实战 在Vmware中Ubuntu22.04的vm-tools和网络问题 修改k8s节点主机名并重新加入集群 离线安装Grafana插件 在SpringBoot项目配置Liquibase数据库版本管理 记录Vue中父子组件传值的实战应用 实现单例模式的8种方法 三种常用的生产者消费者模式实现 使用两个线程交替打印0-100的奇偶数 关于部署于JBoss5中的Spring应用获取项目真实部署路径的问题 获取下一个完全对称日 通过短信验证码验证修改密码的解决方案 在Win10中使用Win+R快速启动软件 使用RSA加解密时注意Cipher.getInstance(String var0,Provider var1)提供的Provider是否正确 在RestEasy2.x中解决接口重复提交问题 几道简单的CTF题目思路 重温Spring---Spring事务控制与基于XML和注解的配置方法 重温Spring---Spring AOP基于XML和注解的配置 重温Spring---AOP动态代理和Spring AOP及其基本原理 重温Spring---Spring IOC基于XML和注解的配置和比较 在Windows10中安装MySQL5.7 Zip版本及常用配置 重温Spring---使用Spring IOC解决程序耦合 策略模式与责任链模式实战应用 Linux上直接打开war包修改文件 在Windows上运行两个微信的简单脚本 ThreadPoolExecutor的使用方法与分页查询数据实例 IDEA中Shelve Changes 和 Git Stash 通过resteasy发布RESTful接口 解决前端请求后台接口,后台报错Can not deserialize instance of java.util.ArrayList out of START_OBJECT token 使用VBA脚本汇总Excel文档 使用Jenkins+GitLab实现自动部署vue项目 Kubernetes:使用hostPath挂载nginx集群的配置文件和html 彻底搞定VirtualBox虚拟机的网络设定 在Docker中安装MySQL5.7并开启远程访问(附授权和修改密码方式) 利用git命令和java文件流 获取自己改动过的文件 浅谈Spring定时任务的使用(Scheduled注解) 在Spring项目简单配置Flyway(V4.2版本)数据库版本管理 解决Spring单元测试中因外键关联导致的失败integrity constraint violation:foreign key no action Redis安装与哨兵模式配置入门 关于Vue中使用Element-UI样式row-class-name失效的问题 Element-UI中实现可动态增加行列和可编辑单元格的表格 Windows系统查看端口占用、结束进程方法和命令 层次分析法(AHP)分析步骤与计算方法 源码分析之解决layui框架重载表格时额外参数不清空的问题 Spring Data Jpa 返回自定义对象(实体部分属性、多表联查) 如何将一个jar放到本地maven仓库中 关于SSM项目停止Tomcat时Log4j出现java.lang.NoClassDefFoundError: 获取el-table单元格值并根据该值对元素自定义样式渲染 解决Git每次push都要重新输入账号密码和HttpRequestException encountered的问题 解决前后端分离项目中Vue不带cookies的问题 SSM集成Shiro自定义权限过滤器不执行解决方案 SSM集成Shiro不进入自定义Realm的doGetAuthorizationInfo的解决方案 Vue+SSM中使用Token验证登录 Git拉代码推送代码提示密码错误如何修改 Git配置SSH Key(Git配置多个账户) 安装Tomcat服务器以及错误汇总(tomcat8.0、jdk8) 关于我
Spring Data Jpa 中使用CriteriaBuilder动态拼接SQL
ycyin · 2022-03-09 · via 小松鼠的博客

2022年3月8日大约 4 分钟Spring Data JpaSpring Data JpaSpring Boot


之前在我的博客园Spring data jpa - 随笔分类 - 敲代码的小松鼠 - 博客园 (cnblogs.com)有记录过相关技巧问题,之前的应用场景太简单,重新记录一篇。

应用场景

  在Spring Data Jpa中,可以使用提供的Spring Data JPA - query-methods进行方便的查询,甚至可以使用@Query注解自己写HQL或SQL完成更复杂的数据库操作。但是这些都很难实现动态拼接SQL(即where条件中某个参数没有值就不添加这个条件)。

在这个场景下,我们需要返回自定义的结果(即非实体结果),这在我之前的博客中有记录可以直接通过@Query注解自己定义HQL实现Spring Data Jpa 返回自定义对象(实体部分属性、多表联查) | 敲代码的小松鼠 (ycyin.eu.org),本文记录另一种可以通过CriteriaBuilder实现。同时我们通过CriteriaBuilder实现动态拼接SQL,在大多复杂的业务场景中都需要动态拼接Where查询条件。

本篇文章以实体类(UserPO)为主,根据department字段分组sum计算moneyCount字段查询为例,这实体类在文章末尾提供。

封装字段

首先根据需求先将返回的两个字段(分组的department和计算后的值count)封装为一个对象(MoneyCount),这个对象不需要加任何注解。

/**
 * 统计Money数
 */
public class MoneyCount {
    /**
     * department号
     */
    private String department;
    /**
     * money数
     */
    private Long count;

    public MoneyCount() {
    }

    public MoneyCount(String department, Long count) {
        this.department = department;
        this.count = count;
    }

    // 省略getter、setter
}

使用JPA的Criteria API实现查询

参考:Spring Data JPA - Reference Documentation

SpringBoot使用JPA自定义Query多表动态查询_西方黑灵梦-CSDN博客

Spring-data-Jpa分组分页求和查询_AAXXWX的博客-CSDN博客

spring-data-jpa带括号的复杂查询写法_Lovme_du的博客-CSDN博客_jpa 括号

注入EntityManager

@Component
public class UserDao {

    @PersistenceContext
    EntityManager em;
    
    // ...
}    

具体的方法实现

private List<SegmentCount> countSegments(final LocalDateTime startDate, final LocalDateTime endDate,
                                         final String billId, final String type,
                                         final List<Integer> agentIds, final List<String> corpCodes,
                                         final List<String> exCorpCodes){
    // 1 EntityManager获取CriteriaBuilder
    CriteriaBuilder cb = em.getCriteriaBuilder();
    // 2 CriteriaBuilder创建CriteriaQuery 指定查询返回,如果返回就是实体直接写实体.class
    // 如果是更新 CriteriaUpdate<...> update = cb.createCriteriaUpdate(...class)
    CriteriaQuery<MoneyCount> query = cb.createQuery(MoneyCount.class);
    // 3 CriteriaQuery指定要查询的表,得到Root<UserInfo>,Root代表要查询的表
    Root<UserPO> userPoRoot = query.from(UserPO.class);
    // 4 设置要查询的字段(注意要和Root指定的实体中的字段一致)
    // 如果是更新 update.set(userPoRoot.get("billId"), billId)
    query.multiselect(userPoRoot.get("department"),cb.sum(userPoRoot.get("moneyCount").as(Long.class)));
    // 5 创建where查询条件
    List<Predicate> predicateWhereList = new ArrayList<>();

    // 5.1 使用or(Predicate... restrictions)多个参数生成的SQL会打上括号
    List<Predicate> tTimeWhereList = new ArrayList<>();
    tTimeWhereList.add(cb.greaterThanOrEqualTo(userPoRoot.get("tTime"),startDate));
    tTimeWhereList.add(cb.lessThan(userPoRoot.get("tTime"),endDate));

    Predicate predicate = cb.and(tTimeWhereList.toArray(new Predicate[2]));
    predicateWhereList.add(cb.or(predicate,cb.equal(userPoRoot.get("billId"),billId)));

    predicateWhereList.add(cb.and(cb.equal(userPoRoot.get("type"), type)));

    predicateWhereList.add(cb.and(userPoRoot.get("agentId").in(agentIds)));

    // 根据业务逻辑corpCodes和exCorpCodes这两个值可能为空,动态拼入SQL
    if (CollectionUtils.isNotEmpty(corpCodes)) {
        predicateWhereList.add(cb.and(userPoRoot.get("corpCode").in(corpCodes)));
    }

    if (CollectionUtils.isNotEmpty(exCorpCodes)) {
        predicateWhereList.add(cb.not(userPoRoot.get("corpCode").in(exCorpCodes)));
    }

    // 6 指定where查询条件
    query.where(predicateWhereList.toArray(predicateWhereList.toArray(new Predicate[predicateWhereList.size()])) );
    // 7 指定groupBy条件
    query.groupBy(userPoRoot.get("department"));
    // 8 查询结果返回
    // 如果是更新 em.createQuery(update).executeUpdate()
    return  em.createQuery(query).getResultList();
}

上面的方法就实现了一个动态拼接的SQL查询(其中AND (t.CORP_CODE IN (?, ?))AND (t.CORP_CODE NOT IN (?, ?))是动态的),实际生成执行的SQL是:

SELECT t.DEPARTMENT AS COL_0_0_, SUM(CAST(t.MONEY_COUNT AS number(19, 0))) AS COL_1_0_
FROM T_TABLE_USER t
WHERE (t.T_TIME >= ? AND t.T_TIME < ? OR t.BILL_ID = ?)
   AND t.TYPE = ?
   AND (t.AGENT_ID IN (?, ?))
   AND (t.CORP_CODE IN (?, ?))
   AND (t.CORP_CODE NOT IN (?, ?))
 GROUP BY t.DEPARTMENT

使用JpaSpecificationExecutor实现

可以参考之前我在博客园的文章SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法和上面的逻辑实现。核心逻辑差不多。

附录:

这里贴上实体类(省略toString()、getters和setters):

UserPO:

@Entity
@Table(name = "T_TABLE_USER")
@EntityListeners(AuditingEntityListener.class)
public class UserPO implements Serializable {

    /**
     * 主键ID
     */
    @Id
    private String id;

    /**
     * 代理人ID
     */
    private Integer agentId;

    /**
     * 所属账单ID
     */
    private String billId;

    /**
     * 类型
     */
    private String type;

    /**
     * 来源
     */
    private String source;

    /**
     * 出生日期
     */
    private LocalDateTime tTime;

    /**
     * department号
     */
    private String department;

    /**
     * 公司编码
     */
    private String corpCode;

    /**
     * 金额数
     */
    private Integer moneyCount;

    /**
     * 创建时间
     */
    @CreatedDate
    private LocalDateTime createTime;

    /**
     * 修改时间
     */
    @LastModifiedDate
    private LocalDateTime updateTime;
}