SpringBoot15-PATCH 请求方式

一、PATCH 请求方式

1-1、PATCH 是什么

PATCH 是一种 HTTP 请求方法 ,用于对服务器上已有资源进行部分修改(partial update)。

  • 全名PATCH

  • 作用 :修改资源的一部分字段,而不是像 PUT 那样替换整个资源。

  • 典型场景

    • 更新用户资料中的单个字段(如只改邮箱或昵称)。

    • 更新订单状态,而不改变订单的其它信息。

    • 修改文档的某一部分数据。


与其他方法对比

方法 含义 数据传递 是否需要传递完整资源
GET 获取资源 查询参数 / 路径
POST 新增资源 请求体
PUT 完整更新资源(全量替换) 请求体 ✅ 必须提供整个资源
PATCH 部分更新资源 请求体 ❌ 只需提供要修改的部分
DELETE 删除资源 路径

关键差异
PUT → 整个资源被替换;
PATCH → 只更新提交的字段,其它保持不变。


1-2、PATCH 请求示例

假设后端有一个用户资源:
GET /users/123 返回的数据是:

复制代码
{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "age": 25
}

1、用 PATCH 只修改邮箱

复制代码
PATCH /users/123
Content-Type: application/json

{
  "email": "alice_new@example.com"
}
  • 服务器只会把 email 字段更新为新值,nameage 等其他字段保持不变。

2、用 PUT 替换整个对象(对比用法)

复制代码
PUT /users/123
Content-Type: application/json

{
  "id": 123,
  "name": "Alice",
  "email": "alice_new@example.com",
  "age": 25
}
  • 这里必须把完整对象都发过去,否则服务器可能会把没传的字段覆盖成 null 或默认值。

1-3、PATCH 常用内容类型

Content-Type 用途 示例
application/json 最常见,直接传要修改的字段 { "email": "new@example.com" }
application/merge-patch+json RFC 7396 标准的 JSON Merge Patch { "email": "new@example.com" }
application/json-patch+json RFC 6902 JSON Patch,描述操作列表 [ { "op": "replace", "path": "/email", "value": "new@example.com" } ]

大多数 REST API(如 GitHub API)使用 JSON Merge Patch (和普通 JSON 基本一样)。

如果是要更复杂的操作(比如增删数组元素),有些会用 JSON Patch 格式。


1-4、何时用 PATCH vs PUT

用 PATCH 的情况

  • 只更新一个或几个字段;

  • 服务器支持部分更新;

  • 不想每次都传整个资源,节省流量。

用 PUT 的情况

  • 后端 API 不支持 PATCH;

  • 需要替换整个对象;

  • 资源结构简单,替换成本不高。


1-5、实际开发注意事项

  • 幂等性

    • PATCH 通常不保证幂等性(多次 PATCH 请求可能产生不同结果),但如果只做简单字段替换,也可以是幂等的。

    • PUT 一般是幂等的(多次发送相同 PUT 请求,结果一样)。

  • 字段缺失:PATCH 只改传入的字段,没传的字段不会被删除;PUT 如果没传,可能会被覆盖为空。

  • 接口设计:API 文档要明确写清楚支持哪些方法,否则客户端要先确认服务端是否允许 PATCH。


1-6、springboot+springmvc+mybatis 示例

在 Spring Boot + Spring MVC + MyBatis 里,PUT/PATCH 的"全量替换 vs 局部更新"完全取决于你在 Controller/Service 层的写法MyBatis 的 SQL。框架不会替你决定"未传字段是否清空"。


常见实现方式

1) 路由区分
java 复制代码
@PutMapping("/users/{id}")   // 语义:全量替换
@PatchMapping("/users/{id}") // 语义:部分更新
2) DTO 反序列化策略(Jackson)
  • PUT:用 完整 DTO,配合 Bean 校验保证字段齐全

    java 复制代码
    public record UserPutDTO(
        @NotNull String name,
        @NotNull String email,
        Integer age
    ) {}

    若缺字段,直接 400;这样就符合"全量替换"的预期。

  • PATCH:用 部分 DTOMap<String,Object>

    java 复制代码
    public record UserPatchDTO(String name, String email, Integer age) {}

    仅对 非 null 字段做合并更新(见下文 Service 层)。

