在前面的章節中,我們在`spring data JPA`的幫助下,使用`Teacher`類來映射了`teacher`表,使用`Klass`類來映射了`klass`表。本節中我們繼續展示`spring data JPA`在數據操作上強大的能力。
由于`Teacher`類定義了相關的屬性以及為這些屬性定義了相應的類型,所以`spring data JPA`才能夠根據`Teacher`類來對應生成相關的`teacher`表。故與其說我們前面講的`Teacher`類是對應`teacher` 表,不如說`Teacher`類映射的是`teacher`表**結構**。
| 序號 | JAVA類 | 數據表 |
| --- | --- | --- |
| 1 | 類名Teacher | 表名teacher |
| 2 | 屬性名id | 字段名id |
| 3 | 屬性類型Long | 字段類型bigint(long) |
在數據庫中,一張數據表主要以下述兩種要素組成:①表的定義(字段、外健、索引等);②表中所存儲的數據。下面,讓我們共同學習`spring data JPA`是如何處理表中的存儲數據的。
# Repository
`Repository`直譯為`倉庫`,在`spring data JPA`中將存儲了數據的表看做一座數據倉庫,我們可以由這個倉庫中取數據,也可以在這個倉庫中添加數據,同時也可以進行刪除、修改的操作。如想建`klass`數據表對應的倉庫,則需要進行如下操作:
首先同controller及entity一樣,我們建立repository包。
### 初始化JAVA接口文件
repository/KlassRepository.java
```java
package com.mengyunzhi.springBootStudy.repository;
import com.mengyunzhi.springBootStudy.entity.Klass;
import org.springframework.data.repository.CrudRepository;
/**
* 班級倉庫
*/
public interface? KlassRepository? extends? CrudRepository?<Klass?, Long?>{
}
```
* ? 類型定義為interface接口
* ? 按規范命名為`數據表名`+`Repository`(該命名僅為了方便記憶)
* ? 該接口extends繼承CrudRepository?接口的同時,繼承了CrudRepository對數據表操作的功能。
* ? 該數據倉庫對應對Klass類對應的klass數據表進行操作
* ? 該數據表的主鍵類型為Long
### 自定義查詢方法
接本節定義的前后臺對接接口,我們需要將班級名稱中包括前臺傳入的值的所有班級數據查詢出來,那么我們可以在KlassRepository中如下定義查詢方法:
repository/KlassRepository.java
```java
import java.util.List;
...
public interface KlassRepository extends CrudRepository<Klass, Long>{
List<Klass>? findAllByNameContains?(String name?);
}
```
* ? 定義返回值為`List<Klass>`來說明我要查詢多條班級數據。
* ? 定義方法名為:查詢出字段`name`中包含有第一個參數的所有數據。
* ? 定義第一個傳入參數的類型。
### 調用
我們來到想對班級表進行查詢操作的controller/KlassController中,使用下面的方法進行數據表的查詢操作:
controller/KlassController.java
```
import org.springframework.beans.factory.annotation.Autowired;
@RestController
@RequestMapping("Klass")
public class KlassController {
private static final Logger logger = LoggerFactory.getLogger(KlassController.class);
@Autowired ?
KlassRepository klassRepository;
@GetMapping
public List<Klass> getAll(@RequestParam String name) {
return this.klassRepository.findAllByNameContains(name);?
}
}
```
* ? 自動的向當前對象中注入一個實現了KlassRepository接口的對象。
* ? 調用該對象的findAllByNameContains()方法來完成數據的查詢操作并返回。
*****
在面向對象的語言中,我們學習過通過`new`關鍵字來由`類`來實例化一個供我們使用的對象。`Spring boot`是面向接口開發的框架,它為我們管理了大多數的對象,所以`new`操作發生在了`Spring boot`框架的底層,而我們在使用該框架的過程中,只需要通過`@Autowired`來告知它我們需要一個什么功能的對象即可。就也就是所謂的`ioc 控制反轉` ---- 以前我們在編寫代碼的過程中來管理對象,現在這個管理對象的任務卻**反轉為**框架來完成了。
*****
### 刪除冗余的配置項
由于`spring data jpa`已經包含了`JDBC`,所以我們此時可以在`pom.xml`刪除原來對`jdbc`依賴了。
pom.xml
```
<dependencies>
<dependency> ?
<groupId>org.springframework.boot</groupId> ?
<artifactId>spring-boot-starter-data-jdbc</artifactId> ?
</dependency> ?
```
原則上,如果你不刪除該依賴也不應該出現問題。但實際的情況是,如果你不刪除該依賴則在應用啟動時會出現以下錯誤:
**以下內容選讀**
```
org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'klassRepository' defined in null:
Cannot register bean definition [Root bean: class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'klassRepository':
There is already [Root bean: class [org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.
```
它的意思是說:在進行對象管理的時候進行新的對象注冊的時候,發現了重名的對象klassRepository。這個原因我猜測是由于:由`JPA`管理的`JpaRepositoryFactoryBean`以與由`JDBC`管理的`JdbcRepositoryFactoryBean`分別掃描到了`KlassRepository`。他們兩個都想把它注冊到`spring`統一管理的對象中,但該對象只允許存在一個,所以就報錯了。解決該問題也可以在配置文件中增加一行:`spring.main.allow-bean-definition-overriding=true`,意思是說如果發現有重名的了,就用后面的覆蓋前面的。此做法并不推薦。
## 測試
我們重新啟動應用,然后在數據表中添加2條測試數據,并重新運行測試:
教師表:

班級表:

當name為hello,返回0條數據:
```
GET http://localhost:8080/Klass?name=hello
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 04 Nov 2019 07:08:15 GMT
[]
Response code: 200; Time: 260ms; Content length: 2 bytes
```
當name為空時,返回3條數據:
```
GET http://localhost:8080/Klass?name=
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 04 Nov 2019 07:09:47 GMT
[
{
"id": 1,
"name": "測試1"
},
{
"id": 2,
"name": "測試2"
},
{
"id": 3,
"name": "其它"
}
]
Response code: 200; Time: 59ms; Content length: 66 bytes
```
當name為`測試`時,返回2條數據
```
GET http://localhost:8080/Klass?name=%E6%B5%8B%E8%AF%95
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 04 Nov 2019 07:10:17 GMT
[
{
"id": 1,
"name": "測試1"
},
{
"id": 2,
"name": "測試2"
}
]
Response code: 200; Time: 23ms; Content length: 45 bytes
```
當name為`其它`時返回1條數據
```
GET http://localhost:8080/Klass?name=%E5%85%B6%E5%AE%83
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 04 Nov 2019 07:10:49 GMT
[
{
"id": 3,
"name": "其它"
}
]
Response code: 200; Time: 25ms; Content length: 22 bytes
```
當name為 `試`時,返回2條數據:
```
GET http://localhost:8080/Klass?name=%E8%AF%95
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 04 Nov 2019 07:11:20 GMT
[
{
"id": 1,
"name": "測試1"
},
{
"id": 2,
"name": "測試2"
}
]
Response code: 200; Time: 21ms; Content length: 45 bytes
```
### SETER/GETER
對照我們自己定義的接口,剛剛的測試貌似完全的滿足了我們的要求。但問題是我們在前面想顯示的為如下數據:

而返回的數據當中,我們并沒有看到`教師`的信息,這樣的數據返回給前臺必然是無法滿足實際需求的。這一問題的出現,暴露了以下問題:
**在接口的定義過程中,返回數據的值定義的過于寬泛,在細節上沒有實際的指導意義。這為以后的BUG埋下的伏筆!**
這個問題產生的原因在這:
entity/Klass.java
```
package com.mengyunzhi.springBootStudy.entity;
import javax.persistence.*;
@Entity
public class Klass {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Teacher teacher;
private String name;
public Klass() {
}
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;
}
}
```
通過觀察此文件我們發現,`teacher`屬性是沒有`setter`及`getter`方法的。為了更好的說明`setter`及`getter`在整個程序執行過程中發揮的作用,我們依次添加`getter`及`setter`函數:
#### GETTER與SETTER
entity/Klass.java
```
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
```
然后重新運行后臺、重新添加測試數據后重新測試:
```
GET http://localhost:8080/Klass?name=%E6%B5%8B%E8%AF%95
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 04 Nov 2019 08:22:42 GMT
[
{
"id": 1,
"teacher": {
"id": 1,
"name": "張三更新",
"sex": false,
"username": "newzhangsan",
"email": "newzhangsan@yunzhiclub.com",
"createTime": null,
"updateTime": null
},
"name": "測試1"
},
{
"id": 2,
"teacher": {
"id": 1,
"name": "張三更新",
"sex": false,
"username": "newzhangsan",
"email": "newzhangsan@yunzhiclub.com",
"createTime": null,
"updateTime": null
},
"name": "測試2"
}
]
Response code: 200; Time: 164ms; Content length: 331 bytes
```
測試結果中返回了教師數據,依此我們可以得出結論:
** 如果沒有setter與getter方法,查詢到的數據將無法返回給前臺。**
## 請測試
那么到底是getter還是setter在真正的起做用呢?先猜猜,然后自己測試一下來驗證自己的猜測結果。
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.2.9](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.2.9) | - |
| Accessing data with MySQL | [https://spring.io/guides/gs/accessing-data-mysql/](https://spring.io/guides/gs/accessing-data-mysql/)| 15 |
- 序言
- 第一章: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
- 總結
- 開發規范
- 備用