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。

相关推荐
沐雨橙风ιε3 小时前
Spring Boot整合Apache Shiro权限认证框架(应用篇)
java·spring boot·后端·apache shiro
小蒜学长3 小时前
springboot基于javaweb的小零食销售系统的设计与实现(代码+数据库+LW)
java·开发语言·数据库·spring boot·后端
EnCi Zheng4 小时前
JPA 连接 PostgreSQL 数据库完全指南
java·数据库·spring boot·后端·postgresql
_extraordinary_5 小时前
Java SpringBoot(一)--- 下载Spring相关插件,创建一个Spring项目,创建项目出现的问题
java·spring boot·spring
ruleslol5 小时前
SpringBoot14-ThreadLocal讲解
spring boot
摇滚侠5 小时前
【IT老齐456】Spring Boot优雅开发多线程应用,笔记01
spring boot·redis·笔记
还是鼠鼠6 小时前
《黑马商城》微服务保护-详细介绍【简单易懂注释版】
java·spring boot·spring·spring cloud·sentinel·maven
Terio_my7 小时前
Spring Boot 热部署配置
java·spring boot·后端
苹果醋38 小时前
数据结构其一 线性表
java·运维·spring boot·mysql·nginx