想要更精细地区分"字段缺失" vs "显式传 null",可用 JsonNodeJsonMergePatch/JsonPatch


3) Service 层合并逻辑(PATCH 核心)
java 复制代码
public void patchUser(Long id, UserPatchDTO patch) {
    User db = userMapper.selectById(id);
    if (db == null) throw new NotFoundException();

    if (patch.name() != null)  db.setName(patch.name());
    if (patch.email() != null) db.setEmail(patch.email());
    if (patch.age() != null)   db.setAge(patch.age());

    userMapper.updateSelective(db); // 见下文:只更新非空列
}
4) MyBatis 动态 SQL("只更新非空列")
XML 复制代码
<update id="updateSelective" parameterType="User">
  UPDATE user
  <set>
    <if test="name != null">name = #{name},</if>
    <if test="email != null">email = #{email},</if>
    <if test="age != null">age = #{age},</if>
  </set>
  WHERE id = #{id}
</update>
  • 这就是最常见的 "选择性更新(Selective Update)"

  • 注意:这样会导致无法把某字段更新为 NULL (因为 null 被过滤掉了)。若需要"把字段置空",要另做一条 SQL 或通过 Map<String,Object> 显式标记"要置空的字段"。

5) PUT 的全量替换

做法 A(严格):要求客户端传完整 DTO(用 @NotNull),然后:

  1. 先把数据库对象取出;

  2. 新的 DTO 全量覆盖(包括置空);

  3. 非选择性更新 执行:

    XML 复制代码
    <update id="updateFull" parameterType="User">
      UPDATE user
      SET name = #{name},
          email = #{email},
          age = #{age}
      WHERE id = #{id}
    </update>

做法 B(宽松):仍用 updateSelective,但这就把 PUT 弄成了"看起来像 PATCH",不推荐。


一句话指南

  • 想要 PUT=全量替换 :用完整 DTO + 非选择性 updateFull,缺字段就 400。

  • 想要 PATCH=只改传入字段 :Service 里按非 null 合并,MyBatis 用 updateSelective

  • 需要"把字段置空":不要用"忽略 null"的 update;要么提供专门接口,要么用 JSON Merge Patch/JSON Patch 明确表达。


小结

@PatchMapping@PutMapping 在 Spring MVC 里主要是"语义和请求方法"上的区别,框架本身不会自动改变你的更新逻辑。

  • 作用

    • @PutMapping 只是告诉 Spring MVC:这个方法处理 HTTP PUT 请求。

    • @PatchMapping 只是告诉 Spring MVC:这个方法处理 HTTP PATCH 请求。

  • 不会自动实现部分更新

    Spring 不会因为用了 @PatchMapping 就帮你做"只改传入字段,缺失字段保持不变";也不会因为用了 @PutMapping 就自动替换整条记录。
    你必须自己在 Service/Mapper 层写全量更新或选择性更新的逻辑

  • 常用做法

    • PUT → 在 Controller 接收完整 DTO,要求字段齐全,然后用非选择性 SQL(全部字段都更新)。

    • PATCH → 在 Controller 接收部分 DTO/Map,Service 层只对非 null 字段合并,再用 MyBatis 动态 SQL 只更新有值的列。

所以,@PatchMapping/@PutMapping 只是声明接口方法的 HTTP 动词,实际更新行为(全量/局部、如何处理 null)完全由你在代码里决定。

二、回顾:@NotEmpty的使用


2-1、常用 Bean Validation 注解适用范围

