Jpa Specification如何实现and和or同时使用查询

目录
  • 同时使用and和or的查询
  • JPA 动态查询之AND、OR结合使用
    • 问题描述
    • 代码示例

同时使用and和or的查询

UserServiceImpl 类,service实现类

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private RongUserRepository rongUserRepository;
     //FriendNumResult  自定的返回类型
    //FriendNumParam  自定义的封装参数的类型
    //RongUser  实体类型
    @Override
    public FriendNumResult friendNum(FriendNumParam friendNumParam) {
        FriendNumResult friendNumResult=new FriendNumResult();

        Specification<RongUser> specification = new Specification<RongUser>(){

            @Override
            public Predicate toPredicate(Root<RongUser> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //封装and语句
                List<Predicate> listAnd = new ArrayList<Predicate>();
                //这里是hql,所以root.get(),方法里面必须是对应的实体属性
                listAnd.add(criteriaBuilder.equal(root.get("perLevel").as(Integer.class), friendNumParam.getPerLevel()));
                Predicate[] array_and=new Predicate[listAnd.size()];
                Predicate Pre_And = criteriaBuilder.and(listAnd.toArray(array_and));

                //封装or语句
                List<Predicate> listOr = new ArrayList<Predicate>();
                listOr.add(criteriaBuilder.equal(root.get("fId").as(Integer.class), friendNumParam.getUid()));
                listOr.add(criteriaBuilder.equal(root.get("fId2").as(Integer.class), friendNumParam.getUid()));
                Predicate[] arrayOr = new Predicate[listOr.size()];
                Predicate Pre_Or = criteriaBuilder.or(listOr.toArray(arrayOr));

                return criteriaQuery.where(Pre_And,Pre_Or).getRestriction();
                //单独使用  and 或者  or 时 返回
                //return criteriaBuilder.and(list.toArray());
            }
        };
        long num=this.rongUserRepository.count(specification);
        friendNumResult.setFriendNum(Integer.valueOf((int)num));
        return friendNumResult;
    }
}

RongUserRepository接口

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
//RongUser  自己的实体类型
public interface RongUserRepository extends JpaRepository<RongUser,Integer> , JpaSpecificationExecutor<RongUser> {
}

注意:使用Specification之前,RongUserRepository接口必须实现JpaSpecificationExecutor<RongUser>,RongUser对应表的实体类

JPA 动态查询之AND、OR结合使用

现在,我负责开发的项目中,使用JPA作为ORM框架。有了JPA,一行SQL都没写过。在昨天,有一个新的需求,需要进行动态查询,这个简单。但是有一个地方需要AND、OR结合使用,这里,我将记录下我的理解与写法,希望能帮助到大家。

问题描述

需要根据条件进行动态查询,实现一条类似下文的语句:

SELECT *
FROM   table
WHERE  1 = 1
   if (a == 1)
        AND table.column1 = a
   if (b != null)
        AND table.column2 = b
   if (cList != null && cList.size() > 0)
        AND table.column3 IN cList
   if (d == 2 || dd == 2)
        AND (table.column4 = d OR table.column5 = dd)

上面是几行伪代码。意思是,几个条件之间是AND连接,但是其中的部分条件,是使用OR连接的。

在我们的实际项目中,这个场景也是很常见的。这里,我将分享下具体的写法。以我们项目中的例子为例。

代码示例

JPA的动态查询,这里我们使用的方式是:实现 Specification 接口,自定义动态查询逻辑。这也是我个人比较推荐的方式。JPA的使用、Specification 接口基础知识这里我就不讲了。有兴趣的朋友可以查阅官方文档学习。

下面,我们首先定义好我们的数据库实体:

@Data
@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 生日
     */
    private Date birthDay;
    /**
     * 删除标识; 0 - 未删除,1 - 已删除
     */
    private Integer deleteFlag;
}

然后定义好DAO层接口:

@Repository
public interface UserDAO extends JpaRepository<User, Long> {
    /**
     * 其实,这个功能一般用作 list 接口使用,一般结合分页查询使用。这里,我不做介绍,看情况要不要后期加上教程
     */
    List<User> findAll(Specification<User> querySpec);
}

下面是前端传过来的动态查询的参数对象:

@Data
public class UserDTO {
    /**
     * 用户名,用于模糊搜索
     */
    private String username;
    /**
     * 用户ID,用于 In 查询
     */
    private List<String> userIdList;
    /**
     * 用户年龄,用于 OR In 查询
     */
    private List<Integer> ageList;
    /**
     * 生日,开始
     */
    @JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8")
    private Date birthDayBegin;
    /**
     * 生日,结束
     */
    @JsonFormat(pattern = "yyyy-MM-dd", locale = "zh", timezone = "GMT+8")
    private Date birthDayEnd;
}

