如何在Spring Boot中实现DTO部分更新、空值设置及参数校验
Spring Boot实现部分字段更新及类型校验
问题场景
现有如下DTO:
@Getter @Setter public class TestDto { private Long id; private String name; private String sex; }
服务器存储的初始对象为:
{"id":1, "name": "alex", "sex": "M"}
需要实现:
- 发送部分字段请求(如
{"id":1, "name":"adam"}),仅更新指定字段,未传递的字段保留原值 - 支持主动传递
null清空字段(如{"id":1, "name":"adam", "sex":null}),将对应字段设为null - 字段类型校验:若传入非法类型(如
id传"1AA"),触发序列化异常并返回错误
实现方案
1. 客户端请求构造
若客户端为Java环境,按提示将TestDto转为Object发送,确保仅序列化已设置的字段:
// 构造更新对象,仅设置需要修改的字段 TestDto updateDto = new TestDto(); updateDto.setId(1L); updateDto.setName("adam"); // 若要清空sex字段,添加:updateDto.setSex(null); // 转为Object类型,配合Jackson配置仅序列化已设置字段 Object requestBody = updateDto; // 发送请求(推荐用PATCH语义更贴合部分更新) restTemplate.patchForObject("/test", requestBody, TestDto.class);
2. 服务器端核心实现
全局异常处理(捕获类型错误)
添加全局异常处理器,统一处理类型校验失败及业务异常:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler({JsonProcessingException.class, NumberFormatException.class}) public ResponseEntity<String> handleInvalidFieldType(Exception e) { return ResponseEntity.badRequest().body("字段类型错误:" + e.getMessage()); } @ExceptionHandler({IllegalArgumentException.class, RuntimeException.class}) public ResponseEntity<String> handleBusinessException(Exception e) { return ResponseEntity.badRequest().body(e.getMessage()); } }
更新接口实现
用JsonNode接收请求体,既可以明确识别客户端传递的字段,又能通过Jackson完成类型校验:
@RestController @RequestMapping("/test") public class TestController { // 模拟数据库存储 private final Map<Long, TestDto> testStore = new ConcurrentHashMap<>(); // 初始化测试数据 public TestController() { TestDto initDto = new TestDto(); initDto.setId(1L); initDto.setName("alex"); initDto.setSex("M"); testStore.put(1L, initDto); } @PatchMapping public TestDto updateTest(@RequestBody JsonNode requestNode) { // 1. 校验必填字段id if (!requestNode.has("id")) { throw new IllegalArgumentException("id不能为空"); } // 2. 解析id并校验类型 Long id; try { id = requestNode.get("id").asLong(); } catch (NumberFormatException e) { throw new IllegalArgumentException("id必须为有效数字"); } // 3. 查询原对象 TestDto originalDto = testStore.get(id); if (originalDto == null) { throw new RuntimeException("目标对象不存在"); } // 4. 将请求体绑定到TestDto,完成全局类型校验 ObjectMapper objectMapper = new ObjectMapper(); TestDto updateDto; try { updateDto = objectMapper.treeToValue(requestNode, TestDto.class); } catch (JsonProcessingException e) { throw new RuntimeException("字段类型不匹配", e); } // 5. 获取客户端实际传递的字段列表 Set<String> passedFields = new HashSet<>(); Iterator<String> fieldNames = requestNode.fieldNames(); while (fieldNames.hasNext()) { passedFields.add(fieldNames.next()); } // 6. 仅更新客户端传递的字段(包括主动传null的) Field[] fields = TestDto.class.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); String fieldName = field.getName(); if (passedFields.contains(fieldName)) { try { Object value = field.get(updateDto); field.set(originalDto, value); } catch (IllegalAccessException e) { throw new RuntimeException("字段更新失败", e); } } } // 7. 保存更新(模拟数据库操作) testStore.put(id, originalDto); return originalDto; } }
3. 可选:客户端Jackson配置
如果客户端是Spring Boot应用,配置Jackson仅序列化已设置的字段,避免未修改字段出现在请求中:
@Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper() // 仅序列化非默认值的字段(包装类默认值为null,未设置的字段不会被序列化) .setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT); } }
内容的提问来源于stack exchange,提问作者user19282140
相关产品推荐
相关产品推荐

