Jpa Specification如何实现and和or同时使用查询
作者:qq_41315539
同时使用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结合使用,教程到这里就结束了。以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。