其他
详述 Spring Data JPA 的那些事儿
The following article is from zempty 笔记 Author zempty
spring data jpa常用的 jpa 的配置
下面所有演示的代码均来自我个人 github 的 spring-data-jpa 仓库,仓库地址:https://github.com/kickcodeman/spring-data-jpa, 读者可以clone 下来运行本项目,验证下面讲的所有知识点。下面把spring boot 项目关于 jpa 的常用配置 application.properties 配置如下:server.port=8081
# 数据库连接的配置
spring.datasource.url=jdbc:mysql:///jpa?useSSL=false
spring.datasource.username=root
spring.datasource.password=zempty123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接池的配置,hikari 连接池的配置
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.auto-commit=true
#通过 jpa 自动生成数据库中的表
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
该配置比较常用,当服务首次启动会在数据库中生成相应表,后续启动服务时如果实体类有增加属性会在数据中添加相应字段,原来数据仍在,该配置除了 update ,还有其他配置值,
create :该值慎用,每次重启项目的时候都会删除表结构,重新生成,原来数据会丢失不见。
create-drop :慎用,当项目关闭,数据库中的表会被删掉。
validate :验证数据库和实体类的属性是否匹配,不匹配将会报错。
综上:个人感觉还是使用 update 较为稳妥。spring.jpa.show-sql=true
该配置当在执行数据库操作的时候会在控制台打印 sql 语句,方便我们检查排错等。spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
数据库的方言配置。
类映射到数据库表的常用注解分析
spring data jpa 提供了很多注解,下面我们把日常常用注解总结如下:@Entity 是一个类注解,用来注解该类是一个实体类用来进行和数据库中的表建立关联关系,首次启动项目的时候,默认会在数据中生成一个同实体类相同名字的表(table),也可以通过注解中的 name 属性来修改表(table)名称, 如@Entity(name=“stu”) , 这样数据库中表的名称则是 stu 。该注解十分重要,如果没有该注解首次启动项目的时候你会发现数据库没有生成对应的表。@Table 注解也是一个类注解,该注解可以用来修改表的名字,该注解完全可以忽略掉不用,@Entity 注解已具备该注解的功能。@Id 类的属性注解,该注解表明该属性字段是一个主键,该属性必须具备,不可缺少。@GeneratedValue 该注解通常和 @Id 主键注解一起使用,用来定义主键的呈现形式,该注解通常有多种使用策略,先总结如下:@GeneratedValue(strategy= GenerationType.IDENTITY) 该注解由数据库自动生成,主键自增型,在 mysql 数据库中使用最频繁,oracle 不支持。 @GeneratedValue(strategy= GenerationType.AUTO) 主键由程序控制,默认的主键生成策略,oracle 默认是序列化的方式,mysql 默认是主键自增的方式。 @GeneratedValue(strategy= GenerationType.SEQUENCE) 根据底层数据库的序列来生成主键,条件是数据库支持序列,Oracle支持,Mysql不支持。 @GeneratedValue(strategy= GenerationType.TABLE) 使用一个特定的数据库表格来保存主键,较少使用。
@Getter
@Accessors(chain = true)
@Entity(name = "stu")
//@Table
public class Student {
@Id
@GeneratedValue(strategy= GenerationType.TABLE)
private long id;
private String name;
@Transient
private String test;
private int age;
private LocalTime onDuty;
private LocalDate onPosition;
private LocalDateTime birthdayTime;
}
使用上述实体类的注解,当运行项目的时候就会在数据库中生成一个表名是 stu 的表。
类的继承分析
下面来研究一下类之间存在继承关系的时候,jpa 又是如何处理继承关系的呢?这个是很值得了解清楚的,这个搞明白了我们在使用 spring data jpa 的时候可能会事半功倍。大致总结继承这块有这样三种情况:多类一表:多个类之间的属性相同,唯一的区别就是类型上的差异(类名不同),这个时候我们可以为这个共同属性的类建立一个父类,只让父类应射到数据库。 多类多表:把多个类之间公有的属性提取出来放在它们公有的父类中,各个类之间可以定义自己特有的属性,子类和父类在数据库中都有相应的表和其对应。 子类建表:把多个类之间公有的属性提取出来放在它们公有的父类中,各个类之间可以定义自己特有的属性,仅仅子类和数据库中的表建立关联关系,父类中的属性延续到每一个子类中,在数据库中每一个子类对应的表都有父类中定义的属性。
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 该注解从字面来理解即可大致看出含义,只生成一个 table。现在先给出结论:该注解的使用场景是几个实体类的属性大致相同,没有什么区别,唯一区别的可能也就是类名了,这样的话我们可以考虑使用该注解,使用该注解的话我们多个实体类公用一个table ,该表由父类生成,父类中默认会生成一个 dtype 字段,用来表明该条数据是属于哪一个实体类的数据。详细使用可以参考项目包com.zempty.springbootjpa.entity.inheritance.single_table 中的三个类,A1,B1, Group1 三个类的使用,类中的 Group1 是 A1 和 B1 的子类,A1 和 B1 中通常会使用如下的一个注解:@DiscriminatorValue 该注解只有一个 value 值用来标注在插入数据的时候 dtype 字段的值。在包 com.zempty.springbootjpa. inheritance. controller 中的 SingleController 有几个详细的测试案例,可以运行项目,测试几个接口,查看一下数据库查看使用细则。 @Inheritance(strategy = InheritanceType.JOINED)该注解使用后,会生成多张表。现在先给出结论性总结如下:当有一个这样的需求,一些属性是多数类都有的,比如,username,password … ,那么我们可以考虑把共有的属性给提取出来,单独做成一个表,类中特殊属性定义在各自的类中。详细使用可以参项目包 com.zempty. springbootjpa. entity.inheritance.joined 中的三个类 A2, B2, Group2 ,三个类的使用, Group2 是 A2 和 B2 的类,该案例将会把三个实体类都生成各自的表,当我们在添加 A2 或者 B2 数据进入数据库的时候 ,Group2 对用也会相应的添加一条数据, 子类中有一个注解 @PrimaryKeyJoinColumn 可以用来定义一个子类生成表的主键的名字,如果没有默认使用 id。 在包 com.zempty .springbootjpa. inheritance. controller 中的 JoinedController 中有几个测试案例,运行项目,即可查看感受一下使用细则。 @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)只有子类生成 table 。现在先给出一个结论:父类中的属性是共有属性,父类不会生成 table ,子类定义自己特有的属性,子类生成的 table 会有父类中定义的属性字段。这里需要注意使用该注解的时候父类中的主键生成策略不可以是@GeneratedValue(strategy = GenerationType.IDENTITY) ,这里我定义成了 @GeneratedValue(strategy = GenerationType.AUTO) ,否则会报错。详细使用可以参考包 com.zempty .springbootjpa..entity.inheritance.per_table 中的三个类 A3,B3,Group3 的注解使用,Group3 是 A3 和 B3 的 父类,该案例,Group3 将不会被生成 table,但是其中的属性将会出现在每一个子类生成的 table 当中。在包 com.zempty .springbootjpa. inheritance. controller 中的 PerController 中有几个测试案例,运行项目,即可查看感受一下使用细则。
类之间的关系分析
在数据库当中表和表之间都是有一定的关联关系的,jpa 是如何在实体类之间建立和数据库表中类似的关联关系呢?jpa 是通过一系列的注解来实现类之间的关联关系的,下面我们就来透彻的分析一下如何使用注解来表明类之间的关系,类之间的关系大致可以有一下几种情况:一对一的关系,jpa 使用的注解是 @OneToOne 一对多的关系,jpa 使用的注解是 @OneToMany 多对一的关系,jpa 使用的注解是 @ManyToOne 多对多的关系,jpa 使用的注解是 @ManyToMany
一个学生通常只有一个课桌,一个课桌通常给一个学生作,这里学生和课桌的关系就是互为 @OneToOne 一个教室通常可以容纳很多的学生,教室到学生的关系就可以定义为 @OneToMany 很多学生容纳在一个教室当中,学生到教室的关系可以定义为@ManyToOne 一个学生可以有很多的老师,一个老师可以有很多的学生,这里学生和老师的关系就互为 @ManyToMany
@OneToOne
@Setter
@Getter
@Accessors(chain = true)
@Entity(name = "stu")
public class Student {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;
@Column(length = 100)
private String name;
// 这里是定义学生和课桌的一对一的关系
@OneToOne
// 下面的这个注解用来生成第三张表,来维护学生和课桌的关系
@JoinTable( name = "stu_desk",joinColumns = @JoinColumn(name="student_id"),inverseJoinColumns = @JoinColumn(name="desk_id") )
private Desk desk;
@Transient
private String test;
private int age;
private LocalTime onDuty;
private LocalDate onPosition;
private LocalDateTime birthdayTime;
}
@Setter
@Getter
@Accessors(chain = true)
@Entity
public class Desk {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer deskNum;
@OneToOne(mappedBy = "desk")
private Student student;
}
desk_id 用来维护关系。 在 Desk 类,@OneToOne 注解中有一个 mappedBy = “desk” 的属性,该字段说明 Desk 类放弃主键的维护,关于 mappedBy 这个属性下文也会重点谈到。
@OneToMany
教室类如下:@Getter
@Accessors(chain = true)
@Entity(name="class_room")
public class ClassRoom {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(mappedBy = "classRoom")
private Set<Student> students;
}
@ManyToOne
学生类 Student 中关键新增代码片段如下:private ClassRoom classRoom;
@ManyToMany
Teacher 类如下所示:@Getter
@Accessors(chain = true)
@Entity
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String subject;
@ManyToMany(mappedBy = "teachers")
private Set<Student> students;
}
@JoinTable(name="stu_teacher",joinColumns = @JoinColumn(name ="stu_id"),inverseJoinColumns = @JoinColumn(name="teacher_id"))
private Set<Teacher> teachers;
进一步剖析级联操作(cascade)
什么是 jpa 的级联操作?这里级联可能不好理解,你可以理解成关联,我在操作 Student 类的增删改查的时候, Student 类的关联类会受到相应的影响。在使用 spring data jpa 进行增删改查的时候一定要弄清彼此的级联关系,这很重要,很重要,很重要。。。级联应该怎么使用呢?在上面讲解的四种类之间的关系的时候,四个关系注解 @OneToMany , @ManyToOne, @OneToOne , @ManyToMany 中都有一个属性叫 cascade 该属性值是一个 CascadeType 的数组,下面来重点解释和分析各种 CascadeType, 没有使用 cascade ,默认是 default, 就是不存在级联。CascadeType.PERSIST 该级联是级联保存。 CascadeType.MERGE 该级联是级联更新 CascadeType.REMOVE 该级联是级联删除 CascadeType.REFRESH 该级联是级联刷新(不常用) CascadeType.DETACH 该级联是级联托管(不常用) CascadeType.ALL 具有上述五个级联的功能
保存学生的时候,级联保存课桌
Student 级联设置关键代码如下所示:@OneToOne(cascade = CascadeType.PERSIST)
@JoinTable( name = "stu_desk",joinColumns = @JoinColumn(name="student_id"),inverseJoinColumns = @JoinColumn(name="desk_id") )
private Desk desk;
public Student saveStudent() {
Student student = new Student()
.setAge(19)
.setName("zempty");
Desk desk = new Desk()
.setDeskNum(10);
// 学生分配座位,如果设置了级联保存,保存学生的时候也会保存座位,如果没设置级联保存,添加课桌会报错
student.setDesk(desk);
return studentRepository.save(student);
}
更新学生,级联更新教室数据
Student 类的关键代码如下:@ManyToOne(cascade = CascadeType.MERGE)
private ClassRoom classRoom;
public Student updateStudent(@PathVariable("id") Integer id) {
Optional<Student> optional = studentRepository.findById(id);
optional.ifPresent(student->{
student.setName("zempty_zhao");
ClassRoom room = student.getClassRoom();
room.setName("IT 666999");
studentRepository.save(student);
});
return optional.get();
}
删除学生,把老师也删除了
学生的关键代码如下所示:// 删除学生的同时会删除关联的老师,如果老师还有关联其他的学生,就会报错,除非老师类也要级联删除,这个删除是要慎重的
@ManyToMany(cascade = {CascadeType.REMOVE})
@JoinTable(name="stu_teacher",joinColumns = @JoinColumn(name = "stu_id"),inverseJoinColumns = @JoinColumn(name="teacher_id"))
private Set<Teacher> teachers;
public void deleteStudent(@PathVariable("id") Integer id) {
Optional<Student> optional = studentRepository.findById(id);
optional.ifPresent(student -> {
studentRepository.delete(student);
});
}
如果该老师还有其他关联的学生这里会报错,请注意。 如果老师那里也配置了级联删除,删除老师的同时,老师的关联学生都会连带删除。
如果不清楚级联删除的功能,可能会造成很严重的后果,建议读者一定反复测试该案例弄清楚级联删除的使用。
教室里有学生,如何删除教室
如果数据库中教室和学生存在绑定关系,如果删除这个教室就会出现问题,无法正常删除因为存在外键,如何解决这个问题呢?当删除数据的时候,如果该数据存在外键是无法直接删除的,这是在日常使用当中很容易遇到的一个问题,现在就这个问题给出一些解决方案:ClassRoom 核心代码如下所示:@JsonIgnore
private Set<Student> students;
public void deleteClassRoom(@PathVariable("id") Integer id) {
Optional<ClassRoom> optional= classRoomRepository.findById(id);
optional.ifPresent(classRoom ->{
// 先找到所有的学生,把教室置空,然后删除教室
Set<Student> students = classRoom.getStudents();
students.forEach(student -> student.setClassRoom(null));
classRoomRepository.delete(classRoom);
});;
}
教室类在@OneToMany 中添加一个属性 orphanRemoval = true
教师类关键代码如下所示:
@JsonIgnore
private Set<Student> students;
public void deleteClassRoom(@PathVariable("id") Integer id) {
Optional<ClassRoom> optional= classRoomRepository.findById(id);
optional.ifPresent(classRoom ->{
// 先找到所有的学生,把教室置空,然后删除教室
// Set<Student> students = classRoom.getStudents();
// students.forEach(student -> student.setClassRoom(null));
classRoomRepository.delete(classRoom);
});;
}
orphanRemoval 使用细则
orphanRemoval 这个属性只存在两类关系注解中 @OneToOne 和 @OneToManyjpa 为什么把这个注解仅仅只放在这两个关系类注解中呢?个人分析是使用 @OneToOne 和 @OneToMany 的实体类是存在外键的,操作存在外键的类,尤其是删除的时候就会很头痛,于是就提供了这样的一个属性,来消除外键带来的烦恼。在使用该属性的时候,也就是该属性设置成 true 的时候一定要慎重,从上面的例子可以看出来,当我在删除教室的时候,教室里的学生也都被删除了,该属性会有一个级联的效果。
进一步剖析 mappedBy
在 jpa 中的关系属性中,mappedBy 是很重要的一个属性存在,做为一个使用者我们一定要清楚 mappedBy 的使用细则,下面根据个人的使用经验总结如下:当一个实体类使用了 mappedBy 属性,是可以避免多余的表生成的,如果没有使用该属性,程序运行后在数据库会多生成一个关系表。 当一个实体类使用了 mappedBy 属性,表示该类放弃主键的维护,该类生成的表中不存放和它关联类的外键。
mappedBy 细节分析
使用 mappedBy 的一方是放弃主键的维护的,当在使用 mappedBy 的一方进行级联操作的时候有些细节你应该知道:通常 mappedBy 在处理级联删除的时候使用 orphanRemoval 属性就好,当然在@ManyToMany 这个注解当中是没有 orphanRemoval 这个属性的,还是需要使用自己的级联删除属性的。 级联保存和级联更新的时候你需要知道在保存和更新关联数据的时候是没有关联到外键的,你需要借助关联类去维护外键,下面看代码展示:
@JsonIgnore
private Set<Student> students;
public ClassRoom saveClassRoom() {
ClassRoom room = new ClassRoom()
.setName("IT 教室");
Set<Student> students = new HashSet<>();
Student student = new Student().setName("test123");
students.add(student);
room.setStudents(students);
return classRoomRepository.save(room);
}
我们必须弄清楚谁是维护彼此关系的,上面的教室使用了 mappedBy 属性放弃了主键的维护,因此我们需要借助学生类来维护彼此的关系,我们在测试代码中需要在学生类中把教室给设置进去,这样问题就解决了:
改进测试代码:
public ClassRoom saveClassRoom() {
ClassRoom room = new ClassRoom()
.setName("IT 教室");
Set<Student> students = new HashSet<>();
Student stu = new Student().setName("test123");
students.add(stu);
//改进代码,学生类维护关系,把教室设置到每一个学生当中
students.forEach(student -> student.setClassRoom(room));
room.setStudents(students);
return classRoomRepository.save(room);
}
使用spring data jpa关键字进行增删改查
在使用 spring data jpa 进行数据库的增删改查的时候,基本上我们无需写 sql 语句的,但是我们必须要遵守它的规则,下面就来聊一聊:如何定义 DAO 层
spring data jpa 的数据层,我们只需要定义一个接口继承 JpaRepository 就好,JpaRepository 接口中定义了丰富的查询方法供我们使用,足以供我们进行增删改查的工作,参考代码如下:定义一个 Student 的 dao 层,这样我们的增删改查就已经有了
}
使用关键字自定义查询
我们可以使用 jpa 提供的 find 和 get 关键字完成常规的查询操作,使用 delete 关键字完成删除,使用 count 关键字完成统计等下面看下面的一段代码展示:// 查询数据库中指定名字的学生
List<Student> findByName(String name);
// 根据名字和年龄查询学生
List<Student> getByNameAndAge(String name, Integer age);
//删除指定名字的学生
Long deleteByName(String name);
// 统计指定名字学生的数量
Long countByName(String name);
}
@GetMapping("/find/{name}")
public List<Student> findStudentsByName(@PathVariable("name") String name) {
return studentRepository.findByName(name);
}
//根据名字和年龄进行查询
public List<Student> getStudentByNameAndAge(@PathVariable("name") String name,@PathVariable("age") Integer age) {
return studentRepository.getByNameAndAge(name, age);
}
@DeleteMapping("/delete/{name}")
//删除的时候一定要添加事务注解
@Transactional
public Long deleteStudentByName(@PathVariable("name") String name) {
return studentRepository.deleteByName(name);
}
//统计指定名字学生的数量
public Long countStudentByName(@PathVariable("name") String name) {
return studentRepository.countByName(name);
}
jpa 使用 sql 增删改查
有时候我们不习惯使用上述的关键字去操作数据,就是喜欢写 sql , spring data jpa 也是支持写 sql 语句的,如何使用呢?案例代码如下所示://使用的 JPQL 的 sql 形式 from 后面是类名
// ?1 代表是的是方法中的第一个参数
@Query("select s from ClassRoom s where s.name =?1")
List<ClassRoom> findClassRoom1(String name);
//这是使用正常的 sql 语句去查询
// :name 是通过 @Param 注解去确定的
@Query(nativeQuery = true,value = "select * from class_room c where c.name =:name")
List<ClassRoom> findClassRoom2(@Param("name")String name);
}
JPQL 形式的 sql 语句,from 后面是以类名呈现的。 原生的 sql 语句,需要使用 nativeQuery = true 指定使用原生 sql
使用问号 ?,紧跟数字序列,数字序列从1 开始,如 ?1 接收第一个方法参数的值。 使用冒号:,紧跟参数名,参数名是通过@Param 注解来确定。
使用 Sort 来对数据进行一个排序
spring data jpa 提供了一个 Sort 类来进行分类排序,下面通过代码来说明 Sort 的使用:// 正常使用,只是多加了一个 sort 参数而已
@Query("select t from Teacher t where t.subject = ?1")
List<Teacher> getTeachers(String subject, Sort sort);
}
public List<Teacher> getTeachers(@PathVariable("subject") String subject) {
// 第一种方法实例化出 Sort 类,根据年龄进行升序排列
Sort sort1 = Sort.by(Sort.Direction.ASC, "age");
//定义多个字段的排序
Sort sort2 = Sort.by(Sort.Direction.DESC, "id", "age");
// 通过实例化 Sort.Order 来排序多个字段
List<Sort.Order> orders = new ArrayList<>();
Sort.Order order1 = new Sort.Order(Sort.Direction.DESC, "id");
Sort.Order order2 = new Sort.Order(Sort.Direction.DESC, "age");
orders.add(order1);
orders.add(order2);
Sort sort3 = Sort.by(orders);
//可以传不同的 sort1,2,3 去测试效果
return teacherRepositoty.getTeachers(subject, sort1);
}
jpa 的分页操作
数据多的时候就需要分页,spring data jpa 对分页提供了很好的支持,下面通过一个 demo 来展示如何使用分页://正常使用,只是多加了一个 Pageable 参数而已
@Query("select t from Teacher t where t.subject = :subject")
Page<Teacher> getPage(@Param("subject") String subject, Pageable pageable);
}
public Page<Teacher> getPage(@PathVariable("subject") String subject) {
// 第一种方法实例化 Pageable
Pageable pageable1 = PageRequest.of(1, 2);
//第二种实例化 Pageable
Sort sort = Sort.by(Sort.Direction.ASC, "age");
Pageable pageable2 = PageRequest.of(1, 2, sort);
//第三种实例化 Pageable
Pageable pageable3 = PageRequest.of(1, 2, Sort.Direction.DESC, "age");
//可以传入不同的 Pageable,测试效果
Page page = teacherRepositoty.getPage(subject, pageable3);
System.out.println(page.getTotalElements());
System.out.println(page.getTotalPages());
System.out.println(page.hasNext());
System.out.println(page.hasPrevious());
System.out.println(page.getNumberOfElements());
System.out.println(page.getSize());
return page;
}
public static PageRequest of(int page, int size) public static PageRequest of(int page, int size, Sort sort) 分页的同时还可以针对分页后的结果进行一个排序。 public static PageRequest of(int page, int size, Direction direction, String… properties) 直接针对字段进行排序。
jpa 使用 Specification
上面提供的各种 jpa 的使用方法已经相当的丰富了,可以根据自己的需求去选择,下面我们在来分析另一种 spring data jpa 查询数据的方法,使用 Specification 去处理数据:接口继承 JpaSpecificationExecutor
}
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);
}
分析 Specification
Specification 是一个函数式接口,里面有一个抽象的方法:Predicate 是用来建立 where 后的查寻条件的相当于上述sql语句的 age > 20。 Root 使用来定位具体的查询字段,比如 root.get(“age”) ,定位 age字段, CriteriaBuilder是用来构建一个字段的范围,相当于 > ,= ,<,and …. 等等 CriteriaQuery 可以用来构建整个 sql 语句,可以指定sql 语句中的 select 后的查询字段,也可以拼接 where , groupby 和 having 等复杂语句。
public List<Teacher> specification(@PathVariable("subject") String subject) {
//实例化 Specification 类
Specification specification = ((root, criteriaQuery, criteriaBuilder) -> {
// 构建查询条件
Predicate predicate = criteriaBuilder.equal(root.get("subject"), subject);
// 使用 and 连接上一个条件
predicate = criteriaBuilder.and(predicate, criteriaBuilder.greaterThan(root.get("age"), 21));
return predicate;
});
//使用查询
return teacherRepositoty.findAll(specification);
}
使用spring data jpa 的 Projection (投影映射)
该部分是很有趣的一部分,简单容易操作, Projection 是要解决什么问题呢?当我们使用 spring data jpa 查询数据的时候,有时候不需要返回所有字段的数据,我们只需要个别字段数据,这样使用 Projection 也是不错的选择,下面讲一下使用细则。定义一个接口
现在的需求是我只需要 Teacher 类对应的表 teacher 中的 name 和 age 的数据,其他数据不需要。定义一个如下的接口:String getName();
Integer getAge();
@Value("#{target.name +' and age is' + target.age}")
String getTotal();
}
使用自定义接口
定义好一个接口后,在查询方法中指定返回接口类型的数据即可,参考代码如下:// 返回 TeacherProjection 接口类型的数据
@Query("select t from Teacher t ")
List<TeacherProjection> getTeacherNameAndAge();
}
public List<TeacherProjection> projection() {
// 返回指定字段的数据
List<TeacherProjection> projections = teacherRepositoty.getTeacherNameAndAge();
// 打印字段值
projections.forEach(teacherProjection -> {
System.out.println(teacherProjection.getAge());
System.out.println(teacherProjection.getName());
System.out.println(teacherProjection.getTotal());
});
return projections;
}
继续学习,求一波关注
这篇文章很长,也写了很久,文中表达的观点个人都经过反复的验证,力求确保准确,如果文中表达有错误之处,欢迎指正,本文案例代码来自本人 github 仓库 https://github.com/kickcodeman/spring-data-jpa ,可以 clone 下来运行测试即可。路漫漫其修远矣,学习的路还很长,期待和你做朋友,一起探讨,一起进步。