前面的章节中,我们使用了`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) | - |