Spring Data JPA 入门

注解说明

  1. @Entity(name = "")类注解,用来注解该类是一个实体类并用来和数据库中的表建立关联关系。其中name表示该表的名称
  2. @Table(name = "") 类注解,跟@Entity(name = "")作用一致
  3. @Id属性注解,该注解表明该属性字段是一个主键,该属性必须具备,不可缺少
  4. @GeneratedValue(strategy = ,generator = "")@Id主键注解一起使用,用来定义主键的呈现形式。其中strategy表示JPA通用的主键策略生成器,generator表示使用指定的主键生成器时,设置生成器的名称(即@GenericGenerator注解的name值)
    1. @GeneratedValue(strategy= GenerationType.IDENTITY) 该注解由数据库自动生成,主键自增型,在MySql数据库中使用最频繁,Oracle不支持
    2. @GeneratedValue(strategy= GenerationType.AUTO) 主键由程序控制,默认的主键生成策略,Oracle默认是序列化的方式,MySql默认是主键自增的方式
    3. @GeneratedValue(strategy= GenerationType.SEQUENCE) 根据底层数据库的序列来生成主键,条件是数据库支持序列,Oracle支持,MySql不支持
    4. @GeneratedValue(strategy= GenerationType.TABLE) 使用一个特定的数据库表格来保存主键,较少使用
  5. @GenericGenerator(name = "",strategy = "uuid") 自定义主键生成策略,其中name表示生成器的名称,strategy表示预定义的 Hibernate 策略或完全限定的类名
  6. @Column(name = "",nullable = ,columnDefinition = "") 类的属性注解,可以定义一个字段映射到数据库属性的具体特征(比如字段长度、非空、唯一、字段注释等)
  7. @Transient类的属性注解,该注解标注的字段不会被映射到数据库当中
  8. @JoinColum(name = "",referencedColumnName = "")保存表与表之间关系的字段,它要标注在实体属性上。一般修饰在主控方,用来定义一对一,一对多,多对多等关系。配合实体关系注解使用
    1. 关联的实体的主键一般是用来做外键的。但如果不想主键作为外键,则需要设置referencedColumnName属性
  9. @JoinTable(name = "", joinColumns = @JoinColumn(name = "",referencedColumnName = ""), inverseJoinColumns = @JoinColumn(name = "",referencedColumnName = ""))用于构建一对多,多对多时的连接表,默认会以主表加下划线加外键表为表名
    1. name中间表的表名称
    2. joinColumns = @JoinColumn(name = "",referencedColumnName = "")指明当前对象哪个列(referencedColumnName的值)和中间表哪个列(name的值)对应
    3. inverseJoinColumns = @JoinColumn(name = "",referencedColumnName = "")指明依赖对象哪个列(referencedColumnName的值)和中间表哪个列(name的值)对应

