# SpringBoot项目实战记录
今天写项目的业务逻辑部分,有许多问题还不太明白。于是总结一下,下面从这段代码开始谈起吧,业务代码要实现`条件查询带分页`的功能。其中涉及到不少知识点,值得好捋一捋。很多时候,我们只是会用,但容易忘记原理。因此,要时常总结复习。
```java
// 3.条件查询带分页
@PostMapping("findPageHospSet/{current}/{limit}")
public Result findPageHospSet(
@PathVariable long current, @PathVariable long limit, @RequestBody(required = false) HospitalSetQueryVO hospitalSetQueryVO)
{
//创建page对象,传递当前页,每页记录
Page<HospitalSet> page = new Page<>(current, limit);
//构建条件
QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>();
String hosname = hospitalSetQueryVO.getHosname();
String hoscode = hospitalSetQueryVO.getHoscode();
//入参校验
if (!StringUtils.isEmpty(hosname)) {
wrapper.like("hosname", hospitalSetQueryVO.getHosname());
}
if (!StringUtils.isEmpty(hoscode)) {
wrapper.eq("hoscode", hospitalSetQueryVO.getHoscode());
}
//调用方法实现分页查询
Page<HospitalSet> pageHospitalSet = hospitalSetService.page(page, wrapper);
//返回结果
return Result.ok(pageHospitalSet);
}
```
再来看一看swagger测试。
![image-20220412142759813](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220412142759813.png)
提出了几个问题,值得学习过一遍。通过自我提问的方式,查漏补缺,补上自己的知识漏洞。
```
/**
* 写一份业务分析的文章,快速写完。
* 谈一谈 GetMapping和PostMapping的区别
* @RequestBody为什么对于前端友好,在API中,为什么是JSON?
* QueryWrapper究竟是怎么回事?是如何构造的?
* 返回Result为什么直接是Page对象
* 条件查询带分页这一过程是如何在SQL中实现的。
* @PostMapping("findPageHospSet/{current}/{limit}")中{current}是怎么回事?
*
* */
```
首先,聊一聊GetMapping与PostMapping区别,很简单一点。两者都是RequestMapping的快捷方式的组合注释。
## GetMapping与PostMapping区别
**@GetMapping**
用于将Http Get请求映射到特定处理程序方法的注释。
具体来说就是:@GetMapping是一个作为快捷方式的组合注释 @RequestMapping(method = RequestMethod.GET)。
**@PostMapping**
用于将Http Post 请求映射到特定处理程序方法的注释。
具体来说就是:@PostMapping是一个作为快捷方式的组合注释@RequestMapping(method = RequestMethod.POST)。
**@RequestMapping**
一般情况下都是用@RequestMapping(method = RequestMethod),因为@RequestMapping可以直接替代以上两个注解,但是以上两个注解并不能替代@RequestMapping,@RequestMapping相当于以上两个注解的父类。
------
在来谈一谈Get与Post的区别。这一部分内容,还是要到实际业务中才会要更深体会。在这一段业务代码,为什么用Post请求呢?这就要结合实际考虑了,我们要实现`条件查询带分页`,这里就要谈一谈`HospitalSetQueryVO`中的两个属性值。
```java
@ApiModel(description = "医院设置查询VO")
@Data
public class HospitalSetQueryVO {
@ApiModelProperty(value = "医院名称")
private String hosname;
@ApiModelProperty(value = "医院编号")
private String hoscode;
}
```
这2个属性值,属于敏感信息,不能给别人知道,肯定是不能用GET请求。用GET请求,参数会直接暴露在URL,危险,不能用。当然,POST放在RequsetBody中,这样对于前端也是友好,使用JSON,传递Body值。
## Get与Post的区别
#### 一、是什么
`GET`和`POST`,两者是`HTTP`协议中发送请求的方法。
##### **GET**
`GET`方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据。
##### POST
`POST`方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或**副作用**。
本质上都是`TCP`链接,并无差别
但是由于`HTTP`的规定和浏览器/服务器的限制,导致他们在应用过程中会体现出一些区别。
#### 二、区别
从`w3schools`得到的标准答案的区别如下:
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET产生的URL地址可以被Bookmark,而POST不可以。
- GET请求会被浏览器主动cache,而POST不会,除非手动设置。
- GET请求只能进行url编码,而POST支持多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- GET请求在URL中传送的参数是有长度限制的,而POST没有。
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
- GET参数通过URL传递,POST放在Request body中。
##### 参数位置
貌似从上面看到`GET`与`POST`请求区别非常大,但两者实质并没有区别。
无论 `GET `还是 `POST`,用的都是同一个传输层协议,所以在传输上没有区别。
当不携带参数的时候,两者最大的区别为第一行方法名不同。
> POST /uri HTTP/1.1 \r\n
>
> GET /uri HTTP/1.1 \r\n
当携带参数的时候,我们都知道`GET`请求是放在`url`中,`POST`则放在`body`中
`GET` 方法简约版报文是这样的
```
GET /index.html?name=qiming.c&age=22 HTTP/1.1
Host: localhost
```
`POST `方法简约版报文是这样的
```
POST /index.html HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
name=qiming.c&age=22
```
注意:这里只是约定,并不属于`HTTP`规范,相反的,我们可以在`POST`请求中`url`中写入参数,或者`GET`请求中的`body`携带参数。
##### 参数长度
`HTTP `协议没有`Body`和 `URL` 的长度限制,对 `URL `限制的大多是浏览器和服务器的原因。
`IE`对`URL`长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。
这里限制的是整个`URL`长度,而不仅仅是参数值的长度。
服务器处理长` URL` 要消耗比较多的资源,为了性能和安全考虑,会给 `URL` 长度加限制。
##### 安全
`POST `比` GET` 安全,因为数据在地址栏上不可见。
然而,从传输的角度来说,他们都是不安全的,因为` HTTP` 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文。
只有使用`HTTPS`才能加密安全。
##### 数据包
对于`GET`方式的请求,浏览器会把`http header`和`data`一并发送出去,服务器响应200(返回数据)。
对于`POST`,浏览器先发送`header`,服务器响应100 `continue`,浏览器再发送`data`,服务器响应200 ok。
并不是所有浏览器都会在`POST`中发送两次包,`Firefox`就只发送一次。
那么又提出一个问题呢?既然POST的优势这么多,为什么不干脆都使用POST呢?
## @ReuestBody的作用原理
该注解和RequestParam有点类似,同样用于绑定参数,但不是用于绑定表单提交的数据,也就是不是用于绑定`Content-Type`是`application/x-www-form-urlencoded`情况下的参数,
一般用于绑定`Content-Type`是`application/json`,`application/xml`等类型的参数。
我们可以通过配置HttpMessageConverter来支持我们的`Content-Type`类型,一般情况下,我们使用最多的是`application/json`这种格式的参数传递,而在Spring中,默认支持json传递对象的converter是`MappingJackson2HttpMessageConverter`。
在业务代码中,关于@RequestBody,我们这么处理的。
```
@RequestBody(required = false) HospitalSetQueryVO hospitalSetQueryVO
```
@RequestBody该注解只有一个参数:`required`,参数是否必须,默认是true。这里我们标注这一参数值为false,参数不必须。这就要考虑到我们的业务,对于条件查询,比如搜索在北京地区名字含协和的医院。在前端页面就会展示2个输入框(地区和名字),1个搜索按钮。就要考虑到,搜索人可能只输入1个参数,或者没有任何参数输入,更或者输入为空。当然,其实对于输入这一操作的校验,直接交给前端人员。不过,在后端也要考虑到这种情况。
我们来看一下swagger的测试,就明白@ReuestBody的功能。
## QueryWrapper及分页构建
QueryWrapper究竟是怎么回事?是如何构造的?
1. `QueryWrapper` 的构造方法如下,可以看到主要的操作是调用父类 `AbstractWrapper#initNeed()` 方法完成内部属性的初始化
```java
public QueryWrapper() {
this(null);
}
public QueryWrapper(T entity) {
super.setEntity(entity);
super.initNeed();
}
```
2. `AbstractWrapper#initNeed()` 方法很简单,其中关键属性如下:
> 1. `paramNameValuePairs`:用于保存上层使用条件构造器时的实际入参
> 2. `expression`:组织保存 SQL 语句 where 条件的关键容器
> 3. `lastSql`:代表实际执行的 SQL 语句的尾部字符串
> 4. `sqlComment`:代表实际执行的 SQL 语句的注释
> 5. `sqlFirst`:代表实际执行的 SQL 语句的起始字符串
```java
protected void initNeed() {
paramNameSeq = new AtomicInteger(0);
paramNameValuePairs = new HashMap<>(16);
expression = new MergeSegments();
lastSql = SharedString.emptyString();
sqlComment = SharedString.emptyString();
sqlFirst = SharedString.emptyString();
}
```
在实际业务中,我们除了要做条件查询之外,还要构建分页。
```java
//创建page对象,传递当前页,每页记录
Page<HospitalSet> page = new Page<>(current, limit);
//构建条件,传递HospitalSet
QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>();
Page<HospitalSet> pageHospitalSet = hospitalSetService.page(page, wrapper);
//返回结果
return Result.ok(pageHospitalSet);
```
想一想,它的实际SQL语句是什么?
```sql
SELECT id,hosname,hoscode,api_url,sign_key,contacts_name,contacts_phone,status,create_time FROM hospital_set LIMIT ?,?
#传递参数current和limit
也就是
SELECT id,hosname,hoscode,api_url,sign_key,contacts_name,contacts_phone,status,create_time FROM hospital_set LIMIT 1,3
```
------
还有一个问题,返回Result为什么直接是Page对象?为什么不是list呢?
我们来看一下,ResponseBody的结果
![image-20220412140439971](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220412140439971.png)
Page对象共有10属性参数,分别如下:
| 参数名 | 参数类型 | 默认值 | 描述 |
| ---------------- | ----------------- | ------ | --------------------------------------------- |
| records | `List<T>` | | 用来存放查询出来的数据 |
| total | `long` | | 返回记录的总数 |
| size | `long` | 10 | 每页显示条数 |
| current | `long` | 1 | 当前页 |
| orders | `List<OrderItem>` | | 排序字段信息 |
| optimizeCountSql | `boolean` | true | 自动优化 COUNT SQL |
| isSearchCount | `boolean` | true | 是否进行 count 查询,设置false后不会返回total |
| hitCount | `boolean` | false | 是否命中count缓存 |
| countId | `String` | | 暂时未知 |
| maxLimit | `Long` | null | 单页分页条数限制 |
SpringBoot项目实战记录