前面的章节中,我们使用了`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`:
![](https://img.kancloud.cn/d5/86/d586390925216649f7fa40940478193b_1029x573.gif)
发起请求后,我们来到控制台。
![](https://img.kancloud.cn/b0/7d/b07d028e8b12e514e1e1a2f141b3d118_663x557.png)
```
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`方法中使用。
## 测试
打断点
![](https://img.kancloud.cn/a1/20/a120d54c1f739b082e16b8615c317633_887x219.png)
使用debug模式启动项目
![](https://img.kancloud.cn/9f/2d/9f2d5127241debeaa0d236d07776e0d7_715x60.png)
启动`REST Client`,添加`Content-Type`信息来告诉后台:我现在将`json`字符串发送给你。
![](https://img.kancloud.cn/72/97/7297859367d1f84a9daeee1fb91c303b_603x470.png)
依次点击下列信息:
![](https://img.kancloud.cn/bd/fd/bdfd9762a89da8fadae143e7fc3490eb_1880x535.png)
> 截图中传入的数据是`json字符串`与平常我们输写的`json对象`并不相同,你出错了吗?
此时`idea`将自动弹到`debug`界面。
![](https://img.kancloud.cn/83/a4/83a454b6e00be69912294e8178ba0e95_1016x502.png)
通过观察`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 }
```
再进行测试,操作成功。
![](https://img.kancloud.cn/98/ab/98abc5ba5a2043a1b21b65e0b64a83e1_442x93.png)
# 本节小测
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
- 总结
- 开发规范
- 备用