前面的章節中,我們使用了`GetMapping`注解來映射到地址`/Teacher`的`get`請求,本節中我們開始使用`PostMapping`注解來映射到地址`/Teacher`的`post`請求。
# 初始化
TeacherController
```java
@RestController
@RequestMapping("Teacher")
public class TeacherController {
... ?
@PostMapping ?
@CrossOrigin("*") ?
public void save() { ?
logger.info("觸發了保存方法"); ?
}
}
```
* ? `...`表示省略其它非核心代碼
* ? 當使用`post`方法請求`/Teacher`地址時,觸發`save`方法。
* ? 支持任何地址對此方法的跨域訪問都是允許的。
* ? 定義方法名及返回類型
* ? 打印測試日志
## 測試
除了使用已有的前臺來進行測試以外,還有很多專門的測試工具來測試單獨的后臺項目,比如大名遠揚的`postman`。在此,我們展示一種使用`idea`自帶的測試功能的方法。
首先,讓我們啟動后臺,然后按以下操作來到`REST Client`:

發起請求后,我們來到控制臺。

```
2019-10-11 13:36:10.061 DEBUG 25994 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : POST "/Teacher", parameters={}
2019-10-11 13:36:10.061 DEBUG 25994 --- [nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public void com.mengyunzhi.springBootStudy.TeacherController.save()
2019-10-11 13:36:10.061 INFO 25994 --- [nio-8080-exec-8] c.m.springBootStudy.TeacherController : 觸發了保存方法 ?
2019-10-11 13:36:10.061 ? DEBUG ? 25994 ?--- [nio-8080-exec-8 ?] m.m.a.RequestResponseBodyMethodProcessor ?: Using 'application/json', given [application/json] and supported [application/json, application/*+json, application/json, application/*+json]?
2019-10-11 13:36:10.061 DEBUG 25994 --- [nio-8080-exec-8] m.m.a.RequestResponseBodyMethodProcessor : Nothing to write: null body
2019-10-11 13:36:10.061 DEBUG 25994 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK
```
得到以上幾條日志信息。除?以外,其它幾條均是`spring`的提示信息。在開發的過程中,無論是正常還是異常,我們都離不開控制臺的打印信息,在此再次進行解讀如下:
* ?時間
* ?日志等級
* ? 進程號
* ? 線程號
* ? 發送該日志的類
* ? 日志內容
# 數據綁定
前臺發送`數據`給后臺,后臺便需要使用變量來進行接收`數據`。這個使用變量進行接收的過程又叫作`Data Binding`,即`數據綁定`。在`spring`中,使用如下方法來綁定前臺傳入的`post`數據。
```java
import org.springframework.web.bind.annotation.*;
...
public void save(@RequestBody ? Teacher ? teacher?) {
```
* ?接收前臺`post`過來的數據對象。
* ?使用`Teacher`類實例化一個對象,拼按`Teacher`類中的屬性名進行數據綁定。
* ?數據綁定成功后把這個對象的名字起名為`teacher`,該對象可以在`save`方法中使用。
## 測試
打斷點

使用debug模式啟動項目

啟動`REST Client`,添加`Content-Type`信息來告訴后臺:我現在將`json`字符串發送給你。

依次點擊下列信息:

> 截圖中傳入的數據是`json字符串`與平常我們輸寫的`json對象`并不相同,你出錯了嗎?
此時`idea`將自動彈到`debug`界面。

通過觀察`teacher`變量的值,我們發送通過`REST Client`模擬發送過來的數據的確已經按照`Teacher`的字段名成功的綁定到`teacher`對象上了。
# 持久化
有了`teacher`對象以后,我們可以靜心地思索后臺的JAVA代碼進而完成將數據保存到數據庫中的操作了。和查詢數據相同,我們在此仍然使用`JDBC Template`,執行查詢方法時我們使用了`jdbcTemplate.query()`,新增數據時我們使用`jdbcTemplate.execute`。
```java
/**
* 新增教師
* 1. 獲取前臺傳入的教師對象
* 2. 拼接插入sql語句
* 3. 執行sql語句。
*
* @param teacher 教師
*/
@PostMapping
@CrossOrigin("*")
public void save(@RequestBody Teacher teacher) {
String sql = String.format(
"insert into `teacher` (`name`, `username`, `email`, `sex`) values ('%s', '%s', '%s', %s)", ?
teacher.getName(), teacher.getUsername(), teacher.getEmail(), teacher.getSex().toString() ?
); ?
logger.info(sql);
jdbcTemplate.execute(sql);
}
```
* ?調用String.format方法對字符串進行格式化。
* ?定義插入sql語句,并預留幾個`%s`作為數據插入項(注意,最后一個`%s`不能使用''包裹)。
* ?按順序用實際的值覆蓋字符串中的`%s`。
## 測試
*****
**注意**后臺的代碼只要發生變更,那么就必須重新啟動項目來使其生效,在教程后面的部分不再重復。
*****
啟動`REST Client`來進行測試,將得到以下錯誤信息:
```
{"timestamp":"2019-10-11T06:37:24.258+0000","status":500,"error":"Internal Server Error","message":"StatementCallback; SQL [insert into `teacher` (`name`, `username`, `email`, `sex`) values ('張三', 'zhangsan', 'zhangsan@yunzhiclub.com', true)]; Duplicate entry 'zhangsan' for key 'nx1HkMqiUveGnJz5lHE7mEcFI5WVew3iXbv3HCwF'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'zhangsan'? for key 'nx1HkMqiUveGnJz5lHE7mEcFI5WVew3iXbv3HCwF'","path":"/Teacher"}
```
* ? 在數據庫中發現已經有了用戶名 ---- `zhangsan`,而用戶名必須是唯一的。
在REST Client中修正測試的用戶名后:
```
{ "name": "張三", "username": "zhangsan1?", "email": "zhangsan@yunzhiclub.com" , "sex": true }
```
再進行測試,操作成功。

# 本節小測
1. 請求方法除了常用的用于查詢數據`get`方法及用于新增數據的`post`方法以外,我們后面還會接觸用于刪除數據的`delete`方法,用于更新全部屬性的`put`方法以及用于更新部分屬性的`patch`方法。這些方法在`spring`中是否有對應的映射注解呢?請猜測并驗證。
2. 在本節的代碼那個字符串為什么不能這樣寫:
```sql
"insert into `teacher` (`name`, `username`, `email`, `sex`) values ('%s', '%s', '%s', '%s')"
```
## 上節答案
可以將字符串定義到某個公共變量中,`AppComponent`及`TeacherAddComponet`在進行后臺請求時,均使用該公共變量。
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| mvc-ann-arguments | [https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#mvc-ann-arguments](https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#mvc-ann-arguments) | 10 |
| mysql 插入數據 | [https://www.runoob.com/mysql/mysql-insert-query.html](https://www.runoob.com/mysql/mysql-insert-query.html) | 10 |
| Accessing Relational Data using JDBC with Spring | [https://spring.io/guides/gs/relational-data-access/](https://spring.io/guides/gs/relational-data-access/) | - |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.3.4](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.3.4) | - |
- 序言
- 第一章: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
- 總結
- 開發規范
- 備用