欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何在 Spring Boot 中 读写数据

程序员文章站 2022-04-07 17:33:10
...

写在前面:2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上,内容详细,图文并茂,有需要学习的朋友可以Star一下!
GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master

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。

如何在 Spring Boot 中 读写数据

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 框架实现的。

如何在 Spring Boot 中 读写数据

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属性,具体说明如下:

如何在 Spring Boot 中 读写数据

(5)@Basic

指定类变量读取方法到数据库表字段的映射关系。对于没有任何特殊注解的getXxxx()方法,默认带有 @Basic 注解。也就是说,除非特殊情况,否则所有的类变量都带有 @Basic 注解,这些变量都映射到指定的表字段中。

@Basic 注解有一个 fetch 属性用于表示读取策略。策略有两种EAGER和LAZY,它们分别表示为主动读取与懒加载。默认为 EAGER。

(6)@Column

表示列的说明,如果字段名与列名相同,则可以省略。

@Column 注解拥有以下属性:

如何在 Spring Boot 中 读写数据

(7)@Transient

类变量注解,表示该变量不是一个到数据库表的字段映射。因为类变量的默认注解是 @Basic,所以某些场景下的非持久化类变量就会用到该注解。

(8)@Temporal

类变量注解(也可用在 getXxx 方法上),表示时间格式。具体说明如下:

如何在 Spring Boot 中 读写数据

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 属性用于指定数据延迟加载策略:

如何在 Spring Boot 中 读写数据

cascade 属性用于指定级联策略: 策略 | 说明 — | — CascadeType.PERSIST | 级联持久化;保存父实体时,也会同时保存子实体。 CascadeType.MERGE | 级联合并;修改了子实体,保存父实体时也会同时保存子实体(常用)。 CascadeType.REMOVE | 级联删除;删除父实体时,会级联删除关联的子实体。 CascadeType.REFRESH | 级联刷新;获取父实体的同时也会重新获取最新的子实体。 CascadeType.ALL | 以上四种策略 无 | 默认值

因为这四种注解只能表示实体之间几对几的关系,指定与所操作实体相关联的数据库表中的列字段,就需要用到 @JoinColumn 注解。

如何在 Spring Boot 中 读写数据

假设有这样的一组实体关系。一个用户拥有一个密码;而一个用户属于一个部门,一个部门下拥有多个用户;一个用户可以拥有多个角色,而一个角色下也可以包含多个用户。

(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> 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;