实体关系注解说明

  1. @OneToOne(cascade = {},fetch = {},mappedBy = "")实体间一对一的关系。实现的方式有外键关联中间表保存关联关系

    1. cascade当前类对象操作了之后,级联对象的操作
      1. REMOVE级联删除操作。删除当前实体时,与它有映射关系的实体也会跟着被删除
      2. MEGER级联更新(合并)操作。当前对象中的数据改变,会相应地更新级联对象中的数据
      3. DETACH级联脱管/游离操作。如果要删除一个实体,但是它有外键无法删除,就需要这个级联权限了。它会撤销所有相关的外键关联
      4. REFRESH级联刷新操作。更新数据前先刷新对象和级联对象,再更新
      5. PERSIST级联持久化(保存)操作。持久保存拥有方实体时,也会持久保存该实体的所有相关数据
      6. ALL当前类增删改查改变之后,关联类跟着增删改查,拥有以上所有级联操作权
    2. fetch关联对象的立即加载和延迟加载
      1. FetchType.LAZY懒加载
      2. FetchType.EAGER立即加载
    3. mappedBy表示该类放弃外键的维护,是关系被维护端。其中mappedBy的值是被拥有类中的属性名

    一对一的关系实例如下:一个学生拥有一张书桌的,一张书桌属于一个学生的

    @Getter
    @Setter
    @Entity
    @NoArgsConstructor
    @Table(name = "desk")
    public class Desk implements Serializable {
    
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
    
       @Column(name = "identifier", length = 20)
       private String identifier;
    
       /**
        * mappedBy = "desk" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
        */
       @OneToOne(mappedBy = "desk")
       private Student student;
    }
    
    @Getter
    @Setter
    @Entity
    @NoArgsConstructor
    @Table(name = "student")
    public class Student implements Serializable {
    
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
    
       @Column(name = "name", length = 100)
       private String name;
    
       /**
        * name = "desk_id" 设置外键在表中的字段名
        * nullable = false 说明该外键的值在表中不允许为 null
        */
       @OneToOne(fetch = FetchType.EAGER)
       @JoinColumn(nullable = false, name = "desk_id")
       private Desk desk;
    }
    
  2. @OneToMany@ManyToOne实体一对多和多对一的双向关系。一端(One)使用@OneToMany,多端(Many)使用@ManyToOne

    1. 在JPA规范中,一对多的双向关系由多端(Many)来维护,即多端(Many)负责关系的CRUD
    2. 一端(One)则为关系被维护端,不能维护关系,使用@OneToManymappedBy属性表明是关系的被维护端,值是被拥有类中的属性名
    3. 多端(Many)使用@ManyToOne表明是多端,使用@JoinColum设置在表中关联的字段(外键)

    一对多和多对一的双向关系实例如下:多个学生属于一间教室,一间教室属于多名学生

    @Getter
    @Setter
    @Entity
    @NoArgsConstructor
    @Entity
    @Table(name = "student")
    public class Student implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "name",length = 100)
        private String name;
    
        /**
         * name = "class_room_id" 设置外键在表中的字段名
         * nullable = false 说明该外键的值在表中不允许为 null
         */
        @ManyToOne
        @JoinColumn(nullable = false,name = "class_room_id",referencedColumnName = "class_no")
        private ClassRoom classRoom;
    }
    
    @Getter
    @Setter
    @Entity
    @NoArgsConstructor
    @Entity
    @Table(name = "class_room")
    public class ClassRoom implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "class_no")
        private String classNo;
    
        /**
         * mappedBy = "classRoom" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
         */
        @OneToMany(mappedBy = "classRoom")
        private Set<Student> students;
    }
    
  3. @ManyToMany实体间多对多的关系。由一个中间表来维护关系,表名默认为 主表名_从表名

    1. 多对多关系中一般不设置级联保存、级联删除、级联更新等操作
    2. 使用@JoinTable注解,中间表会按照注解指定的方式生成

    多对多的关系实例如下:一个教师有多名学生,一个学生有多个教师

    @Getter
    @Setter
    @Entity
    @NoArgsConstructor
    @Entity
    @Table(name = "student")
    public class Student implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "name",length = 100)
        private String name;
    
        /**
         * 在多对多的关系中,需要使用中间表来维护双方的关系,通过@JoinTable定义中间表
         *  name = "student_teacher" 中间表的表名称
         *  joinColumns = @JoinColumn(name = "student_id",referencedColumnName = "id") 指明当前对象哪个列(referencedColumnName的值)和中间表哪个列(name的值)对应
         *  inverseJoinColumns = @JoinColumn(name = "teacher_id",referencedColumnName = "id") 指明依赖对象哪个列(referencedColumnName的值)和中间表哪个列(name的值)对应
         */
        @ManyToMany
        @JoinTable(name = "student_teacher",
                joinColumns = @JoinColumn(name = "student_id",referencedColumnName = "id"),
                inverseJoinColumns = @JoinColumn(name = "teacher_id",referencedColumnName = "id"))
        private Set<Teacher> teachers;
    }
    
    @Getter
    @Setter
    @Entity
    @NoArgsConstructor
    @Table(name = "teacher")
    public class Teacher implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "name",length = 100)
        private String name;
    
        /**
         * mappedBy = "teacher" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
         */
        @ManyToMany(mappedBy = "teachers")
        private Set<Student> students;
    }
    

级联操作

级联在关系映射注解中指主动方对象执行操作(增删改)时,被关联对象(关系被维护段)是否同步执行统一操作
四个关系注解 @OneToMany@ManyToOne@OneToOne@ManyToMany 中都有一个属性cascade,通过该属性维护级联关系(默认值是default不存在级联操作):

  1. CascadeType.PERSIST级联是级联保存

    @Builder
    @Getter
    @Setter
    @AllArgsConstructor
    @Entity
    @Table(name = "student")
    public class Student implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        /**
         * @JoinColumn(nullable = false,name = "desk_id")
         *  name = "desk_id" 设置外键在表中的字段名
         *  nullable = false 说明该外键的值在表中不允许为 null
         *
         * @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
         *  fetch = FetchType.LAZY 采用懒加载方式
         *  cascade = CascadeType.PERSIST 级联保存,当保存学生实体时,会同步保存课桌实体
         */
        @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
        @JoinColumn(nullable = false,name = "desk_id")
        private Desk desk;
        
        @Tolerate
        public Student() { }
    }
    
    @Builder
    @Getter
    @Setter
    @Entity
    @Table(name = "desk")
    public class Desk implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "identifier",length = 20)
        private String identifier;
    
        /**
         * mappedBy = "desk" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
         */
        @OneToOne(mappedBy = "desk")
        private Student student;
    
        @Tolerate
        public Desk() { }
    }
    
    /***级联保存实例保存student对象同时保存desk对象*/
    @Test
    @Transactional
    public void saveStudent(){
      Student student = Student.builder().name("wen").age(23).birthday(LocalDate.now()).build();
      Desk desk = Desk.builder().identifier("456").build();
      student.setDesk(desk);
      studentRepository.save(student);
    }
    
    /***单纯保存desk不影响student*/
    @Test
    public void saveDesk(){
      Desk desk = Desk.builder().identifier("123").build();
      deskRepository.save(desk);
    }
    
  2. CascadeType.MERGE 级联更新

    @Builder
    @Getter
    @Setter
    @AllArgsConstructor
    @Entity
    @Table(name = "student")
    public class Student implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        /**
         * @JoinColumn(nullable = false,name = "desk_id")
         *  name = "desk_id" 设置外键在表中的字段名
         *  nullable = false 说明该外键的值在表中不允许为 null
         *
         * @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
         *  fetch = FetchType.LAZY 采用懒加载方式
         *  cascade = CascadeType.MERGE 级联更新,当保存更新的学生实体中又对Desk对象修改时,会同步保存课桌实体
         */
        @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
        @JoinColumn(nullable = false,name = "desk_id")
        private Desk desk;
        
        @Tolerate
        public Student() { }
    }
    
    @Builder
    @Getter
    @Setter
    @Entity
    @Table(name = "desk")
    public class Desk implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "identifier",length = 20)
        private String identifier;
    
        /**
         * mappedBy = "desk" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
         */
        @OneToOne(mappedBy = "desk")
        private Student student;
    
        @Tolerate
        public Desk() { }
    }
    
    /***级联更新实例student对象同时更新desk对象*/
    @Test
    @Transactional
    @Commit
    public void updateStudent() {
      Optional<Student> student = studentRepository.findById(1L);
      student.ifPresent(stu->{
          stu.setName("cheng");
          Desk desk = stu.getDesk();
          desk.setIdentifier("1001-11");
          studentRepository.save(stu);
      });
    }
    
  3. CascadeType.REMOVE 级联删除

    @Builder
    @Getter
    @Setter
    @AllArgsConstructor
    @Entity
    @Table(name = "student")
    public class Student implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        /**
         * @JoinColumn(nullable = false,name = "desk_id")
         *  name = "desk_id" 设置外键在表中的字段名
         *  nullable = false 说明该外键的值在表中不允许为 null
         *
         * @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
         *  fetch = FetchType.LAZY 采用懒加载方式
         *  cascade = CascadeType.REMOVE 级联删除,当删除学生实体时,会同步删除课桌实体
         */
        @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
        @JoinColumn(nullable = false,name = "desk_id")
        private Desk desk;
        
        @Tolerate
        public Student() { }
    }
    
    @Builder
    @Getter
    @Setter
    @Entity
    @Table(name = "desk")
    public class Desk implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "identifier",length = 20)
        private String identifier;
    
        /**
         * mappedBy = "desk" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
         */
        @OneToOne(mappedBy = "desk")
        private Student student;
    
        @Tolerate
        public Desk() { }
    }
    
    /***级联删除实例student对象同时删除desk对象*/
    @Test
    @Transactional
    @Commit
    public void deleteStudent() {
      studentRepository.deleteById(1L);
    }
    
  4. CascadeType.REFRESH 级联刷新

  5. CascadeType.DETACH 级联托管

  6. CascadeType.ALL 具有上述五个级联的功能

    注意点:@OneToOne@OneToMany中存在一个orphanRemoval属性,表示当关联关系被删除的时候,是否级联删除

    @Builder
    @Getter
    @Setter
    @AllArgsConstructor
    @Entity
    @Table(name = "student")
    public class Student implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "name",length = 100)
        private String name;
    
        /**
         * name = "class_room_id" 设置外键在表中的字段名
         * nullable = false 说明该外键的值在表中不允许为 null
         */
        @ManyToOne(cascade = CascadeType.ALL)
        @JoinColumn(nullable = false,name = "class_room_id",referencedColumnName = "class_no")
        private ClassRoom classRoom;
    
        @Tolerate
        public Student() {
    
        }
    }
    
    @Getter
    @Setter
    @Builder
    @Entity
    @Table(name = "class_room")
    public class ClassRoom implements Serializable {
    
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
    
       @Column(name = "class_no")
       private String classNo;
    
       /**
        * mappedBy = "classRoom" 表明该实体表放弃外键的维护。其中 mappedBy 的值是 Student 类中的属性
        * orphanRemoval = true 表示删除ClassRoom对象前先将Student对象删除后再删除ClassRoom对象
        */
       @OneToMany(mappedBy = "classRoom",orphanRemoval = true)
       private Set<Student> students;
    
       @Tolerate
       public ClassRoom() {
    
       }
    }
    
    /***级联删除实例ClassRoom对象前先删除外键引用的Student对象*/
    @Test
    @Transactional
    @Commit
    public void deleteClassRoom() {
       classRoomRepository.deleteById(8L);
    }
    

使用JPA关键字进行CRUD操作

使用JPA进行CRUD操作时,基本不用写简单的SQL语句,通过JPA提供的JpaRepository类即可完成简单的CRUD

Spring Data JPA提供了JpaRepository,该接口继承PagingAndSortingRepositoryQueryByExampleExecutor接口

  1. PagingAndSortingRepository接口继承CrudRepository,提供了排序和分页的方法。CrudRepository接口定义了简单的CRUD方法,而JpaRepository接口对该接口进行增强操作
  2. QueryByExampleExecutor接口提供了⼀种⽤户友好的查询技术,具有简单的接⼝,它允许动态查询创建,并且不需要编写包含字段名称的查询

简单CRUD入门操作

  1. 定义DAO层,不需要写@Repository注解
    public interface StudentRepository extends JpaRepository<Student,Long> { }
    
  2. 使用JPA提供的find和get关键字完成常规的查询操作,使用delete关键字完成删除,使用count关键字完成统计等,通过跟By关键字进行字段绑定
    public interface StudentRepository extends JpaRepository<Student,Long> {
        /**根据名称获取实体*/
        Student findByName(String name);
    
        /**根据age获取实体*/
        Student getByAge(Integer age);
    
        /**根据id和名称获取实体*/
        List<Student> findByIdAndName(Long id, String name);
    
        /**删除list中包含的实体并返回影响的行数*/
        Long deleteByIdIn(List<Long> ids);
    
        /**查询统计name包含关键字的个数*/
        Long countByNameContains(String key);
    }
    
  3. 如果要对某个对象进行修改操作,需要先查询出给实体后使用save方法进行更新,或者自定义SQL的方式进行更新操作

自定义SQL语句

自定义SQL在JPA中有两种呈现形式

  1. JPQL形式的SQL语句,from 后面是以类名呈现的,?1 代表是的是方法中的第一个参数
    public interface DeskRepository extends JpaRepository<Desk,Long> {
        @Query(value = "select desk from Desk desk where desk.id in ?1")
        List<Desk> findByIdIn(List<Long> ids);
    }
    
  2. 原生的SQL语句,需要使用nativeQuery = true指定使用原生SQL,:name是通过@Param注解去绑定的
    public interface DeskRepository extends JpaRepository<Desk,Long> {
        @Query(value = "select * from desk where identifier = :identifier",nativeQuery = true)
        Desk findByIdentifier(@Param("identifier") String identifier);
    }
    
  3. 自定义SQL更新数据。@Query注解中编写JPQL实现DELETE和UPDATE操作的时候必须加上@Modifying注解,以通知Spring Data 这是一个DELETE或UPDATE操作,同时接口的返回值表示影响的行数
    @Modifying
    @Query(value = "update student set age = :age where name = :name",nativeQuery = true)
    int updateStudentAgeByName(@Param("name") String name,@Param("age") Integer age);
    

备注:

  1. SQL 中的参数传递也有两种形式:
    1. 使用问号 ?,紧跟数字序列,数字序列从1开始
    2. 使用冒号 :,紧跟参数名,参数名是通过@Param注解来绑定
  2. 注意JPQL不支持INSERT操作
  3. UPDATE或者DELETE操作需要使用事务,此时需要 定义Service层,在Service层的方法上添加事务操作

动态创建查询

QueryByExampleExecutor接口提供了⼀种⽤户友好的查询技术,具有简单的接⼝,它允许动态查询创建,并且不需要编写包含字段名称的查询

QueryByExampleExecutor使用场景:使用一组静态或动态约束来查询数据存、频繁重构域对象,而不用担心破坏现有查询、简单的查询的使用场景

public interface QueryByExampleExecutor<T> {
    // 根据实例查找一个对象
    <S extends T> Optional<S> findOne(Example<S> example);
    // 根据实例查找一批对象
    <S extends T> Iterable<S> findAll(Example<S> example);
    // 根据实例查找一批对象,且排序
    <S extends T> Iterable<S> findAll(Example<S> example, Sort sort);
    // 根据实例查找一批对象,且排序和分页
    <S extends T> Page<S> findAll(Example<S> example, Pageable pageable);
    // 根据实例查找,返回符合条件的对象个数
    <S extends T> long count(Example<S> example);
    // 根据实例查找,返回符合条件的对象
    <S extends T> boolean exists(Example<S> example);
}

public interface Example<T> {

   static <T> Example<T> of(T probe) {
      return new TypedExample<>(probe, ExampleMatcher.matching());
   }

   static <T> Example<T> of(T probe, ExampleMatcher matcher) {
      return new TypedExample<>(probe, matcher);
   }

   T getProbe();

   ExampleMatcher getMatcher();

   @SuppressWarnings("unchecked")
   default Class<T> getProbeType() {
      return (Class<T>) ProxyUtils.getUserClass(getProbe().getClass());
   }
}

从源码中可以看出 Example 主要包含三部分内容。

  1. Probe:这是具有填充字段的域对象的实际实体类,即查询条件的封装类(又可以理解为:查询条件参数),必填。
  2. ExampleMatcher:ExampleMatcher 有关于如何匹配特定字段的匹配规则,它可以重复使用在多个示例,必填;如果不填,用默认的(又可以理解为参数的匹配规则)
  3. Example:Example 由 Probe 探针和 ExampleMatcher 组成,它用于创建查询,即组合查询参数和参数的匹配规则
//创建查询条件数据对象
Student student = new Student();
student.setName("i-");
student.setAge(22);

//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching()
       ////name采用“开始匹配”的方式查询
       .withMatcher("name", ExampleMatcher.GenericPropertyMatchers.startsWith())
       //忽略属性age
       .withIgnorePaths("age");


//创建实例
Example<Student> example = Example.of(student, matcher);
List<Student> studentList = studentRepository.findAll(example);
for (Student stu : studentList) {
   System.out.println(stu);
}

