Spring Boot项目中varchar字段为什么不用NULL?告别空指针从建表开始

前言

在后端开发中,特别是当我们使用Spring Boot配合MySQL等关系型数据库时,经常会遇到一个看似微小但实则令人纠结的问题: "当用户的某个字符串字段没有填写时,数据库里到底该存 NULL 还是存空字符串 ''?" 今天我们就结合Spring Boot项目实例,来聊聊这个话题。


一、 先搞清楚:它们到底有什么区别?

在决定用谁之前,我们必须先通过一个通俗的例子来看看这两者的本质区别。 假设我们有一张用户表 user,其中有一个字段是 nickname(昵称)。

  1. NULL :代表"未知"或"不存在"。
    • 意思是:"我不知道这个用户的昵称是什么,或者用户压根没填。"
    • 它是一个占位符,表示数据的缺失。
  2. 空字符串 '' :代表"确定的空值"。
    • 意思是:"我知道这个用户的昵称是什么,它就是一个空的,长度为0的字符串。"
    • 它是一个具体的值生活中的类比:
  • NULL 就像是一个还没拆封的问卷,关于"姓名"那一栏是一片空白,你不知道对方是忘了写还是不想写。
  • 空字符串 就像对方把问卷交上来了,但在"姓名"那一栏画了一个横线"------",明确告诉你"我不想填"。

二、 结合Spring Boot实战:谁更"省心"?

作为开发者,我们不仅关心理论,更关心写代码时会不会给自己挖坑。让我们看看在Spring Boot项目中,这两种选择会带来什么不同。

1. 避免 NullPointerException 的噩梦

如果你的数据库存的是 NULL,在Java代码中处理不当,极易引发空指针异常。 场景: 判断用户的昵称是否为空。 如果存的是 NULL:

java 复制代码
// 从数据库查出来的 user 对象
User user = userMapper.selectById(1L);
// 这里的 nickname 可能是 null
if (user.getNickname().isEmpty()) { 
	// ... 
    // 如果 nickname 是 null,调用 .isEmpty() 直接报错!NullPointerException!
}

为了安全,你不得不写出这样防御性的代码:

java 复制代码
// 丑陋的防御性代码
if (user.getNickname() != null && user.getNickname().isEmpty()) {
    // ...
}
// 或者使用工具类
if (StringUtils.isEmpty(user.getNickname())) {
    // ...
}

如果存的是空字符串 ''

java 复制代码
// 数据库查出来的 nickname 是 ""
if (user.getNickname().isEmpty()) {
    // 安全!不会报错,逻辑正常执行
    System.out.println("昵称为空");
}

结论: 使用空字符串 '',可以减少代码中大量的 null 检查,让代码更简洁,减少报错风险。

2. MyBatis 查询的"坑"

在使用 MyBatis-Plus 或 XML 写查询条件时,NULL 和空字符串的处理方式完全不同。 场景: 查询所有没有设置昵称的用户。 如果存的是 NULL: SQL语句必须写成:

sql 复制代码
SELECT * FROM user WHERE nickname IS NULL;

如果你写成 WHERE nickname = '',你一条数据也查不到。而且在 MyBatis XML 中,判断 NULL 需要格外小心:

xml 复制代码
<if test="nickname != null">
    AND nickname = #{nickname}
</if>
<!-- 如果要查空的,通常还得单独处理 -->

如果存的是空字符串 '' SQL语句非常统一:

sql 复制代码
SELECT * FROM user WHERE nickname = '';

这就和你查询普通数据(比如 WHERE nickname = '张三')的逻辑完全一致,不仅符合直觉,而且在拼接动态 SQL 时更加方便。

3. 前端交互与 JSON 序列化

在 Spring Boot 中,我们通常使用 Jackson 进行 JSON 序列化返回给前端。 如果存的是 NULL: 返回的 JSON 可能是这样的:

json 复制代码
{
  "id": 1,
  "nickname": null
}

前端同学可能会跑来问你:"为什么这个字段是 null?我还要多写一层判断,能不能给我个空字符串?"或者,如果配置了 @JsonInclude(JsonInclude.Include.NON_NULL),这个字段直接就在 JSON 里消失了,前端还得处理字段丢失的情况。 如果存的是空字符串 '' 返回的 JSON 是这样的:

json 复制代码
{
  "id": 1,
  "nickname": ""
}

前端接收到的数据类型永远是 String,不需要做 null 兼容,处理起来非常顺滑。

三、 什么时候应该坚持使用 NULL?

看了上面的例子,你可能会觉得:"既然空字符串这么好用,那是不是要把所有的 varchar 都设为默认空字符串?" 非也! 在以下场景中,NULL 才是正解。

1. 需要区分"没填"和"填了空的"

假设有一个 remark(备注)字段。

  • 用户提交了表单,但备注栏没写东西 -> 存 ''
  • 系统自动生成的数据,根本就没有备注这个概念 -> 存 NULL。 如果你需要在业务上严格区分"用户主动清空了备注"和"系统还没录入备注",那么 NULL 是必须的。

2. 索引与统计函数

虽然 MySQL 现在的版本优化了很多,但在某些老旧版本或特定数据库中,索引列如果存储大量空字符串 '',可能会导致索引效率问题。而 NULL 值通常不被计入索引(取决于数据库实现)。 另外,在使用 COUNT() 函数时:

  • COUNT(nickname):如果全是 NULL,结果是 0;空字符串则会被计入统计。 这会直接影响你的报表统计逻辑。

四、 总结与最佳实践建议

说了这么多,到底该怎么选?这里给出一个简单粗暴的建议,适用于绝大多数 Spring Boot 业务开发:

推荐方案:默认使用空字符串 ''

对于绝大多数 varchartext 类型的业务字段(如姓名、地址、描述、电话等):

  • 数据库默认值设为 ''(空字符串)。
  • 字段定义设为 NOT NULL

理由:

  1. 代码更健壮 :告别烦人的 NullPointerException
  2. SQL 更简单 :统一使用 =!= 查询,不需要区分 IS NULL
  3. 前后端交互更友好:类型统一,前端少报错。

结语

技术选型没有绝对的对错,只有适不适合。 但为了团队协作的效率和代码的可维护性,"varchar 字段默认空字符串,NOT NULL" 确实能在多数情况下少踩些坑。

相关推荐
Mr.45672 小时前
JDK17+Druid+SpringBoot3+ShardingSphere5 多表分库分表完整实践(MySQL+PostgreSQL)
java·数据库·spring boot·mysql·postgresql
tsyjjOvO2 小时前
Spring Boot 入门
java·spring boot·后端
小码哥_常2 小时前
从云端坠落:PUT和DELETE请求在大公司的失宠之谜
后端
Elastic 中国社区官方博客2 小时前
使用 ES|QL 变量控件将仪表板转变为调查工具
大数据·运维·服务器·数据库·elasticsearch·搜索引擎·全文检索
feng68_2 小时前
Ansible还原数据库节点
linux·运维·数据库·ansible
文心快码BaiduComate2 小时前
有奖征集|解锁Comate超能力:一文玩转Comate Skills
前端·后端
StackNoOverflow2 小时前
Spring Boot 核心知识点总结
java·spring boot·后端
世界哪有真情2 小时前
使用 Arthas 精准排查 SpringBoot 多模块项目中未使用的类(安全清理无用代码)
java·后端
乐hh2 小时前
清理MySQL数据
数据库·mysql