3.4 Spring Boot 实现 RESTful 风格
3.4.1 关于RESTFul
REST(英文:Representational State Transfer,简称 REST)
RESTFul是一种互联网软件架构设计的风格,但它并不是标准,它只是提出了一组客户端和服务器交互时的架构理念和设计原则,基于这种理念和原则设计的接口可以更简洁,更有层次,REST这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。
任何的技术都可以实现这种理念,如果一个架构符合 REST 原则,就称它为 RESTFul 架构。
比如:
我们要访问一个 http 接口:http://localhost:8080/boot/order?id=1021&status=1
采用 RESTFul 风格则 http 地址为:http://localhost:8080/boot/order/1021/1
简单点来说RESTful就是把参数放到请求路径中,而省略了参数名
3.4.2 Spring Boot 开发 RESTFul
Spring boot 开发 RESTFul 主要是几个注解实现
RESTFul风格要求后端响应请求的方式通常是有明确性的,即使用表示意义明确的注解标注方法响应类型
(1) @PathVariable
该注解是实现 RESTFul 最主要的一个注解 ,我们一般通过该注解获取 url 中的数据
使用
@RestController
public class studentController {
@GetMapping(value = "/student/details/{id}")
public Object studentDetails(@PathVariable("id") Integer id){
return "ID -> " + id;
}
}
上面代码中,后端接收的参数写在value中,用{}括起来,在方法参数内的@PathVariable(“id”) Integer id表示将接收到的参数赋值给Integer类型的引用id,其中@PathVariable内的参数与value中的参数应一致。
浏览器测试:
多个参数
@GetMapping(value = "/student/details/{id}/{name}")
public Object studentDetails2(@PathVariable("id") Integer id, @PathVariable("id") String name){
return "ID -> " + id + " NAME -> " + name;
}
可以看到是没有问题的:
注意
下面的写法实际并不会产生报错:
@RestController
public class studentController {
@GetMapping(value = "/student/details")
public Object studentDetail(Integer id){
return "id -> " + id;
}
@GetMapping(value = "/student/details/{id}")
public Object studentDetails(@PathVariable("id") Integer id){
return "ID -> " + id;
}
}
测试:
可以看到,两个方法都是能响应的。
这是因为传统的http请求风格将方法接收的参数视为请求中传来的参数,而RESTFul风格的写法将传来的参数视为url的一部分,这是不一样的。
但是下面这种写法是会报错的:
@RestController
public class studentController {
@GetMapping(value = "/student/details/{id}/{name}")
public Object studentDetails2(@PathVariable("id") Integer id, @PathVariable("name") String name){
return "ID -> " + id + " NAME -> " + name;
}
@GetMapping(value = "/student/details/{id}/{grade}")
public Object studentDetails3(@PathVariable("id") Integer id, @PathVariable("grade") String grade){
return "ID -> " + id + " GRADE -> " + grade;
}
}
运行后会报如下错误:
java.lang.IllegalStateException: Ambiguous handler methods mapped for '/student/details/1/%E7%8E%8B%E4%BA%94': {public java.lang.Object com.example.springbootworkspace.web.studentController.studentDetails3(java.lang.Integer,java.lang.String), public java.lang.Object com.example.springbootworkspace.web.studentController.studentDetails2(java.lang.Integer,java.lang.String)}
这是因为两个方法接收的参数类型顺序是一致的,无法分辨响应请求的到底是哪一个方法。
想要解决请求冲突,只能通过改写请求方式 或者 对url进行更加详细的描写
其他注解
@PostMapping
接收和处理 Post 方式的请求
@DeleteMapping
接收 delete 方式的请求,可以使用 GetMapping 代替
@PutMapping
接收 put 方式的请求,可以用 PostMapping 代替
@GetMapping
接收 get 方式的请求
3.4.3 使用 RESTful 风格模拟对学生信息的增删改查操作
集成 MyBatis、springboot,模拟实现对学生的增删改查操作
(1) pom.xml 文件
应具备如下内容
<dependencies>
<!--SpringBoot框架web项目起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--MyBatis集成SpringBoot框架起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<!--指定配置资源的位置-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<!--mybatis代码自动生成插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<!--配置文件的位置-->
<configurationFile>GeneratorMapper.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(2) application.yml 核心配置文件
server:
port: 8090
servlet:
context-path: /
#配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
username: root
password: qks218126
(3) 逆向工程生成 DAO
根目录下的GeneratorMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 指定连接数据库的 JDBC 驱动包所在位置,指定到你本机的完整路径 -->
<classPathEntry location="C:\Users\15998\.m2\repository\mysql\mysql-connector-java\8.0.25\mysql-connector-java-8.0.25.jar"/>
<!-- 配置 table 表信息内容体,targetRuntime 指定采用 MyBatis3 的版本 -->
<context id="tables" targetRuntime="MyBatis3">
<!-- 抑制生成注释器,因为生成的注释都是英文的,可以不让它生成 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 配置数据库连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/springboot"
userId="root"
password="qks218126">
</jdbcConnection>
<!-- 生成 model 类,targetPackage 指定 model 类的包名, targetProject 指定
生成的 model 放在 eclipse 的哪个工程下面-->
<javaModelGenerator targetPackage="com.example.springbootworkspace.model"
targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
<property name="trimStrings" value="false" />
</javaModelGenerator>
<!-- 生成 MyBatis 的 Mapper.xml 文件,targetPackage 指定 mapper.xml 文件的
包名, targetProject 指定生成的 mapper.xml 放在 eclipse 的哪个工程下面 -->
<sqlMapGenerator targetPackage="com.example.springbootworkspace.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- 生成 MyBatis 的 Mapper 接口类文件,targetPackage 指定 Mapper 接口类的包
名, targetProject 指定生成的 Mapper 接口放在 eclipse 的哪个工程下面 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.example.springbootworkspace.mapper"
targetProject="src/main/java">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 数据库表名及对应的 Java 模型类名 -->
<table tableName="t_student" domainObjectName="Student"
enableCountByExample="false"
enableUpdateByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
selectByExampleQueryId="false"/>
</context>
</generatorConfiguration>
逆向工程生成DAO
(4)编写代码
控制层
@RestController
public class RESTfulController {
@Autowired
private StudentService studentService;
/**
* 增加学生信息
* @param id
* @param name
* @return
*/
@PostMapping(value = "/student/{id}/{name}/{age}")
public Object addStudent(@PathVariable("id") Integer id, @PathVariable("name") String name, @PathVariable("age") Integer age) {
Student student = new Student();
student.setId(id);
student.setStuName(name);
student.setStuAge(age);
return studentService.insert(student);
}
/**
* 删除学生信息
* @param id
* @return
*/
@DeleteMapping(value = "/student/{id}")
public Object removeStudent(@PathVariable("id") Integer id) {
return studentService.deleteByPrimaryKey(id);
}
/**
* 修改学生信息
* @param id
* @return
*/
@PutMapping(value = "/student/{id}/{name}")
public Object modifyStudent(@PathVariable("id") Integer id, @PathVariable("name") String name) {
Student student = new Student();
student.setId(id);
student.setStuName(name);
return studentService.modifyStudentById(student);
}
/**
* 通过id查询学生信息
* @param id
* @return
*/
@GetMapping(value = "/student/{id}")
public Object queryStudent(@PathVariable("id") Integer id) {
Student student = studentService.queryStudentById(id);
return student;
}
}
业务层
public interface StudentService {
/*
* 根据学生id获取学生详情
* */
Student queryStudentById(Integer id);
/**
* 根据student对象更新学生信息
* @param student
* @return
*/
int modifyStudentById(Student student);
/**
* 根据id删除学生信息
* @param id
* @return
*/
int deleteByPrimaryKey(Integer id);
/**
* 增加学生信息
* @param student
* @return
*/
int insert(Student student);
}
业务层实现类
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public Student queryStudentById(Integer id) {
return studentMapper.selectByPrimaryKey(id);
}
@Override
public int modifyStudentById(Student student){
return studentMapper.updateByPrimaryKeySelective(student);
}
@Override
public int deleteByPrimaryKey(Integer id) {
return studentMapper.deleteByPrimaryKey(id);
}
@Override
public int insert(Student student) {
return studentMapper.insert(student);
}
}
(5) 使用 Postman 测试
插入
删除
查询
修改
(6)RESTful好与坏
好处就是:传递参数变简单了,服务提供者对外只提供了一个接口服务,而不是传统的 CRUD 四个接口
坏处就是除了需要指定特定的请求方式之外,还需要对url进行更加细致的划分
3.4.5 RESTful 原则
增 post 请求、删 delete 请求、改 put 请求、查 get 请求
请求路径不要出现动词例如:查询订单接口
/boot/order/1021/1(推荐)
/boot/queryOrder/1021/1(不推荐)
- 分页、排序等操作,不使用斜杠传参数例如:订单列表接口
/boot/orders?page=1&sort=desc
- 一般传的参数不是数据库表的字段,可以不采用斜杠