注解 作用 常用于字段类型
@NotNull 不能为 null,但允许空字符串/空集合 任何对象类型
@NotEmpty 不能为 null 且不能为空(长度 > 0) StringCollectionMapArray
@NotBlank 不能为 null 且去掉空格后长度 > 0 只用于 String
@Size 限制集合、字符串、数组长度 字符串、集合、数组

⚠️ @NotEmpty 不能用在原始类型(如 int),因为它们不可能为 null

如果是字符串最好用 @NotBlank(会自动去掉前后空格再判断)。


2-2、在 Controller 方法参数上使用

1、用在 请求体 DTO 字段上(最常见)

java 复制代码
import jakarta.validation.constraints.NotEmpty;

public class UserPatchDTO {
    @NotEmpty(message = "用户名不能为空")
    private String name;

    @NotEmpty(message = "邮箱不能为空")
    private String email;
    // getter & setter
}
java 复制代码
@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public ResponseEntity<Void> create(@Valid @RequestBody UserPatchDTO dto) {
        // dto 中的字段会自动校验
        return ResponseEntity.ok().build();
    }
}
  • 需要在 Controller 类或方法参数前加上 @Valid(或 @Validated)。

  • 如果不加 @Valid,注解不会触发校验。


2、用在 方法参数(如请求参数)

java 复制代码
@GetMapping("/search")
public ResponseEntity<?> search(@RequestParam @NotEmpty String keyword) {
    // keyword 不能为空或null
    return ResponseEntity.ok().build();
}
  • 可以直接放在 @RequestParam 后面。

  • 注意 :如果你用的是 @PathVariable,也可以加 @NotEmpty,同样需要启用校验。


3、启用校验的关键

  • Spring Boot 3.x / 2.x 默认已集成 jakarta.validation (Hibernate Validator),只要在 pom.xmlspring-boot-starter-validation

  • Controller 方法里要有 @Valid 或类上有 @Validated 才会触发。

java 复制代码
@RestController
@Validated  // 类级别开启
public class UserController {
    @GetMapping("/find")
    public User find(@RequestParam @NotEmpty String id) { ... }
}

4、建议用法总结

场景 推荐注解
Body 里对象字段 @NotEmpty(集合/字符串) / @NotBlank(字符串) / @NotNull(一般对象)
Query 参数 / Path 变量 @NotEmpty@NotBlank
数字参数 @NotNull + @Min / @Max

2-3、@NotEmpty vs @NotBlank 对比

注解 检查内容 允许值示例
@NotEmpty 不能为 null,并且长度必须 > 0 "abc" ✅,"" ❌,null ❌," " ✅(因为长度是3)
@NotBlank 不能为 null,去掉前后空格后长度必须 > 0 "abc" ✅,"" ❌,null ❌," "

关键区别:@NotBlank 会去掉空格再判断是否为空@NotEmpty 不会去掉空格。


实际影响

  • 如果你的字符串允许只包含空格(例如用户可以输入 " " 作为合法值),用 @NotEmpty 就可以。

  • 如果你不希望 " " 这种全空格的字符串通过验证,应该用 @NotBlank

例如:

java 复制代码
class UserDTO {
    @NotEmpty
    private String nickname;

    @NotBlank
    private String realName;
}
输入值 @NotEmpty @NotBlank
null
""
" "
"abc"

总结

  • @NotEmpty = 不允许 null 和空串,但允许全空格。

  • @NotBlank = 不允许 null、空串和全空格字符串。

  • 对大多数用户输入场景(如姓名、邮箱、标题),更推荐 @NotBlank

  • 对集合、数组等用 @NotEmpty 更合适,因为 @NotBlank 只支持字符串。

所以:如果你的需求是"必须有非空白字符",要用 @NotBlank

如果只需要"不是 null、长度不为 0",那 @NotEmpty 就够了。

三、更新用户头像的场景


3-1、常见的头像存储架构

1、应用服务器只保存头像 URL 或文件路径

  • 数据库表(如 user)中一般只有一个字段,例如:
sql 复制代码
ALTER TABLE user ADD COLUMN avatar_url VARCHAR(255);
  • 保存的只是 云存储返回的访问地址 (如 https://cdn.xxx.com/avatar/123.jpg)或相对路径(如 /avatar/123.jpg)。

2、真正的图片文件存放在对象存储或 CDN 上

常见方案:

  • 阿里云 OSS、腾讯云 COS、AWS S3、七牛云 Kodo

  • 静态资源服务器(Nginx + 本地磁盘)

  • 搭配 CDN 加速(Cloudflare、阿里云 CDN)

应用服务器本身通常不直接存储和分发大文件,减少压力和带宽成本。


3-2、上传流程(常见两种)

方式 A:客户端直传云存储(推荐)

  1. 客户端(Web/APP)向后端请求上传凭证(如 S3 pre-signed URL、OSS STS token)。

  2. 客户端直接用该凭证将文件传到云存储。

  3. 云存储返回文件 URL,客户端再把这个 URL 提交给后端更新用户信息。

优点:后端不用承受文件流量和大带宽;更省成本,上传速度快。


方式 B:客户端先传到后端,再由后端转存云存储

  1. 客户端发一个 multipart/form-data 请求到后端。

  2. 后端接收文件后,用 SDK 上传到云存储。

  3. 得到文件 URL 后更新数据库。

优点 :后端可统一做图片处理(压缩、裁剪、水印、鉴黄等);
缺点:占用服务器带宽和磁盘 IO,成本较高。


3-3、为什么很少把图片直接存在数据库或本地磁盘

  • 数据库 :可以用 BLOB 存,但会严重拖慢数据库性能和备份速度。

  • 本地磁盘:如果是多机部署或容器环境(K8s、Docker),会难以共享和扩展,迁移也麻烦。

  • 云存储:高可用、便宜、可用 CDN 加速、便于权限控制,是业界最佳实践。


3-4、总结

大多数系统:

  • 数据库只存头像 URL

  • 实际图片存云对象存储(OSS/S3/COS)或 CDN 静态资源服务器

  • 上传时可以选择"直传云存储"或"后端中转"。


四、请求参数类型是querystring是什么意思

这是在说 HTTP 请求里参数的传递方式

如果一个接口的 请求参数类型是 query string,意思是:

参数通过 URL 问号 ? 后面的键值对传递,而不是放在请求体(body)里。


4-1、结构

复制代码
https://api.example.com/users?name=alice&age=18
  • ? 之后的部分就是 query string(查询字符串)。

  • 每个参数是 key=value 的形式,用 & 连接多个。

  • 上例中:

    • name=alice

    • age=18


4-2、使用方式

1. GET 请求

最常见:GET 请求参数一般用 query string

复制代码
GET /search?keyword=apple&page=2 HTTP/1.1
Host: example.com

2. POST 请求也可以带 query string

虽然 POST 的主要数据放在 body,但 URL 也可以附带 query string:

复制代码
POST /order/create?source=app HTTP/1.1
Host: example.com
Content-Type: application/json

{ "itemId":123, "count":2 }

4-3、对比其他参数位置

参数位置 形式示例 特点
Query String /users?name=alice&age=18 最常见,适合 GET 查询条件;暴露在 URL
Path Variable /users/123 路径中直接表示资源 ID
Request Body POST /users + JSON 用于创建/修改资源,数据放 body
Header Authorization: Bearer xxx 元信息、token、安全数据

总结

  • Query string = URL 中 ? 后面的键值对参数

  • 常用在 GET 请求或需要在 URL 上直观看到的参数。

  • 与 body 不同:query string 总是明文出现在 URL 上,适合搜索条件、分页参数等。

如果接口文档写"请求参数类型:querystring",就是让你把参数拼在 URL 后面。


五、controller层接受json格式的传参的参数格式可以有哪些

Spring Boot + Spring MVC 的 Controller 里,

"接收 JSON 格式参数"最常见的几种写法可以分成两类:

  • 接收整个 JSON 对象
  • 接收 JSON 中的局部字段

5-1、接收整个 JSON 对象

(1)用 JavaBean / DTO

最常用、推荐方式

java 复制代码
@Data
public class UserDTO {
    private String name;
    private Integer age;
    private String avatarUrl;
}
java 复制代码
@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public String createUser(@RequestBody UserDTO user) {
        return "name=" + user.getName() + ", age=" + user.getAge();
    }
}
  • 前端发送:

    {
    "name": "Alice",
    "age": 18,
    "avatarUrl": "https://xxx.com/a.jpg"
    }

  • Spring 会用 Jackson 自动把 JSON 映射到 UserDTO


