Spring Data Jpa 排序,分页,条件查询

1,051 阅读4分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

上一篇我们写的修改是跟保存一样都用的是Save方法,到底有什么不一样呢,我们看看代码

@Transactional
public <S extends T> S save(S entity) {
    Assert.notNull(entity, "Entity must not be null.");
    if (this.entityInformation.isNew(entity)) {
        this.em.persist(entity);
        return entity;
    } else {
        return this.em.merge(entity);
    }
}

可以看到先是做了一个判断,isNew(entity),是的话就执行this.em.persist(entity);新增,否的话就执行this.em.merge(entity);去更新。指定了主键主键存在就去修改,指定了主键主键不存在或者不指定主键就区新增 所以我们看控制台就有两条,一条是查询,一条新增或修改。

不指定主键
@Test
public void testUpdate(){
    User user = new User();
    user.setAge(21);
    user.setUserName("李四1109");
    user.setAddress("苏高新文体中心");
   // user.setId(1);
    User save = userRepository.save(user);
}

控制台只有一句Insert

image.png

指定主键不存在
@Test
public void testUpdate(){
    User user = new User();
    user.setAge(21);
    user.setUserName("李四1109");
    user.setAddress("苏高新文体中心");
    user.setId(100);
    User save = userRepository.save(user);
}

一条select一条insert image.png

指定主键存在
@Test
public void testUpdate(){
    User user = new User();
    user.setAge(210);
    user.setUserName("李四1109");
    user.setAddress("苏高新文体中心");
    user.setId(4);
    User save = userRepository.save(user);
}

image.png 跟我们上边写的一模一样,还有一个根版本判断的我们按下不表

我们在看下保存多个

@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {
    Assert.notNull(entities, "Entities must not be null!");
    List<S> result = new ArrayList();
    Iterator var3 = entities.iterator();

    while(var3.hasNext()) {
        S entity = var3.next();
        result.add(this.save(entity));
    }

    return result;
}

保存多个我们看到就是它自己写了一个while循环来执行我们的save方法

PagingAndSortingRepository(分页和排序接口)

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    Iterable<T> findAll(Sort sort);

    Page<T> findAll(Pageable pageable);
}

排序

我们排序用的是一个方法,需要一个sort对象来封装我们的排序条件,用Sort.by()返回一个sort对象 都一个参数有两个,表示降序或升序,ASC,DESC;第二个参数表示排序的字段我们先用id排序在根据年龄排,第二个参数可以写多个,现根据第一个排,再根据第二给我排。

public static Sort by(Sort.Direction direction, String... properties) {
    Assert.notNull(direction, "Direction must not be null!");
    Assert.notNull(properties, "Properties must not be null!");
    Assert.isTrue(properties.length > 0, "At least one property must be given!");
    return by((List)Arrays.stream(properties).map((it) -> {
        return new Sort.Order(direction, it);
    }).collect(Collectors.toList()));
}
创建实体类
@Data
@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "studentname")
    private String studentName;
    private Integer age;
    private String address;
}
创建接口
public interface StudentRepository extends PagingAndSortingRepository<Student,Integer> {
}
测试类
@Test
 public void testSort(){
    Iterator<Student> iterator = studentRepository.findAll(Sort.by(Sort.Direction.ASC,"id")).iterator();
    while (iterator.hasNext()){
        Student next = iterator.next();
        System.out.println(next);
    }
}
表里数据

image.png

控制台输出

根据id排序

image.png

根据年龄排序,只需要改一下条件即可

image.png

分页

下边是分页和根据年龄排序分页, 分页的对象是一个Pageable对象,这里我们用他的孙子类PageRequest来实现分页,有一个注意点,他分页默认从0页开始,我们下边还有分页的别的方法获取页数,页面,总页数,等等

@Test
public void testPage(){
    int page = 1;
    int size = 3;
    
PageRequest pageRequest = PageRequest.of(page, size, Sort.Direction.DESC, "age");

    Page<Student> all = studentRepository.findAll(pageRequest);
    //Page<Student> all = studentRepository.findAll(PageRequest.of(page, size, Sort.Direction.DESC,"age"));

    System.out.println("总页数 :"+ all.getTotalPages());

    System.out.println("当前页 :"+ all.getNumber());

    System.out.println(",每页显示 :" + all.getSize());

    System.out.println("总数量 : "+ all.getTotalElements());

    List<Student> content = all.getContent();
    for (Student student : content) {
        System.out.println(student);
    }
}
没有查询条件

image.png

有查询条件(上代码注释里的代码)

image.png

JpaRepository接口

源码

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort sort);

    List<T> findAllById(Iterable<ID> ids);

    <S extends T> List<S> saveAll(Iterable<S> entities);

    void flush();

    <S extends T> S saveAndFlush(S entity);

    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

    /** @deprecated */
    @Deprecated
    default void deleteInBatch(Iterable<T> entities) {
        this.deleteAllInBatch(entities);
    }

    void deleteAllInBatch(Iterable<T> entities);

    void deleteAllByIdInBatch(Iterable<ID> ids);

    void deleteAllInBatch();

    /** @deprecated */
    @Deprecated
    T getOne(ID id);

    T getById(ID id);

    <S extends T> List<S> findAll(Example<S> example);

    <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

我们可以看到JpaRepository接口的查询返回值是list,我们演示一下查询所有

接口集成JpaRepository,参数跟之前crudRepository一样

public interface StudentRepository extends JpaRepository<Student,Integer> {
}

测试类

@Test
public void test1(){
    List<Student> all = studentRepository.findAll();
    for (Student student : all) {
        System.out.println(student);
    }
}

输出结果

image.png

JpaSpecificationExecutor接口

我们看JpaRepository中没有动态条件查询的方法,这时候我们的接口就要继承JpaSpecificationExecutor, 支持动态分页排序查询。

看下源码有五个方法

第一个条件查询一个

第二个条件查询多个全部展示

第三个条件查询带分页

第四个条件查询带带排序

第五个查个数

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

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

    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

    List<T> findAll(@Nullable Specification<T> spec, Sort sort);

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

先改下库里数据

image.png

@Test
public void test2(){
    //查询条件
    StudentVo studentVo = new StudentVo();
    studentVo.setStudentName("张三");
    studentVo.setAddress("吴中");
    studentVo.setAge(30);
    //PageRequest定义分页属性
    PageRequest pageRequest = PageRequest.of(1, 1, Sort.Direction.DESC, "id");
    //调用查询获取结果
    Page<Student> all = studentRepository.findAll(new Specification<Student>() {
        //在匿名内部类里边拼接条件
        @Override
        public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            Predicate predicate = criteriaBuilder.conjunction();
            //判断是否为空
            if (studentVo != null) {
                //判断条件是否为空
                if (!ObjectUtils.isEmpty(studentVo.getStudentName())) {
                    //拼接查询条件 姓名包含
                    predicate.getExpressions().add(criteriaBuilder.like(root.get("studentName"), "%" + studentVo.getStudentName() + "%"));
                }
                //判断条件是否为空
                if (!ObjectUtils.isEmpty(studentVo.getAge())) {
                    //拼接查询条件 大于多少岁
                    predicate.getExpressions().add(criteriaBuilder.ge(root.get("age"), studentVo.getAge()));
                }
                //判断条件是否为空
                if (!ObjectUtils.isEmpty(studentVo.getAddress())) {
                    //拼接查询条件 地址包含
                    predicate.getExpressions().add(criteriaBuilder.like(root.get("address"), "%" + studentVo.getAddress() + "%"));
                }
            }
            return predicate;
        }
    }, pageRequest);

    System.out.println("总页数 :"+ all.getTotalPages());

    System.out.println("当前页 :"+ all.getNumber());

    System.out.println("每页显示 :" + all.getSize());

    System.out.println("总数量 : "+ all.getTotalElements());

    List<Student> content = all.getContent();
    for (Student student : content) {
        System.out.println(student);
    }
}

控制台打印结果

image.png