QueryByExampleExecutor的特点及约束:

  1. 支持动态查询,输入字段为null则忽略这个条件
  2. 不支持过滤条件分组,即不支持过滤条件用 or(或)来连接,所有的过滤条件都是简单一层的用 and(并且)连接,如 name = ?1 or (email = ?2 and email = ?3)
  3. 支持字符串的开始、包含、结束、正则匹配和其他属性类型的精确匹配,如针对于“name”的过滤也只有这么一个存储过滤值的位置,没办法同时传入两个过滤值

ExampleMatcher说明:

  1. nullHandler:Null值处理方式,枚举类型,有两个可选值,INCLUDE(包括)、IGNORE(忽略)
    1. 标识作为条件的实体对象中,一个属性值(条件值)为 Null 时,是否参与过滤
    2. 当该选项值是 INCLUDE 时,表示仍参与过滤,会匹配数据库表中该字段值是 Null 的记录
    3. 若为 IGNORE 值,表示不参与过滤
  2. defaultStringMatcher:默认字符串匹配方式,枚举类型,有 6 个可选值。该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的匹配方式
    1. DEFAULT(默认,效果同 EXACT)
    2. EXACT(相等)
    3. STARTING(开始匹配)
    4. ENDING(结束匹配)
    5. CONTAINING(包含,模糊匹配)
    6. REGEX(正则表达式)
  3. defaultIgnoreCase:默认大小写忽略方式,布尔型,当值为false时,即不忽略,大小写不相等
    1. 该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的忽略大小写方式
  4. ignoredPaths:忽略属性列表,忽略的属性不参与查询过滤
  5. propertySpecifiers: 各属性自定义查询方式包含:属性名、字符串匹配方式、大小写忽略方式、属性转换器

QueryByExampleExecutor实际中需要考虑的因素
查询条件表示有两部分: 一是条件值,二是查询方式,条件值使用实体对象存储,页面传入值按值进行过滤,未传入值则忽略

  1. null值的处理,是否需要匹配数据库中字段值为null的记录
  2. 基本类型的处理,比如int的默认值是0,为了避免不传值时,使用默认值进行查询,避免使用基本类型,采用包装类型
  3. 忽略某些属性值,实体类中的所有属性是否都需要参与查询
  4. 不同的过滤方式,查询条件判断不一样
  5. 大小写匹配

排序实现

JPA提供了一个Sort类来定义排序,排序实现方式如下

  1. 使用原生SQL来写
    public interface DeskRepository extends JpaRepository<Desk,Long> {
        @Query(value = "select * from desk where identifier = :identifier order by id desc",nativeQuery = true)
        Desk findByIdentifier(@Param("identifier") String identifier);
    }
    
  2. 使用JPQL来写
    public interface DeskRepository extends JpaRepository<Desk,Long> {
        @Query(value = "select desk from Desk desk where desk.id in ?1 order by id asc")
        List<Desk> findByIdIn(List<Long> ids);
    }
    
  3. Sort作为参数传递给方法
    public interface DeskRepository extends JpaRepository<Desk,Long> {
        @Query(value = "select desk from Desk desk where desk.id in ?1")
        List<Desk> findByIdIn(List<Long> ids,Sort sort);
    }
    
    //单字段排序
    List<Desk> desks = deskRepository.findByIdIn(List.of(3L,4L,5L), Sort.by(Sort.Direction.DESC,"id"));
    System.out.println(desks);
    
    //多字段排序
    Sort.Order order1 = new Sort.Order(Sort.Direction.DESC, "id");
    Sort.Order order2 = new Sort.Order(Sort.Direction.DESC, "identifier");
    List<Sort.Order> orders = List.of(order1, order2);
    Sort sort = Sort.by(orders);
    List<Desk> deskList = deskRepository.findByIdIn(List.of(3L,4L,5L), sort);
    System.out.println(deskList);
    
  4. 基于特殊参数的排序
    public interface DeskRepository extends JpaRepository<Desk,Long> { 
         List<Desk> findAllByOrderByIdDesc();
    }
    