(2)用 Map

灵活,但缺少类型检查,适合动态 JSON。

java 复制代码
@PostMapping("/map")
public String saveUser(@RequestBody Map<String, Object> json) {
    return "name=" + json.get("name");
}

请求:

复制代码
{ "name": "Bob", "age": 22 }

(3)用 JsonNode(Jackson Tree)

适合要处理不固定结构或只取部分字段时。

java 复制代码
@PostMapping("/tree")
public String saveUser(@RequestBody com.fasterxml.jackson.databind.JsonNode node) {
    return "name=" + node.get("name").asText();
}

5-2、接收局部字段(JSON 里的某些值)

(1)直接用多个基本参数(不常用)

java 复制代码
@PostMapping("/partial")
public String partial(@RequestBody Map<String, Object> json) {
    String name = (String) json.get("name");
    Integer age = (Integer) json.get("age");
    return name + "-" + age;
}

⚠️ 这种方式要自己从 Map 里取值,类型安全差。

spring validation在此时是失效的!需要手动校验参数。


(2)用 @RequestParam(不适合 JSON Body)

  • 注意:@RequestParam 用于 query string 或表单,不是 JSON。
    如果请求体是 JSON,不能用 @RequestParam 去直接接字段。
java 复制代码
// ❌ 错误示例
@PostMapping("/wrong")
public String wrong(@RequestParam String name) { ... }

5-3、高级用法

1、接收并校验

java 复制代码
@PostMapping
public String create(@Valid @RequestBody UserDTO user) {
    // @NotBlank、@Size 等注解会自动触发
    return user.getName();
}

2、接收 List / 嵌套结构

java 复制代码
@PostMapping("/batch")
public void batch(@RequestBody List<UserDTO> users) {
    // 接收 JSON 数组
}

3、接收 Map<String, List> 等复杂结构

java 复制代码
@PostMapping("/complex")
public void complex(@RequestBody Map<String, List<String>> payload) { ... }

总结

用法 适用场景 推荐度
@RequestBody + DTO 结构固定、可做校验 ⭐⭐⭐⭐✅
@RequestBody + Map 动态字段、简单快速 ⭐⭐⭐
@RequestBody + JsonNode 不确定结构、手动解析 ⭐⭐
List<DTO> 批量对象 ⭐⭐⭐⭐
混合:DTO + @Valid 强类型 + 校验 ⭐⭐⭐⭐⭐

一般来说:

  • 固定接口 → DTO + @RequestBody + 校验注解;

  • 动态 JSON → Map 或 JsonNode;

  • 数组List<DTO>

  • 不要用 @RequestParam 去解析 JSON。

相关推荐
qq_12498707532 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_818732063 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
汤姆yu6 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶6 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
biyezuopinvip7 小时前
基于Spring Boot的企业网盘的设计与实现(任务书)
java·spring boot·后端·vue·ssm·任务书·企业网盘的设计与实现
JavaGuide8 小时前
一款悄然崛起的国产规则引擎,让业务编排效率提升 10 倍!
java·spring boot
figo10tf8 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
zhangyi_viva8 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端
橙露8 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot