1 JPA
JPA全稱為Java Persistence API(Java持久層API),它是在 jdk 5中提出的Java持久化規(guī)范。它為開發(fā)人員提供了一種對象/關(guān)聯(lián)映射工具,實現(xiàn)管理應(yīng)用中的關(guān)系數(shù)據(jù),從而簡化Java對象的持久化工作。很多ORM框架都是實現(xiàn)了JPA的規(guī)范,比如:Hibernate、EclipseLink 等。
1.1 Java 持久層框架
Java 持久層框架訪問數(shù)據(jù)庫的方式分為兩種。一種以 SQL 為核心,封裝一定程度的 JDBC 操作,比如: MyBatis 框架。另一種是以 Java 實體類為核心,建立實體類和數(shù)據(jù)庫表之間的映射關(guān)系,也就是ORM框架,比如:Hibernate、Spring Data JPA。

1.2 JPA 規(guī)范
ORM映射元數(shù)據(jù):JPA支持XML和注解兩種元數(shù)據(jù)形式。元數(shù)據(jù)用于描述對象和表之間的映射關(guān)系,框架會據(jù)此將實體對象持久化到數(shù)據(jù)庫表中。
JPA 的API:用來操作實體對象,執(zhí)行CRUD操作。對于簡單的 CRUD 操作,開發(fā)人員可以不用寫代碼。
JPQL查詢語言:以面向?qū)ο蟮姆绞絹聿樵償?shù)據(jù)。
1.3 Hibernate
Hibernate 框架可以將應(yīng)用中的數(shù)據(jù)模型對象映射到關(guān)系數(shù)據(jù)庫表的技術(shù)。
JPA 是規(guī)范,而Hibernate是JPA的一種實現(xiàn)框架。
2 Spring Data JPA
Spring Data JPA 在實現(xiàn)了JPA規(guī)范的基礎(chǔ)上封裝的一套 JPA 應(yīng)用框架。使用Spring Data JPA能夠在不同的ORM框架之間方便地進(jìn)行切換而不需要更改代碼。Spring Data JPA 的目標(biāo)是統(tǒng)一ORM框架的訪問持久層操作,來提高開發(fā)效率。
Spring Data JPA只是一個抽象層,主要用于減少為各種持久層存儲實現(xiàn)數(shù)據(jù)訪問層所需的樣板代碼量。它的 JPA 實現(xiàn)層就是采用 Hibernate 框架實現(xiàn)的。

2.1 引入依賴包
在 Spring Boot 應(yīng)用中,只需要打開 pom.xml 加入一個 Spring Data JPA 依賴即可。 這個依賴不僅會引入 Spring Data JPA ,還會傳遞性地將 Hibernate 作為 JPA 實現(xiàn)引入進(jìn)來。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2.2 實體類注解
(1)@Entity
類注解,用于標(biāo)識這個實體類是一個JPA實體。
(2)@Table(name = "自定義表名")
類注解,用于自定義實體類在數(shù)據(jù)庫中所對應(yīng)的表名,默認(rèn)是實體類名。特別是那些被作為數(shù)據(jù)庫關(guān)鍵字的實體類名,就會用到這個注解來指定表名。
(3)@Id
類變量注解,用于指定主鍵。
(4)@GeneratedValue
類變量注解,用于指定主鍵的生成策略。
它包含strategy屬性,具體說明如下:
| strategy | 說明 |
|---|---|
| AUTO | 由程序控制,默認(rèn)選項 |
| IDENTITY | 由數(shù)據(jù)庫自增長模式生成,適用于 MySQL |
| SEQUENCE | 由數(shù)據(jù)庫序列生成,適用于 Oracle |
| Table | 由指定的表生成 |
(5)@Basic
指定類變量讀取方法到數(shù)據(jù)庫表字段的映射關(guān)系。對于沒有任何特殊注解的getXxxx()方法,默認(rèn)帶有 @Basic 注解。也就是說,除非特殊情況,否則所有的類變量都帶有 @Basic 注解,這些變量都映射到指定的表字段中。
@Basic 注解有一個 fetch 屬性用于表示讀取策略。策略有兩種EAGER和LAZY,它們分別表示為主動讀取與懶加載。默認(rèn)為 EAGER。
(6)@Column
表示列的說明,如果字段名與列名相同,則可以省略。
@Column 注解擁有以下屬性:
| 屬性 | 說明 |
|---|---|
| name | 在數(shù)據(jù)庫表中所對應(yīng)字段的名稱。 |
| length | 字段的長度。當(dāng)字段的類型為varchar時,才有效;默認(rèn)為255個字符。 |
| nullable | 是否可以為null值,默認(rèn)是true。 |
| precision和scale | 表示精度,當(dāng)字段類型為double時,precision表示數(shù)值的總長度,scale表示小數(shù)點所占的位數(shù)。 |
(7)@Transient
類變量注解,表示該變量不是一個到數(shù)據(jù)庫表的字段映射。因為類變量的默認(rèn)注解是 @Basic,所以某些場景下的非持久化類變量就會用到該注解。
(8)@Temporal
類變量注解(也可用在 getXxx 方法上),表示時間格式。具體說明如下:
| 語法 | 說明 |
|---|---|
| @Temporal(TemporalType.DATE) | 日期,形如 2020-10-10 |
| @Temporal(TemporalType.TIME) | 時間,形如 10:10:10 |
| @Temporal(TemporalType.TIMESTAMP) | 默認(rèn)值;日期+時間,形如 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
}
}
示例中除了應(yīng)用 @Entity 與 @Id 注解之外,還在類級別添加了 @NoArgsConstructor 注解 。因為 JPA 需要實體類提供一個無參構(gòu)造器,所以這里利用 Lombok 的 @NoArgsConstructor 注解來生成這個構(gòu)造器。
@NoArgsConstructor 注解還可以將這個無參構(gòu)造器私有化(access = AccessLevel.PRIVATE),這樣外部就不能直接調(diào)用。
因為這個類的變量 id、name 與 type 還未初始化,所以我們還需要把 force 設(shè)置為 true,將其初始化為 null。
雖然 @Data 注解會為我們添加一個有參構(gòu)造器,但因為之前添加了 @NoArgsConstructor 注解,所以有參構(gòu)造器就沒了。因此,必須再添加一個 @RequiredArgsConstructor 注解,強(qiáng)制生成一個有參構(gòu)造器。
2.3 實體類關(guān)系注解
Spring Data JPA 有四種關(guān)系注解,它們分別是 @OneToOne、@OneToMany、@ManyToOne 和@ManyToMany。
這四種關(guān)系注解都有 fetch 與 cascade 兩種屬性。
fetch 屬性用于指定數(shù)據(jù)延遲加載策略:
| 策略 | 說明 |
|---|---|
| FetchType.LAZY | 默認(rèn)值;懶加載 |
| FetchType.EAGER | 立即加載 |
cascade 屬性用于指定級聯(lián)策略:
| 策略 | 說明 |
|---|---|
| CascadeType.PERSIST | 級聯(lián)持久化;保存父實體時,也會同時保存子實體。 |
| CascadeType.MERGE | 級聯(lián)合并;修改了子實體,保存父實體時也會同時保存子實體(常用)。 |
| CascadeType.REMOVE | 級聯(lián)刪除;刪除父實體時,會級聯(lián)刪除關(guān)聯(lián)的子實體。 |
| CascadeType.REFRESH | 級聯(lián)刷新;獲取父實體的同時也會重新獲取最新的子實體。 |
| CascadeType.ALL | 以上四種策略 |
| 無 | 默認(rèn)值 |
因為這四種注解只能表示實體之間幾對幾的關(guān)系,指定與所操作實體相關(guān)聯(lián)的數(shù)據(jù)庫表中的列字段,就需要用到 @JoinColumn 注解。