JPA分页实现

PagingAndSortingRepository接口继承自CrudRepository接口提供的分页和排序方法

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    //排序功能。它按照Sort制定的排序返回数据
	Iterable<T> findAll(Sort sort);
    //分页查询(含排序功能)
	Page<T> findAll(Pageable pageable);
}

Pageable接口用于构造分页查询,返回Page对象。Page从0开始分页。实例化Pageable方式如下

//方式一,不排序
Pageable pageable = PageRequest.of(page, size);

//方式二,自定义字段排序
Pageable pageable = PageRequest.of(page, size, sort);

//方式三,自定义多个字段相同的排序
Pageable pageable = PageRequest.of(page, size, direction,properties);

实现方式如下:

@Query(value = "select * from desk where id in (:ids)",nativeQuery = true)
Page<Desk> findAllDeskPageById(@Param("ids") List<Long> ids, Pageable pageable);

@Query(value = "from Desk where id in (:ids)")
Page<Desk> getDeskPageById(@Param("ids") List<Long> ids, Pageable pageable);

//当前页码(注意:第一页是从0开始)
int pageIndex = 0;
//分页大小
int pageSize = 10;
Sort sort = Sort.by(Sort.Order.desc("id"));
PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sort);

//查询所有
Page<Desk> desks = deskRepository.findAll(pageRequest);
//使用原生SQL语句
Page<Desk> desks = deskRepository.findAllDeskPageById(List.of(3L,4L),pageRequest);        
//使用JPQL语句
Page<Desk> desks = deskRepository.getDeskPageById(List.of(3L,4L),pageRequest);       


System.out.println(String.join("","当前页码数:", String.valueOf(desks.getNumber() + 1)," 页"));
System.out.println(String.join("","总页数:", String.valueOf(desks.getTotalPages())," 页"));
System.out.println(String.join("","总记录数:", String.valueOf(desks.getTotalElements())," 条"));
System.out.println(String.join("","每页的条数:", String.valueOf(desks.getSize())," 条"));
System.out.println(String.join("","数据列表:", String.valueOf(desks.getContent())));

JpaSpecificationExecutor 接口

可以用于动态生成Query来满足业务中的各种复杂场景

public interface DeskRepository extends JpaRepository<Desk,Long>, JpaSpecificationExecutor<Desk> { }
@Test
@Transactional
@Commit
public void StudentByName(){

  Desk desk = Desk.builder().identifier("5").id(4L).build();

  //排序 :第一个参数是排序的规则(DESC/ASC)  后面参数是排序的字符
   Sort sort = Sort.by(Sort.Order.desc("id"));
   PageRequest pageRequest = PageRequest.of(0, 2, sort);

  /***
   * root:代表了可以查询和操作的实体对象的根,可以通过它的 Path<Y> get(String attributeName); 这个方法拿到要操作的字段
   *      注意:只可以拿到对应的T的字段(Employee)
   * criteriaQuery:代表一个specific的顶层查询对象,包含查询的各个部分,比如select,from,where,group by ,order by 等
   *      简单理解 就是它提供 了查询ROOT的方法(where,select,having)
   * criteriaBuilder:用来构建CriteriaQuery的构建器对象(相当于条件或者说条件组合),构造好后以Predicate的形式返回  
   */
  Page<Desk> desks = deskRepository.findAll((root, criteriaQuery, criteriaBuilder) -> {

      List<Predicate> predicateList = new ArrayList<>();
      if (StringUtils.hasText(desk.getIdentifier())) {
          predicateList.add(criteriaBuilder.equal(root.get("identifier"),desk.getIdentifier()));
      }

      if (desk.getId() != null) {
          predicateList.add(criteriaBuilder.equal(root.get("id"),desk.getId()));
      }

      return criteriaQuery.where(predicateList.toArray(new Predicate[0])).groupBy(root.get("id")).getRestriction();
  },pageRequest);

  System.out.println(desks.getContent());
}

参考 https://zhuanlan.zhihu.com/p/110024146

posted @ 2023-05-28 22:51  伊文小哥  阅读(200)  评论(0编辑  收藏  举报