说说如何使用 Spring Data JPA 持久化数据

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 规范

  1. ORM映射元数据:JPA支持XML和注解两种元数据形式。元数据用于描述对象和表之间的映射关系,框架会据此将实体对象持久化到数据库表中。

  2. JPA 的API:用来操作实体对象,执行CRUD操作。对于简单的 CRUD 操作,开发人员可以不用写代码。

  3. 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;

你可能感兴趣的:(说说如何使用 Spring Data JPA 持久化数据)