如果參與過小型團隊的生產項目開發,相信你一定被不統一的數據表弄的暈頭轉向過。代碼`pull`到本地后,各種`error`便迫不急待的跳出來與我們會面了。產生問題的原因也很簡單:代碼與地數據環境不一致。
本節中,讓我們使用另外一種集成度高且使用較簡單的方法 --- `Spring Data JPA`來使用`JAVA`代碼進行數據表的維護。
# ER圖
在建立數據表以前,我們首先需要建立數據表圖----ER(Entity-relationship model)圖。ER圖又稱為實體關系圖,描述的是數據表(實體)信息及數據表(實體)之間的關聯信息。當前系統存在兩個數據表:教師表、班級表。兩個表的關系為:每個班級必然對應1個教師,每個教師可能對應0個或多個班級,所以`教師`與`班級`的關系是`1 : 0..1`。
用ER圖來表示為:

除了這種表現形式(IE)以外,還可以用以下形式(IDEF1X)來表示:

它們只是表現的形式不同而已,所表達的含意是一樣的。
在團隊的項目中,我們在創建ER圖時為了更好的和類圖相對應,我們規定:
| ER圖(JAVA)類型 | 數據表類型 | 備注 |
| ---- | ---- | ---- |
| Long | bigint | Long在java中為64位,bigint在mysql同為64位 |
| String | varchar(255) | 假設字符串的默認長度為255 |
# 自動建表
參考ER圖,下面來展示如何使用`Spring Data JPA`來進行數據表的維護。
## 引用依賴
由于`Spring Data JPA`并不屬于`Spring Boot`的核心模塊,所以在使用JPA時,需要在`pom.xml`中添加如下依賴。
pom.xml
```
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> ? ?
<groupId>org.springframework.boot</groupId> ? ?
<artifactId>spring-boot-starter-data-jpa</artifactId> ? ?
</dependency> ?
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
```
* ? 添加新的依賴.
* ? 依賴的包所在的班級。
* ? 依賴的包的名字。
`pom.xml`變更后IDEA會自動在右下角提示是否自動導入該包,選擇`是`即可,這樣以后如果該文件再有變更,IDEA則會自動的為我們處理這些變更。如果IDEA沒有為我們自動處理或者我們想手動的處理這些依賴,那么也可以打開控制臺執行:`mvn install`來手動完成依賴更新。
> IDEA處理依賴的時間長短取決于我們的網絡狀況,可以點擊軟件右下角的當前任務來查看處理進度。
## 配置信息
我們找到`src/main/resources/application.properties`,并增加:`spring.jpa.hibernate.ddl-auto`配置項:

配置項有5個:`create`創建數據表、`create-drop`先創建數據表程序終止時刪除數據表、`none`什么也不做、`update`更新數據表、`validate`較驗證表。
在此,我們暫時使用`create-drop`做為配置項。
我們雖然在前面配置過數據的連接信息`spring.datasource.url`為`jdbc:mysql:`,即已經指名了數據庫使用的為`mysql` 。但`mysql`的版本眾多,不同的版本間存在一定的差異,為了讓`JPA`能夠更好的自動處理這些差異,我們還需要配置數據庫`方言`信息。
src/main/resources/application.properties
```
spring.datasource.url=jdbc:mysql://localhost:3307/yunzhi_spring_boot
spring.datasource.username=root
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect ?
```
* ? 告知JPA,我們使用的是`mysql5.7`版本。
如果你使用的是`mysql5.5`或`mysql5.6`,請將上述配置修正為`MySQL55Dialect`。如果你使用的是`MariaDB`請按下圖對應修改:

**注意:** 類擬于~~MySQL57InnoDBDialect~~這樣的標識表示該類的存在為兼容歷史版本,其已經被當前版本棄用而不建議使用了。
## 建立對應的類
我們先建立個`package`,命名為`entity`。

