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

参考链接: https://juejin.cn/post/6876601825244479502

3个月前 182次点击 来自 后端

标签: JPA

原文链接:说说如何使用 Spring Data JPA 持久化数据

Spring Data JPA

Spring Data JPA 在实现了JPA规范的基础上封装的一套 JPA 应用框架。使用Spring Data JPA能够在不同的ORM框架之间方便地进行切换而不需要更改代码。Spring Data JPA 的目标是统一ORM框架的访问持久层操作,来提高开发效率。

Spring Data JPA只是一个抽象层,主要用于减少为各种持久层存储实现数据访问层所需的样板代码量。它的 JPA 实现层就是采用 Hibernate 框架实现的。

1 常见配置

#项目端口的常用配置
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 : 验证数据库和实体类的属性是否匹配,不匹配将会报错。

2.1 引入依赖包

在 Spring Boot 应用中,只需要打开 pom.xml 加入一个 Spring Data JPA 依赖即可。 这个依赖不仅会引入 Spring Data JPA ,还会传递性地将 Hibernate 作为 JPA 实现引入进来。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
复制代码

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 上添加如下注解:

@OneToMany
@JoinColumn(name = "department_id")
private List<User> 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<User> 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<Role> roles;

在角色实体类中添加如下注解:

@ManyToMany
@JoinTable(name = "t_user_role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = {@JoinColumn(name = "user_id") })
private List<User> users;

Made with in Shangrao,China By Devler.

Copyright © Devler 2012 - 2022

赣ICP备19009883号-1

Top ↑