然后,重要的地方来了,我们实现 Specification 接口,定义查询逻辑:

在实际代码操作中,我会将这部分逻辑抽离为一个单独的方法,使用lambda表达式完成,其实也就是匿名内部类。

private Specification<User> getListSpec(UserDTO userDTO) {
    return (root, criteriaQuery, criteriaBuilder) -> {
        List<Predicate> predicateList = new ArrayList<>();
        // 未删除标识,只查询未删除的数据
        predicateList.add(criteriaBuilder.equal(root.get("deleteFlag"), 0));
        // 根据 用户名 或 年龄List 查询
        List<Predicate> usernameOrAgePredicate = new ArrayList<>();
        String username = userDTO.getUsername();
        if (!StringUtils.isEmpty(username)) {
            // 用户名这里,用模糊匹配
            usernameOrAgePredicate.add(criteriaBuilder.like(root.get("username"), "%" + username + "%"));
        }
        List<Integer> ageList = userDTO.getAgeList();
        if (!CollectionUtils.isEmpty(ageList)) {
            // 下面是一个 IN查询
            CriteriaBuilder.In<Integer> in = criteriaBuilder.in(root.get("age"));
            ageList.forEach(in::value);
            usernameOrAgePredicate.add(in);
        }
        /* 下面这一行代码很重要。
         * criteriaBuilder.or(Predicate... restrictions) 接收多个Predicate,可变参数;
         * 这多个 Predicate条件之间,是使用OR连接的;该方法最终返回 一个Predicate对象;
         */
        predicateList.add(criteriaBuilder.or(usernameOrAgePredicate.toArray(new Predicate[0])));
        // 用户ID List,IN 查询
        List<Integer> userIdList = reqDTO.getUserIdList();
        if (!CollectionUtils.isEmpty(userIdList)) {
            CriteriaBuilder.In<Integer> in = criteriaBuilder.in(root.get("id"));
            userIdList.forEach(in::value);
            predicateList.add(in);
        }
        // 生日时间段查询
        Date birthDayBegin = reqDTO.getBirthDayBegin();
        Date birthDayEnd = reqDTO.getBirthDayEnd();
        if (birthDayBegin != null && birthDayEnd != null) {
            // DateUtils 是我自定义的一个工具类
            Date begin = DateUtils.startOfDay(birthDayBegin);
            Date end = DateUtils.endOfDay(birthDayEnd);
            predicateList.add(criteriaBuilder.greaterThanOrEqualTo(root.get("birthDay"), begin));
            predicateList.add(criteriaBuilder.lessThanOrEqualTo(root.get("birthDay"), end));
        }
        // 最终,使用AND 连接 多个 Predicate 查询条件
        return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
    };
}

这样,我们的动态查询部分就构建完毕了。具体怎么使用呢?如下:

Specification<User> querySpec = this.getListSpec(userDTO);
List<User> userList = userDAO.findAll(querySpec);

就这样,我们就执行了一次动态查询,并获取到了结果。

上面的动态查询,实际上等价于下面的伪代码:

SELECT *
FROM   user
WHERE  user.deleteFlag = 0
AND    ( user.username like '%{username}%' OR user.age IN ageList )
AND    user.id IN userIdList
AND    user.birthDay > birthDayBegin AND user.birthDay < birthDayEnd ;

当前,需要对应值不为空,才会拼接相应的AND条件。

