SSM 框架中 JSON 处理空值的最佳实践
在基于 Spring + Spring MVC + MyBatis(SSM)的 Java Web 项目开发中,JSON 数据的交互是前后端通信的核心场景,空值(null 或空字符串)的处理往往成为开发中的痛点:前端可能因后端返回的空值格式不统一导致渲染异常,后端也可能因未正确处理空值引发序列化错误或数据泄露,本文将结合 SSM 框架的特性,从数据流转的各个环节(Controller 接收、Service 处理、MyBatis 查询、JSON 序列化)出发,系统介绍 JSON 空值的处理方案。
问题背景:为什么需要关注 JSON 空值?
在前后端分离架构中,后端通过 Spring MVC 将 Java 对象序列化为 JSON 返回给前端,前端解析 JSON 渲染页面,这一过程中,空值的处理涉及三个核心问题:
- 数据一致性:不同接口对空值的返回格式可能不统一(如
null、、、[]),导致前端适配成本高。 - 异常风险:若直接返回
null,前端未做判空可能导致页面报错(如Cannot read property 'xxx' of null)。 - 安全与性能:敏感字段若返回空值(如 而非
null),可能暴露字段存在;不必要的空值字段也可能增加 JSON 体积,影响传输效率。
核心场景与解决方案
Controller 层:接收与返回 JSON 时的空值处理
(1)接收前端 JSON 时的空值处理
前端通过 POST/PUT 请求发送 JSON 数据时,Spring MVC 默认通过 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)将 JSON 反序列化为 Java 对象,若 JSON 中某字段为 null 或空字符串,需根据业务需求决定是否允许赋值给 Java 对象的对应属性。
使用 @JsonFormat 或 @JsonCreator 指定空值处理规则
通过 Jackson 注解可自定义字段的反序列化逻辑。
public class UserDTO {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", nullsAsEmpty = true)
private Date birthday; // null 转为空字符串
@JsonCreator
public static UserDTO creator(@JsonProperty("name") String name,
@JsonProperty("age") Integer age) {
UserDTO user = new UserDTO();
user.setName(name != null ? name : "未知用户"); // name 为 null 时设为默认值
user.setAge(age != null ? age : 0); // age 为 null 时设为 0
return user;
}
}
使用 @Valid + 校验注解过滤无效空值
若字段不允许为空,可通过校验注解直接拦截请求:
public class UserDTO {
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 1, message = "年龄必须大于0")
private Integer age;
}
@PostMapping("/users")
public Result createUser(@RequestBody @Valid UserDTO userDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new IllegalArgumentException(bindingResult.getFieldError().getDefaultMessage());
}
// 业务处理...
}
若前端传 "name": null,校验会失败并抛出异常,避免将无效空值存入数据库。
(2)返回 JSON 时的空值处理
后端返回 JSON 时,核心目标是统一空值格式,避免前端解析异常,Spring MVC 默认使用 Jackson 进行序列化,可通过全局配置或局部注解控制空值输出。
全局配置 ObjectMapper(推荐)
在 Spring 配置类中自定义 ObjectMapper,统一处理空值:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 策略1:null 转为空字符串
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 策略2:null 转为 "null" 字符串(需配合自定义模块)
// mapper.registerModule(new SimpleModule().addSerializer(Object.class, new NullSerializer()));
// 策略3:忽略未知属性(避免前端新增字段导致反序列化失败)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}
JsonInclude.Include.NON_NULL:所有为null的字段不参与序列化(即 JSON 中不包含该字段)。- 若需将
null转为特定值(如 ),可自定义序列化器:public class NullSerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeString(""); // null 转为空字符串 } }然后在
ObjectMapper中注册:mapper.registerModule(new SimpleModule().addSerializer(Object.class, new NullSerializer()));
局部注解控制字段级别空值处理
若某些字段需要特殊处理,可在 DTO 类上使用 Jackson 注解:
public class UserDTO {
@JsonInclude(JsonInclude.Include.NON_NULL) // 该字段为 null 时不序列化
private String email;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") // Date 类型格式化
private Date createTime;
@JsonSerialize(using = NullToEmptyStringSerializer.class) // 自定义序列化器:null 转 ""
private String nickname;
}
MyBatis 层:查询结果中的空值处理
MyBatis 查询数据库时,若某字段值为 NULL,会直接映射到 Java 对象的属性,此时可通过 SQL 层面或 ResultMap 层面处理空值,避免将 NULL 传递给上层。
(1)SQL 层面:使用 IFNULL/COALESCE 函数
在 SQL 语句中直接为 NULL 字段设置默认值:
<select id="selectUserById" resultType="com.example.dto.UserDTO">
SELECT
id,
IFNULL(name, '未知用户') AS name,
COALESCE(age, 0) AS age,
email
FROM user
WHERE id = #{id}
</select>
IFNULL(name, '未知用户'):若name为NULL,则返回'未知用户'。COALESCE(age, 0):若age为NULL,则返回0(支持多个参数,返回第一个非NULL值)。
(2)ResultMap 层面:使用 <result> 标签的 nullValue 属性
若需复用 ResultMap,可通过 nullValue 指定 NULL 对应的默认值:
<resultMap id="userResultMap" type="com.example.dto.UserDTO">
<id property="id" column="id"/>
<result property="name" column="name" nullValue="未知用户"/> <!-- 数据库 NULL 映射为 "未知用户" -->
<result property="age" column="age" nullValue="0"/>
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
(3)TypeHandler 层面:自定义空值转换逻辑
若需复用空值处理逻辑(如统一将数据库 NULL 转为空字符串),可自定义 TypeHandler:
public class StringNullTypeHandler implements TypeHandler<String> {
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return value != null ? value : ""; // 数据库 NULL 转为 ""
}
// 其他方法实现(getParameter, getResult等)...
}
然后在 XML 中绑定 TypeHandler:
<result property="nickname" column="nickname" typeHandler="com.example.handler.StringNullTypeHandler"/>
Service 层:业务逻辑中的空值处理
Service 层作为业务逻辑核心,需明确空值的处理策略(如默认值、异常抛出等),避免将无效空值传递给其他层。
(1)参数校验与默认值设置
方法入参为 null 时,可通过 Objects.requireNonNull 或 Optional 提供默认值:
@Service
public class UserService {
public UserDTO getUserById(Long id) {
// id 为 null 时抛出异常
Objects.requireNonNull(id, "用户 ID 不能为空


还没有评论,来说两句吧...