1 JPA
JPA全称为Java Persistence API(Java持久层API),它是在 jdk 5中提出的Java持久化规范。它为开发人员提供了一种对象/关联映射工具,实现管理应用中的关系数据,从而简化Java对象的持久化工作。很多ORM框架都是实现了JPA的规范,比如:Hibernate、EclipseLink 等。
1.1 Java 持久层框架
Java 持久层框架访问数据库的方式分为两种。一种以 SQL 为核心,封装一定程度的 JDBC 操作,比如: MyBatis 框架。另一种是以 Java 实体类为核心,建立实体类和数据库表之间的映射关系,也就是ORM框架,比如:Hibernate、Spring Data JPA。
1.2 JPA 规范
ORM映射元数据:JPA支持XML和注解两种元数据形式。元数据用于描述对象和表之间的映射关系,框架会据此将实体对象持久化到数据库表中。
JPA 的API:用来操作实体对象,执行CRUD操作。对于简单的 CRUD 操作,开发人员可以不用写代码。
JPQL查询语言:以面向对象的方式来查询数据。
1.3 Hibernate
Hibernate 框架可以将应用中的数据模型对象映射到关系数据库表的技术。
JPA 是规范,而Hibernate是JPA的一种实现框架。
2 Spring Data JPA
Spring Data JPA 在实现了JPA规范的基础上封装的一套 JPA 应用框架。使用Spring Data JPA能够在不同的ORM框架之间方便地进行切换而不需要更改代码。Spring Data JPA 的目标是统一ORM框架的访问持久层操作,来提高开发效率。
Spring Data JPA只是一个抽象层,主要用于减少为各种持久层存储实现数据访问层所需的样板代码量。它的 JPA 实现层就是采用 Hibernate 框架实现的。
2.1 引入依赖包
在 Spring Boot 应用中,只需要打开 pom.xml 加入一个 Spring Data JPA 依赖即可。 这个依赖不仅会引入 Spring Data JPA ,还会传递性地将 Hibernate 作为 JPA 实现引入进来。
org.springframework.boot
spring-boot-starter-data-jpa
2.2 实体类注解
(1)@Entity
类注解,用于标识这个实体类是一个JPA实体。
(2)@Table(name = "自定义表名")
类注解,用于自定义实体类在数据库中所对应的表名,默认是实体类名。特别是那些被作为数据库关键字的实体类名,就会用到这个注解来指定表名。
(3)@Id
类变量注解,用于指定主键。
(4)@GeneratedValue
类变量注解,用于指定主键的生成策略。
它包含strategy属性,具体说明如下:
strategy | 说明 |
---|---|
AUTO | 由程序控制,默认选项 |
IDENTITY | 由数据库自增长模式生成,适用于 MySQL |
SEQUENCE | 由数据库序列生成,适用于 Oracle |
Table | 由指定的表生成 |
(5)@Basic
指定类变量读取方法到数据库表字段的映射关系。对于没有任何特殊注解的getXxxx()方法,默认带有 @Basic 注解。也就是说,除非特殊情况,否则所有的类变量都带有 @Basic 注解,这些变量都映射到指定的表字段中。
@Basic 注解有一个 fetch 属性用于表示读取策略。策略有两种EAGER和LAZY,它们分别表示为主动读取与懒加载。默认为 EAGER。
(6)@Column
表示列的说明,如果字段名与列名相同,则可以省略。
@Column 注解拥有以下属性:
属性 | 说明 |
---|---|
name | 在数据库表中所对应字段的名称。 |
length | 字段的长度。当字段的类型为varchar时,才有效;默认为255个字符。 |
nullable | 是否可以为null值,默认是true。 |
precision和scale | 表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。 |
(7)@Transient
类变量注解,表示该变量不是一个到数据库表的字段映射。因为类变量的默认注解是 @Basic,所以某些场景下的非持久化类变量就会用到该注解。
(8)@Temporal
类变量注解(也可用在 getXxx 方法上),表示时间格式。具体说明如下:
语法 | 说明 |
---|---|
@Temporal(TemporalType.DATE) | 日期,形如 2020-10-10 |
@Temporal(TemporalType.TIME) | 时间,形如 10:10:10 |
@Temporal(TemporalType.TIMESTAMP) | 默认值;日期+时间,形如 2020-10-10 10:10:10 |
Craig Walls 举了这样一个实体类代码示例:
@Data
@RequiredArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@Entity
public class Ingredient {
@Id
private final String id;
private final String name;
private final Type type;
public static enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
示例中除了应用 @Entity 与 @Id 注解之外,还在类级别添加了 @NoArgsConstructor 注解 。因为 JPA 需要实体类提供一个无参构造器,所以这里利用 Lombok 的 @NoArgsConstructor 注解来生成这个构造器。
@NoArgsConstructor 注解还可以将这个无参构造器私有化(access = AccessLevel.PRIVATE),这样外部就不能直接调用。
因为这个类的变量 id、name 与 type 还未初始化,所以我们还需要把 force 设置为 true,将其初始化为 null。
虽然 @Data 注解会为我们添加一个有参构造器,但因为之前添加了 @NoArgsConstructor 注解,所以有参构造器就没了。因此,必须再添加一个 @RequiredArgsConstructor 注解,强制生成一个有参构造器。
2.3 实体类关系注解
Spring Data JPA 有四种关系注解,它们分别是 @OneToOne、@OneToMany、@ManyToOne 和@ManyToMany。
这四种关系注解都有 fetch 与 cascade 两种属性。
fetch 属性用于指定数据延迟加载策略:
策略 | 说明 |
---|---|
FetchType.LAZY | 默认值;懒加载 |
FetchType.EAGER | 立即加载 |
cascade 属性用于指定级联策略:
策略 | 说明 |
---|---|
CascadeType.PERSIST | 级联持久化;保存父实体时,也会同时保存子实体。 |
CascadeType.MERGE | 级联合并;修改了子实体,保存父实体时也会同时保存子实体(常用)。 |
CascadeType.REMOVE | 级联删除;删除父实体时,会级联删除关联的子实体。 |
CascadeType.REFRESH | 级联刷新;获取父实体的同时也会重新获取最新的子实体。 |
CascadeType.ALL | 以上四种策略 |
无 | 默认值 |
因为这四种注解只能表示实体之间几对几的关系,指定与所操作实体相关联的数据库表中的列字段,就需要用到 @JoinColumn 注解。
假设有这样的一组实体关系。一个用户拥有一个密码;而一个用户属于一个部门,一个部门下拥有多个用户;一个用户可以拥有多个角色,而一个角色下也可以包含多个用户。
(1)@OneToOne
@OneToOne 用来表示一对一的关系,放置在主导类上。比如用户类会有一个指定密码表的主键 pwd_id,将 @OneToOne 放置在用户类的 pwd 字段上,就可以表示用户类与密码类是一对一的关系,并且主导类是用户类。
@OneToOne
@JoinColumn(name = "pwd_id")
private Password pwd;
也可以不使用 @JoinColumn,Hibernate 会自动在用户表生成关联字段,字段默认的命名规则为 “附属类名_附属主键”,如:password_id。
有时候会看到注解 @PrimaryKeyJoinColumn(name = "...") ,其实它本质上是 @Id 与 @JoinColumn(name = "...") 的组合体。
(2)@OneToMany
在分析用户与部门之间关系时,会发现一个用户只能属于一个部门,而一个部门可以包含有多个用户。所以,如果站在部门的角度来看
在分析用户与部门之间的关系时,一个员工只能属于一个部门,但是一个部门可以包含有多个员工,如果我们站在部门的角度来看,部门与员工之间就是一对多的关系,在部门实体类 Department 上添加如下注解:
1. @OneToMany
2. @JoinColumn(name = "department_id")
3. private List user;
如果不指定@JoinColumn 注解,Hibernate会自动生成一张中间表来对用户和部门进行绑定,这张中间表默认的命名规则为:实体类表名_实体类中指定的属性名。例如,部门表名为 t_department ,部门实体类中关联的用户集合属性名为 user,则默认生成的中间表名为:t_department_user。
在实践中,我们推荐使用@JoinTable注解来直接指定中间表:
@OneToMany
@JoinTable(name = " t_department_user ", joinColumns = {
@JoinColumn(name = "department_id") }, inverseJoinColumns = { @JoinColumn(name = "user_id") })
private List users;
@JoinColumn 中的 name 属性用于指定当前实体类(部门)所对应表的关联 ID;inverseJoinColumns 属性用于指定所关联的实体类表(员工)的关联 ID,里面内嵌了 @JoinColumn 注解。
(3)@ManyToOne(多对一)
如果我们站在用户的角度来看待用户与部门之间的关系时,它们之间就变成了多对一的关系(多个用户隶属于一个部门),在用户实体类 User 上添加如下注解:
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
(4)@ManyToMany(多对多)
用户与角色之间是多对多的关系,因为一个用户可以拥有多个角色,而一个角色也可以隶属于多个员工。多对多关系一般通过创建中间表来进行关联,这时就会用到 @JoinTable注解。
在用户实体类中添加如下注解:
@ManyToMany
@JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = {
@JoinColumn(name = "role_id") })
private List roles;
在角色实体类中添加如下注解:
@ManyToMany
@JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = {
@JoinColumn(name = "user_id") })
private List users;