至此,JPA 动态查询之 AND OR结合使用,教程到这里就结束了。以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • JPA多条件复杂SQL动态分页查询功能

    概述 ORM映射为我们带来便利的同时,也失去了较大灵活性,如果SQL较复杂,要进行动态查询,那必定是一件头疼的事情(也可能是lz还没发现好的方法),记录下自己用的三种复杂查询方式. 环境 springBoot IDEA2017.3.4 JDK8 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0&q

  • Spring Data JPA实现动态查询的两种方法

    前言 一般在写业务接口的过程中,很有可能需要实现可以动态组合各种查询条件的接口.如果我们根据一种查询条件组合一个方法的做法来写,那么将会有大量方法存在,繁琐,维护起来相当困难.想要实现动态查询,其实就是要实现拼接SQL语句.无论实现如何复杂,基本都是包括select的字段,from或者join的表,where或者having的条件.在Spring Data JPA有两种方法可以实现查询条件的动态查询,两种方法都用到了Criteria API. Criteria API 这套API可用于构建对数据

  • Spring data jpa的使用与详解(复杂动态查询及分页,排序)

    一. 使用Specification实现复杂查询 (1) 什么是Specification Specification是springDateJpa中的一个接口,他是用于当jpa的一些基本CRUD操作的扩展,可以把他理解成一个spring jpa的复杂查询接口.其次我们需要了解Criteria 查询,这是是一种类型安全和更面向对象的查询.而Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor. 而JpaSpecifica

  • 详解Spring Data JPA动态条件查询的写法

    我们在使用SpringData JPA框架时,进行条件查询,如果是固定条件的查询,我们可以使用符合框架规则的自定义方法以及@Query注解实现. 如果是查询条件是动态的,框架也提供了查询接口. JpaSpecificationExecutor 和其他接口使用方式一样,只需要在你的Dao接口继承即可(官网代码). public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecification

  • Jpa Specification如何实现and和or同时使用查询

    目录 同时使用and和or的查询 JPA 动态查询之AND.OR结合使用 问题描述 代码示例 同时使用and和or的查询 UserServiceImpl 类,service实现类 import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specificat

  • JPA Specification常用查询+排序实例

    目录 JPA Specification常用查询+排序 1.第一步:继承父类 2.第二步 JPA Specification复杂查询+排序 需求 开始了 一.dao 二.service 三.排序 前端 结束语 JPA Specification常用查询+排序 1.第一步:继承父类 public interface TblCarton2RCardLogRepository extends JpaRepository<TblCarton2RCardLog, String>,JpaSpecifica

  • jpa多条件查询重写Specification的toPredicate方法

    目录 Criteria查询基本概念 Criteria查询基本对象的构建 下面我们用两个示例代码来更深入的了解 Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor. Criteria 查询:是一种类型安全和更面向对象的查询 . 这个接口基本是围绕着Specification接口来定义的, Specification接口中只定义了如下一个方法: Predicate toPredicate(Root<T> root, C

  • Spring Data JPA查询方式及方法名查询规则介绍

    目录 Spring Data JPA查询方式及方法名查询规则 一.通过解析方法名创建查询 二.使用 @Query 创建查询 JPA 常用查询方法记录 CrudRepository 默认带的查询方法 简单的扩展-以字段为关键字进行查询 使用@Query 进行复杂查询 使用 Specification 进行复杂查询 Predicate CriteriaBuilder Root Spring Data JPA查询方式及方法名查询规则 Spring Data JPA 一.通过解析方法名创建查询 在执行查

  • Spring Data JPA实现动态条件与范围查询实例代码

    Spring Data JPA为我们提供了Query With Example来实现动态条件查询,当查询条件为空的时候,我们不用做大量的条件判断.但是Query With Example却不支持范围查询(包括日期范围,数值范围查询),本文通过Specification实现了既支持动态条件查询又支持范围查询的方法. 1 实现方式 1.1 范围对象Range定义 import java.io.Serializable; public class Range<E> implements Serial

  • JPA的多表复杂查询的方法示例

    最近工作中由于要求只能用hibernate+jpa 与数据库进行交互,在简单查询中,jpa继承CrudRepository接口 ,然后利用jpa的方法命名规范进行jpql查询,然而在进行复杂查询时,需要继承JpaSpecificationExecutor接口 利用Specification 进行复杂查询,由于我自己就遇到了这一问题,查了好多资料,虽然有方法,但是都没有一个详细的讲解,以至于知道方法而不能很好的利用jpa复杂查询的方便之处.我将举几个栗子,来详细的说一下我自己在使用jpa多表复杂查

  • Spring Data JPA带条件分页查询实现原理

    最新Spring Data JPA官方参考手册 Version 2.0.0.RC2,2017-07-25 https://docs.spring.io/spring-data/jpa/docs/2.0.0.RC2/reference/html/ JPA参考手册 (找了半天, 在线版的只找到这个) https://www.objectdb.com/java/jpa Spring Data JPA的Specification类, 是按照Eric Evans的<领域驱动设计>书中Specificat

  • SpringDataJPA之Specification复杂查询实战

    目录 SpringDataJPA Specification复杂查询 前言 实现 Specification与Controller业务逻辑 ApiReturnUtil.page封装 查询效果 可能遇到的错误 JpaSpecificationExecutor接口 Specification 一个一目了然的方法 Criteria+TypedQuery 开发过程中JPA Specification的应用 为什么需要Specification 应用场景 JPA Specification实现复杂查询 J

  • 使用Spring Data Jpa查询全部并排序

    目录 Spring Data Jpa查询全部并排序 1.Repository层只需要简单地extends JpaRepository 2.Service层中如下 JPA之排序问题 1.总览 2.使用JPA / JQL API排序 3.使用JPA条件查询对象API进行排序 Spring Data Jpa查询全部并排序 1.Repository层只需要简单地extends JpaRepository 依旧不用写任何东西.因为它自己其实有一个findAll(Sort sort)的方法,直接用就可以了.

随机推荐