然后在該包下新建Klass類。
```
.
├── SpringBootStudyApplication.java
├── Teacher.java
├── TeacherController.java
└── entity
└── Klass.java
```
代碼如下:
entity/Klass.java
```
package com.mengyunzhi.springBootStudy.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity ?
public class Klass {
@Id ?
@GeneratedValue(strategy = GenerationType.IDENTITY) ? ?
private Long id; ?
private Long teacherId; ?
private String name; ?
public Klass() { ?
}
public Long getId() { ?
return id;
}
public void setId(Long id) { ?
this.id = id;
}
public Long getTeacherId() {
return teacherId;
}
public void setTeacherId(Long teacherId) {
this.teacherId = teacherId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
* ? 說明Klass是一個與數據表相關的類,在系統啟動時將參考該類來`create 生成`、`update 更新`或`validate 驗證`數據表
* ? 定義相關字段
* ? 國際慣例,構建空的構建函數。
* ? 國際慣例,設置set/get函數。
* ? 說明此數據表的主鍵為id。
* ?? 定義主健的自增屬性。
## 測試
我們重新啟動項目,并使用navicate查看數據庫:


通過觀察可得JPA自動實現了以下功能:
* 將JAVA類`Klass`轉換為`klass`表。
* 將JAVA類中的屬性名轉換成了字段名。
* 將`@Id`注解的屬性轉換為主鍵且`not null`
* 將`@GeneratedValue(strategy = GenerationType.IDENTITY) `注解轉換為自增。
* 在轉換的過程中,將駝峰式命名轉換為下劃線式命名。
未實現的功能:
* 未設置數據表外鍵`teacher_id`。
此時,我們如果點擊項目停止按鈕:

來停止項目,由于我們在前面設置了`create-drop`屬性,所以剛剛為我們自動創建的數據表`klass`會被自動刪除掉。這樣便達到了:在團隊開發中,只要保證`pull`的代碼是最新的,那么數據表必然也會是同步更新的。
# 設置外鍵
`JPA`只所以沒有成功的設置外鍵,是由于對`JPA`而言只有使用`@Entity`注解的類才被認為是數據表,所以其認為當前僅有一個`klass`表,在沒有`teacher`表的前提下,`JPA`也就當然的無法為我們自動添加此外鍵了。為了讓`JPA`能夠看到`teacher`表,我們可以參考`Klass`類建立一個`Teacher`類。寫到這我們發現在前面的章節中,我們已經建立了如下的`Teacher`類了:
Teacher.java
```java
package com.mengyunzhi.springBootStudy;
/**
* 教師模型,用于更方便的存儲查表后返回的數據
*/
public class Teacher {
private Long id;
private String name;
private Boolean sex;
private String username;
private String email;
private Long createTime;
private Long updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getSex() {
return sex;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public Long getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Long updateTime) {
this.updateTime = updateTime;
}
}
```
與`Klass`相比較,我們只需要增加以下信息該類便自動對應起了數據表:
* ★ 說明該類對應數據表。
* ☆ 構建空的構造函數。
* ★ 設置主鍵。
* ★ 定義主健的自增屬性。
> ☆非必要條件;★必要條件。
參考上述四點對`Teacher`表進行改造如下:
```
package com.mengyunzhi.springBootStudy;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* 教師模型,用于更方便的存儲查表后返回的數據
*/
@Entity ①
public class Teacher {
@Id ③
@GeneratedValue(strategy = GenerationType.IDENTITY) ④
private Long id;
private String name;
private Boolean sex;
private String username;
private String email;
private Long createTime;
private Long updateTime;
public Teacher() { ②
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getSex() {
return sex;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public Long getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Long updateTime) {
this.updateTime = updateTime;
}
}
```
## 測試
我們刪除數據庫中所有的數據表,然后重新運行后臺項目,驗證是否為我們生成了相應的數據表并對應生成了相應的字段。

和我們的預期相一致,JPA非常出色的完成了此項任務。前且當遇到`Boolean`類型時,`JPA`自動的將其轉換為`bit(1)`。
## 添加外鍵
實體(數據表)與實體間的關系分為以下四種:1對1,1對多,多對1,多對多;在英文中分別是OneToOne、OneToMany、ManyToOne、ManyToMany;在ER圖中,我們還會用1:1、1:N、N:1、 M:N 來表示。在`JPA`聲明外鍵只需要:
entity/Klass.java
```
private Long teacherId; ?
@ManyToOne ? ?
private Teacher teacher; ? ?
// 省略了getter/setter函數
```
* ? 類型定義為Teacher實體類
* ? 該字段與Teacher實體關聯,關系為多對1。
## 測試
測試是否生成了相應的字段:

不止如此,`JPA`還自動為我們添加了索引:

同時自動為我們添加了外鍵:

這已經完成的符合并超出了我們的預期。
# Spring Data JPA
再認識了`Spring Data JPA`以后,我們再深入地了解下`JPA ---- Java Persistence API JAVA持久層應用接口`。
什么是`持久層`呢,它的作用是什么呢?簡單來講`持久層`的作用就是通過`持久化`的操作將數據保存到數據庫當中。`持久化`其實就是`保存數據`的另一種描述方法,只所以這么講我猜是由于:在數據成功的保存到數據庫中以前,數據是在內存中保存的,我們知道內存中的數據是可能隨時刪除、變更以及被拋棄丟失的(比如突然的停電了),而保存到數據庫中以后即使發生了停電的現象,該數據也會不丟失。就像現實生活中我們會把一些我們認為重要的東西記在筆記本上,免得哪天想用這個知識的時候忘記了,這個記筆記的過程在計算機中就做`持久化`。
由于`JPA`本質上是個`接口`,而`接口`的本質就是`規范`。所以通俗來說,`JPA`就是一個在`JAVA`程序下進行數據訪問的一種`規范`。而實現這個規范的技術有很多,比如我們剛剛使用的`Spring Data JPA`,除此以外,比較出名的`JPA`還有`hibernate`及`mybatis`。實際上`Spring Data JPA`也是基于`hibernate`的。
為了更好的理解接口與實現我們再拿USB做個例子:其實USB2.0 ,USE3.0都是規范,確切的來說他們就是幾張紙一堆文字,規定了接口的大小、形狀、有幾個觸點、每個觸點傳輸什么數據以及如何傳輸數據等等等等。而我們使用的USE鼠標、U盤等都是在這個規范下的實現。他們的設計遵從了USE規范,從而使其能夠與其它同樣遵守該規范的設備協作運行。
那么什么是`Spring Data JPA`呢?官方如是說:
*****
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.
Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.
*****
大概的意思就是說,`Spring Data JPA`也是`JPA`,它更簡單、更強大。
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 什么是JPA | [https://baike.baidu.com/item/JPA](https://baike.baidu.com/item/JPA) | 10 |
| What's JPA | [https://en.wikipedia.org/wiki/Java\_Persistence\_API](https://en.wikipedia.org/wiki/Java_Persistence_API) | 20 |
| Spring Data JPA | [https://spring.io/projects/spring-data-jpa](https://spring.io/projects/spring-data-jpa) | 5 |
| Accessing Data with JPA | [https://spring.io/guides/gs/accessing-data-jpa/](https://spring.io/guides/gs/accessing-data-jpa/) | 15 |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.1) | - |
- 序言
- 第一章:Hello World
- 第一節:Angular準備工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二節:Hello Angular
- 第三節:Spring Boot準備工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四節:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven國內源配置
- 4 package與import
- 第五節:Hello Spring Boot + Angular
- 1 依賴注入【前】
- 2 HttpClient獲取數據【前】
- 3 數據綁定【前】
- 4 回調函數【選學】
- 第二章 教師管理
- 第一節 數據庫初始化
- 第二節 CRUD之R查數據
- 1 原型初始化【前】
- 2 連接數據庫【后】
- 3 使用JDBC讀取數據【后】
- 4 前后臺對接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三節 CRUD之C增數據
- 1 新建組件并映射路由【前】
- 2 模板驅動表單【前】
- 3 httpClient post請求【前】
- 4 保存數據【后】
- 5 組件間調用【前】
- 第四節 CRUD之U改數據
- 1 路由參數【前】
- 2 請求映射【后】
- 3 前后臺對接【前】
- 4 更新數據【前】
- 5 更新某個教師【后】
- 6 路由器鏈接【前】
- 7 觀察者模式【前】
- 第五節 CRUD之D刪數據
- 1 綁定到用戶輸入事件【前】
- 2 刪除某個教師【后】
- 第六節 代碼重構
- 1 文件夾化【前】
- 2 優化交互體驗【前】
- 3 相對與絕對地址【前】
- 第三章 班級管理
- 第一節 JPA初始化數據表
- 第二節 班級列表
- 1 新建模塊【前】
- 2 初識單元測試【前】
- 3 初始化原型【前】
- 4 面向對象【前】
- 5 測試HTTP請求【前】
- 6 測試INPUT【前】
- 7 測試BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后臺對接【前】
- 第三節 新增班級
- 1 初始化【前】
- 2 響應式表單【前】
- 3 測試POST請求【前】
- 4 JPA插入數據【后】
- 5 單元測試【后】
- 6 惰性加載【前】
- 7 對接【前】
- 第四節 編輯班級
- 1 FormGroup【前】
- 2 x、[x]、{{x}}與(x)【前】
- 3 模擬路由服務【前】
- 4 測試間諜spy【前】
- 5 使用JPA更新數據【后】
- 6 分層開發【后】
- 7 前后臺對接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五節 選擇教師組件
- 1 初始化【前】
- 2 動態數據綁定【前】
- 3 初識泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再識單元測試【前】
- 7 其它問題
- 第六節 刪除班級
- 1 TDD【前】
- 2 TDD【后】
- 3 前后臺對接
- 第四章 學生管理
- 第一節 引入Bootstrap【前】
- 第二節 NAV導航組件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三節 footer組件【前】
- 第四節 歡迎界面【前】
- 第五節 新增學生
- 1 初始化【前】
- 2 選擇班級組件【前】
- 3 復用選擇組件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校驗【后】
- 7 唯一性校驗【后】
- 8 @PrePersist【后】
- 9 CM層開發【后】
- 10 集成測試
- 第六節 學生列表
- 1 分頁【后】
- 2 HashMap與LinkedHashMap
- 3 初識綜合查詢【后】
- 4 綜合查詢進階【后】
- 5 小試綜合查詢【后】
- 6 初始化【前】
- 7 M層【前】
- 8 單元測試與分頁【前】
- 9 單選與多選【前】
- 10 集成測試
- 第七節 編輯學生
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 功能開發【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成測試
- 7 @Input 異步傳值【前】
- 8 值傳遞與引入傳遞
- 9 @PreUpdate【后】
- 10 表單驗證【前】
- 第八節 刪除學生
- 1 CSS選擇器【前】
- 2 confirm【前】
- 3 功能開發與測試【后】
- 4 集成測試
- 5 定制提示框【前】
- 6 引入圖標庫【前】
- 第九節 集成測試
- 第五章 登錄與注銷
- 第一節:普通登錄
- 1 原型【前】
- 2 功能設計【前】
- 3 功能設計【后】
- 4 應用登錄組件【前】
- 5 注銷【前】
- 6 保留登錄狀態【前】
- 第二節:你是誰
- 1 過濾器【后】
- 2 令牌機制【后】
- 3 裝飾器模式【后】
- 4 攔截器【前】
- 5 RxJS操作符【前】
- 6 用戶登錄與注銷【后】
- 7 個人中心【前】
- 8 攔截器【后】
- 9 集成測試
- 10 單例模式
- 第六章 課程管理
- 第一節 新增課程
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 async管道【前】
- 4 優雅的測試【前】
- 5 功能開發【前】
- 6 實體監聽器【后】
- 7 @ManyToMany【后】
- 8 集成測試【前】
- 9 異步驗證器【前】
- 10 詳解CORS【前】
- 第二節 課程列表
- 第三節 果斷
- 1 初始化【前】
- 2 分頁組件【前】
- 2 分頁組件【前】
- 3 綜合查詢【前】
- 4 綜合查詢【后】
- 4 綜合查詢【后】
- 第節 班級列表
- 第節 教師列表
- 第節 編輯課程
- TODO返回機制【前】
- 4 彈出框組件【前】
- 5 多路由出口【前】
- 第節 刪除課程
- 第七章 權限管理
- 第一節 AOP
- 總結
- 開發規范
- 備用