Jpa进阶,使用 Specification 进行高级查询

Mr. Zheng
2022-06-08 / 2 评论 / 206 阅读

前言

上一篇文章主要讲了 Jpa 的简单使用,而在实际项目中并不能满足我们的需求。如对多张表的关联查询,以及查询时需要的各种条件,这个时候你可以使用自定义 SQL 语句,但是Jpa并不希望我们这么做,于是就有了一个扩展:使用 Specification 进行查询

修改相应代码

1、修改 User.class

代码用的上一篇文章的,这里在 User 类中进行扩展,待会查询时会用到

@Entity
@Table(name = "user")
public class User {

    //部分代码略

    /**
     *  加上该注解,在保存该实体时,Jpa将为我们自动设置上创建时间
     */
    @CreationTimestamp
    private Timestamp createTime;

    /**
     *  加上该注解,在保存或者修改该实体时,Jpa将为我们自动创建时间或更新日期
     */
    @UpdateTimestamp
    private Timestamp updateTime;

    /**
     * 关联角色,测试多表查询
     */
    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;

    //部分代码略  
}
2、新增Role.class
@Entity
@Table(name = "role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true,nullable = false)
    private String name;

    //get set略  
}
3、修改UserRepository

要使用 Specification,需要继承 JpaSpecificationExecutor 接口,修改后的代码如下

public interface UserRepo extends JpaRepository<User,Long>, JpaSpecificationExecutor {

}
4、查看 JpaSpecificationExecutor 源码

Specification 是 Spring Data JPA 提供的一个查询规范,这里所有的操作都是围绕 Specification 来进行

public interface JpaSpecificationExecutor<T> {
    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);
}

封装查询Service

我这里简单做了下简单封装,编写 UserQueryService.class

@Service
public class UserQueryService {

    @Autowired
    private UserRepo userRepo;

    /**
     * 分页加高级查询
     */
    public Page queryAll(User user, Pageable pageable , String roleName){
        
        return userRepo.findAll(new UserSpec(user,roleName),pageable);
    }

    /**
     * 不分页
     */
    public List queryAll(User user){
        
        return userRepo.findAll(new UserSpec(user));
    }

    class UserSpec implements Specification<User>{

        private User user;

        private String roleName;

        public UserSpec(User user){
            this.user = user;
        }

        public UserSpec(User user,String roleName){
            this.user = user;
            this.roleName = roleName;
        }

        @Override
        public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {

            List<Predicate> list = new ArrayList<Predicate>();

            /**
             * 左连接,关联查询
             */
            Join<Role,User> join = root.join("role",JoinType.LEFT);

            if(!StringUtils.isEmpty(user.getId())){
                /**
                 * 相等
                 */
                list.add(cb.equal(root.get("id").as(Long.class),user.getId()));
            }

            if(!StringUtils.isEmpty(user.getUsername())){
                /**
                 * 模糊
                 */
                list.add(cb.like(root.get("username").as(String.class),"%"+user.getUsername()+"%"));
            }

            if(!StringUtils.isEmpty(roleName)){

                /**
                 * 这里的join.get("name"),就是对应的Role.class里面的name
                 */
                list.add(cb.like(join.get("name").as(String.class),"%"+roleName+"%"));
            }

            if(!StringUtils.isEmpty(user.getCreateTime())){

                /**
                 * 大于等于
                 */
                list.add(cb.greaterThanOrEqualTo(root.get("createTime").as(Timestamp.class),user.getCreateTime()));
            }

            if(!StringUtils.isEmpty(user.getUpdateTime())){

                /**
                 * 小于等于
                 */
                list.add(cb.lessThanOrEqualTo(root.get("createTime").as(Timestamp.class),user.getUpdateTime()));

            }

            Predicate[] p = new Predicate[list.size()];
            return cb.and(list.toArray(p));
        }
    }
}

查询测试

1、新增测试数据
    @Test
    public void test3() {

        /**
         * 新增角色
         */
        Role role = new Role();
        role.setName("测试角色");
        role = roleRepo.save(role);

        /**
         * 新增并绑定角色
         */
        User user = new User("小李",20,"男",role);
        User user1 = new User("小花",21,"女",role);
        
        userRepo.save(user);
        userRepo.save(user1);
    }

查看数据都已经新增成功了,并且 createTime 和 updateTime 也帮我们加上了

l45n48n4.png

2、简单查询
    @Test
    public void Test4(){

        /**
         * 添加查询数据,模糊查询用户名
         */
        User user = new User();
        user.setUsername("花");
        List<User> users = userQueryService.queryAll(user);
        users.forEach(user1 -> {
            System.out.println(user1.toString());
        });
    }

运行结果如下

l45n59zf.png

3、分页+关联查询

    @Test
    public void test5() {

        //页码,Pageable中默认是从0页开始
        int page = 0;
        //每页的个数
        int size = 10;
        Sort sort = new Sort(Sort.Direction.DESC,"id");
        Pageable pageable = PageRequest.of(page,size,sort);

        Page<User> users = userQueryService.queryAll(new User(),pageable,"测试角色");

        System.out.println("总数据条数:"+users.getTotalElements());
        System.out.println("总页数:"+users.getTotalPages());
        System.out.println("当前页数:"+users.getNumber());

        users.forEach(user1 -> {
            System.out.println(user1.toString());
        });

    }
}

通过角色的名称查询用户,运行结果如下

l45n5tz2.png

本文共 445 个字数,平均阅读时长 ≈ 2分钟
2

打赏

评论 (2)

取消
  1. 头像
    1111111
    Windows 10 · Google Chrome

    不如QueryDsl,spring-data-jpa也有QueryDsl的支持

    回复
    1. 头像
      Mr. Zheng 作者
      MacOS · Google Chrome
      @ 1111111

      我是工作后就用的 Specification,因此更偏向于 Specification

      回复