Spring Data JPA 入门
注解说明
@Entity(name = "")
类注解,用来注解该类是一个实体类并用来和数据库中的表建立关联关系。其中name表示该表的名称@Table(name = "")
类注解,跟@Entity(name = "")
作用一致@Id
属性注解,该注解表明该属性字段是一个主键,该属性必须具备,不可缺少@GeneratedValue(strategy = ,generator = "")
和@Id
主键注解一起使用,用来定义主键的呈现形式。其中strategy表示JPA通用的主键策略生成器,generator表示使用指定的主键生成器时,设置生成器的名称(即@GenericGenerator
注解的name值)@GeneratedValue(strategy= GenerationType.IDENTITY)
该注解由数据库自动生成,主键自增型,在MySql数据库中使用最频繁,Oracle不支持@GeneratedValue(strategy= GenerationType.AUTO)
主键由程序控制,默认的主键生成策略,Oracle默认是序列化的方式,MySql默认是主键自增的方式@GeneratedValue(strategy= GenerationType.SEQUENCE)
根据底层数据库的序列来生成主键,条件是数据库支持序列,Oracle支持,MySql不支持@GeneratedValue(strategy= GenerationType.TABLE)
使用一个特定的数据库表格来保存主键,较少使用
@GenericGenerator(name = "",strategy = "uuid")
自定义主键生成策略,其中name
表示生成器的名称,strategy
表示预定义的 Hibernate 策略或完全限定的类名@Column(name = "",nullable = ,columnDefinition = "")
类的属性注解,可以定义一个字段映射到数据库属性的具体特征(比如字段长度、非空、唯一、字段注释等)@Transient
类的属性注解,该注解标注的字段不会被映射到数据库当中@JoinColum(name = "",referencedColumnName = "")
保存表与表之间关系的字段,它要标注在实体属性上。一般修饰在主控方,用来定义一对一,一对多,多对多等关系。配合实体关系注解使用- 关联的实体的主键一般是用来做外键的。但如果不想主键作为外键,则需要设置
referencedColumnName
属性
- 关联的实体的主键一般是用来做外键的。但如果不想主键作为外键,则需要设置
@JoinTable(name = "", joinColumns = @JoinColumn(name = "",referencedColumnName = ""), inverseJoinColumns = @JoinColumn(name = "",referencedColumnName = ""))
用于构建一对多,多对多时的连接表,默认会以主表加下划线加外键表为表名name
中间表的表名称joinColumns = @JoinColumn(name = "",referencedColumnName = "")
指明当前对象哪个列(referencedColumnName的值)和中间表哪个列(name的值)对应inverseJoinColumns = @JoinColumn(name = "",referencedColumnName = "")
指明依赖对象哪个列(referencedColumnName的值)和中间表哪个列(name的值)对应
实体关系注解说明
-
@OneToOne(cascade = {},fetch = {},mappedBy = "")
实体间一对一的关系。实现的方式有外键关联和中间表保存关联关系cascade
当前类对象操作了之后,级联对象的操作REMOVE
级联删除操作。删除当前实体时,与它有映射关系的实体也会跟着被删除MEGER
级联更新(合并)操作。当前对象中的数据改变,会相应地更新级联对象中的数据DETACH
级联脱管/游离操作。如果要删除一个实体,但是它有外键无法删除,就需要这个级联权限了。它会撤销所有相关的外键关联REFRESH
级联刷新操作。更新数据前先刷新对象和级联对象,再更新PERSIST
级联持久化(保存)操作。持久保存拥有方实体时,也会持久保存该实体的所有相关数据ALL
当前类增删改查改变之后,关联类跟着增删改查,拥有以上所有级联操作权
fetch
关联对象的立即加载和延迟加载FetchType.LAZY
懒加载FetchType.EAGER
立即加载
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; }
-
@OneToMany
和@ManyToOne
实体一对多和多对一的双向关系。一端(One)使用@OneToMany
,多端(Many)使用@ManyToOne
- 在JPA规范中,一对多的双向关系由多端(Many)来维护,即多端(Many)负责关系的CRUD
- 一端(One)则为关系被维护端,不能维护关系,使用
@OneToMany
的mappedBy
属性表明是关系的被维护端,值是被拥有类中的属性名 - 多端(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; }
-
@ManyToMany
实体间多对多的关系。由一个中间表来维护关系,表名默认为 主表名_从表名- 多对多关系中一般不设置级联保存、级联删除、级联更新等操作
- 使用@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
不存在级联操作):
-
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); }
-
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); }); }
-
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); }
-
CascadeType.REFRESH
级联刷新 -
CascadeType.DETACH
级联托管 -
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
,该接口继承PagingAndSortingRepository
和QueryByExampleExecutor
接口
PagingAndSortingRepository
接口继承CrudRepository
,提供了排序和分页的方法。CrudRepository
接口定义了简单的CRUD方法,而JpaRepository
接口对该接口进行增强操作QueryByExampleExecutor
接口提供了⼀种⽤户友好的查询技术,具有简单的接⼝,它允许动态查询创建,并且不需要编写包含字段名称的查询
简单CRUD入门操作
- 定义DAO层,不需要写
@Repository
注解public interface StudentRepository extends JpaRepository<Student,Long> { }
- 使用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); }
- 如果要对某个对象进行修改操作,需要先查询出给实体后使用save方法进行更新,或者自定义SQL的方式进行更新操作
自定义SQL语句
自定义SQL在JPA中有两种呈现形式
- 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); }
- 原生的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); }
- 自定义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);
备注:
- SQL 中的参数传递也有两种形式:
- 使用问号 ?,紧跟数字序列,数字序列从1开始
- 使用冒号 :,紧跟参数名,参数名是通过
@Param
注解来绑定
- 注意JPQL不支持INSERT操作
- 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 主要包含三部分内容。
- Probe:这是具有填充字段的域对象的实际实体类,即查询条件的封装类(又可以理解为:查询条件参数),必填。
- ExampleMatcher:ExampleMatcher 有关于如何匹配特定字段的匹配规则,它可以重复使用在多个示例,必填;如果不填,用默认的(又可以理解为参数的匹配规则)
- 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的特点及约束:
- 支持动态查询,输入字段为null则忽略这个条件
- 不支持过滤条件分组,即不支持过滤条件用 or(或)来连接,所有的过滤条件都是简单一层的用 and(并且)连接,如 name = ?1 or (email = ?2 and email = ?3)
- 支持字符串的开始、包含、结束、正则匹配和其他属性类型的精确匹配,如针对于“name”的过滤也只有这么一个存储过滤值的位置,没办法同时传入两个过滤值
ExampleMatcher说明:
nullHandler
:Null值处理方式,枚举类型,有两个可选值,INCLUDE(包括)、IGNORE(忽略)- 标识作为条件的实体对象中,一个属性值(条件值)为 Null 时,是否参与过滤
- 当该选项值是 INCLUDE 时,表示仍参与过滤,会匹配数据库表中该字段值是 Null 的记录
- 若为 IGNORE 值,表示不参与过滤
defaultStringMatcher
:默认字符串匹配方式,枚举类型,有 6 个可选值。该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的匹配方式- DEFAULT(默认,效果同 EXACT)
- EXACT(相等)
- STARTING(开始匹配)
- ENDING(结束匹配)
- CONTAINING(包含,模糊匹配)
- REGEX(正则表达式)
defaultIgnoreCase
:默认大小写忽略方式,布尔型,当值为false时,即不忽略,大小写不相等- 该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的忽略大小写方式
ignoredPaths
:忽略属性列表,忽略的属性不参与查询过滤propertySpecifiers
: 各属性自定义查询方式包含:属性名、字符串匹配方式、大小写忽略方式、属性转换器
QueryByExampleExecutor实际中需要考虑的因素
查询条件表示有两部分: 一是条件值,二是查询方式,条件值使用实体对象存储,页面传入值按值进行过滤,未传入值则忽略
- null值的处理,是否需要匹配数据库中字段值为null的记录
- 基本类型的处理,比如int的默认值是0,为了避免不传值时,使用默认值进行查询,避免使用基本类型,采用包装类型
- 忽略某些属性值,实体类中的所有属性是否都需要参与查询
- 不同的过滤方式,查询条件判断不一样
- 大小写匹配
排序实现
JPA提供了一个Sort
类来定义排序,排序实现方式如下
- 使用原生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); }
- 使用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); }
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);
- 基于特殊参数的排序
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());
}