原创

因错误使用FieldStrategy注解导致的生产BUG

温馨提示:
本文最后更新于 2025年07月01日,已超过 10 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

问题说明

在某个业务表实体类中,其中某个字段使用了MybatisPlusFieldStrategy字段策略,且使用了类似updateById这种自带更新方法,导致表数据更新后和预期结果不一致,从而使得数据异常且丢失了原有状态;

由于历史日志没有存太久,且部分业务数据没有业务及运营人员持续跟进,导致部分数据真实数据丢失。

FieldStrategy 说明

FieldStrategy提供了6种字段更新策略,分别为:

  • IGNORED:忽略判断,该字段等同于ALWAYS。
  • ALWAYS:任何时候都加入 SQL。
  • NOT_NULL:非空时参与SQL生成。
  • NOT_EMPTY:字符串非空时参与SQL生成。
  • DEFAULT:默认NOT_NULL;如果全局配置了策略则代表跟随全局配置
  • NEVER:不加入SQL生成

示例

表数据

表字段包含名称、状态等,数据如下:

id name status
1 test1 0
2 test2 1
3 test3 2

实体类

@Data
@TableName("t_test")
public class TestBean {
    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField(value = "name")
    private String name;

    @TableField(value = "status", updateStrategy = FieldStrategy.ALWAYS)
    private Integer status;
}

更新

@Test
void test() {
    TestBean test2 = new TestBean();
    test2.setName("test2");
    test2.setId(3L);
    testMapper.updateById(test2);

    TestBean result = testMapper.selectById(3L);
    logger.info("result {}", JSON.toJSONString(result));
}

当执行该单元测试后,日志输出为:

==>  Preparing: UPDATE t_test SET name=?, status=? WHERE id=? AND deleted=0
==> Parameters: test2(String), null, 3(Long)
<==    Updates: 1

result {"deleted":"有效","id":3,"name":"test2"}

可以看到,状态字段被设置为null,我们到表里查看对应数据,可以发现该字段已置空;而我们原本期望只更新name值而忽略其它字段值,此时我们应在字段中使用NOT_NULL而非ALWAYS避免误更新。

改为 NOT_NULL

调整实体类,将可能不更新字段上添加非空判断,如下:

    @TableField(value = "name", updateStrategy = FieldStrategy.NOT_EMPTY)
    private String name;

    @TableField(value = "status", updateStrategy = FieldStrategy.NOT_NULL)
    private Integer status;

将数据恢复初始状态,再跑前面单元测试,查看输出日志:

==>  Preparing: UPDATE t_test SET name=? WHERE id=? AND deleted=0
==> Parameters: test2(String), 3(Long)
<==    Updates: 1

result {"deleted":"有效","id":3,"name":"test2","status":2}

可以看到,此次更新未将状态字段设置值,输出日志可以看到状态值还是初始值2未变更。

数据非空

在表设计过程中,对于有业务意义的字段,建议非空设置,此时,即使被误更新也会抛出异常从而避免数据被置空;我们将表状态字段改为非空,并将字段注解改回ALWAYS,再跑一次上述单元测试:

    @TableField(value = "status", updateStrategy = FieldStrategy.ALWAYS)
    private Integer status;

我们可以发现,当数据库字段非空时,更新失败抛出异常提示:

==>  Preparing: UPDATE t_test SET name=?, status=? WHERE id=? AND deleted=0
==> Parameters: test2(String), null, 3(Long)
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2132629e]

org.springframework.dao.DataIntegrityViolationException: 
### Error updating database.  Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'status' cannot be null

小结

1.在使用MybatisPlus过程中,慎用自带更新方法,如使用要特别关注对应实体字段是否使用了FieldStrategy更新策略;
2.在表设计过程中,建议给表加上默认值,更多说明见文章聊聊表设计时是否有必要给字段设置默认值
3.对于有使用自带更新方法的习惯,建议先查询,再对查询结果更新固定字段;
4.如不先查询后更新,且使用了字段更新策略,则建议将非更新操作必填字段加上判空策略确保不会将非更新字段置空;

正文到此结束
本文目录