假設(shè)有這樣的一組實體關(guān)系。一個用戶擁有一個密碼;而一個用戶屬于一個部門,一個部門下?lián)碛卸鄠€用戶;一個用戶可以擁有多個角色,而一個角色下也可以包含多個用戶。
(1)@OneToOne
@OneToOne 用來表示一對一的關(guān)系,放置在主導(dǎo)類上。比如用戶類會有一個指定密碼表的主鍵 pwd_id,將 @OneToOne 放置在用戶類的 pwd 字段上,就可以表示用戶類與密碼類是一對一的關(guān)系,并且主導(dǎo)類是用戶類。
@OneToOne
@JoinColumn(name = "pwd_id")
private Password pwd;
也可以不使用 @JoinColumn,Hibernate 會自動在用戶表生成關(guān)聯(lián)字段,字段默認(rèn)的命名規(guī)則為 “附屬類名_附屬主鍵”,如:password_id。
有時候會看到注解 @PrimaryKeyJoinColumn(name = "...") ,其實它本質(zhì)上是 @Id 與 @JoinColumn(name = "...") 的組合體。
(2)@OneToMany
在分析用戶與部門之間關(guān)系時,會發(fā)現(xiàn)一個用戶只能屬于一個部門,而一個部門可以包含有多個用戶。所以,如果站在部門的角度來看
在分析用戶與部門之間的關(guān)系時,一個員工只能屬于一個部門,但是一個部門可以包含有多個員工,如果我們站在部門的角度來看,部門與員工之間就是一對多的關(guān)系,在部門實體類 Department 上添加如下注解:
1. @OneToMany
2. @JoinColumn(name = "department_id")
3. private List<User> user;
如果不指定@JoinColumn 注解,Hibernate會自動生成一張中間表來對用戶和部門進(jìn)行綁定,這張中間表默認(rèn)的命名規(guī)則為:實體類表名_實體類中指定的屬性名。例如,部門表名為 t_department ,部門實體類中關(guān)聯(lián)的用戶集合屬性名為 user,則默認(rèn)生成的中間表名為: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 屬性用于指定當(dāng)前實體類(部門)所對應(yīng)表的關(guān)聯(lián) ID;inverseJoinColumns 屬性用于指定所關(guān)聯(lián)的實體類表(員工)的關(guān)聯(lián) ID,里面內(nèi)嵌了 @JoinColumn 注解。
(3)@ManyToOne(多對一)
如果我們站在用戶的角度來看待用戶與部門之間的關(guān)系時,它們之間就變成了多對一的關(guān)系(多個用戶隸屬于一個部門),在用戶實體類 User 上添加如下注解:
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
(4)@ManyToMany(多對多)
用戶與角色之間是多對多的關(guān)系,因為一個用戶可以擁有多個角色,而一個角色也可以隸屬于多個員工。多對多關(guān)系一般通過創(chuàng)建中間表來進(jìn)行關(guān)聯(lián),這時就會用到 @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;