mybatis-plus框架配合数据库表时间字段自动更新问题

背景

在项目开发前的设计阶段,我们会根据需求分析、业务梳理的结果进行领域建模。

通常有2种方式:

  1. 实体设计优先
  2. 数据库设计优先

无论哪种方式,最终会创建数据库、数据表。

通常在每一张表,会设计2个时间自动,创建时间和修改时间,这样在查询数据时能够清晰的看到数据行
是什么时候创建、什么时候最后修改的,可用于数据审计、方便问题排查等。

这2个字段的设置又有2种方式:

  1. 程序中在实体类设置字段值,由持久层框架处理保存到数据库
  2. 建表时指定字段默认值,利用数据库特性更新

注:第1种方式也可利用持久层框架的特性来处理,避免程序中手动赋值。

这里我们讨论第2种方式,利用数据库特性,如MySQL建表语句如下:

create table t_xxx
(
    ...
    `create_time` timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
    `update_time` timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    ...
}

其中:
default CURRENT_TIMESTAMP指定了字段默认值为当前时间,即第1次insert数据时字段值为当前时间,sql语句中无需指定字段值;
on update CURRENT_TIMESTAMP表示数据行有修改时,自动更新为当前时间,sql语句中无需指定字段值。

实践

某项目使用mybatis-plus作为持久层框架,采取上述方式建表。
创建实体类如下:

@AllArgsConstructor
@NoArgsConstructor
@Data
@TableName(value = "t_xxx")
public class Xxx implements Serializable {
    ...
    @TableField(value = "create_time")
    private LocalDateTime createTime;

    @TableField(value = "update_time")
    private LocalDateTime updateTime;
    ...
}

实测发现t_xxx表里的create_timeupdate_time未按预期进行更新:

  1. 有时界面上操作了更新,update_time未改变,
  2. 有时create_time发生了变化,不是数据第一次创建的时间,被修改了。

检查项目代码找到了上面2个问题的原因:
项目中有的场景在代码里通过id查询出Xxx实体对象,未在代码中通过setUpdateTime(...)设置为当前时间,
而此时updateTime字段有值,为数据库里查询出来的时间,on update CURRENT_TIMESTAMP未生效,导致了第1个问题。

在有的场景代码里通过id查询出Xxx实体对象后,不是第一次创建,但仍通过setCreateTime(...)设置为了当前时间,
然后执行了updateById(xxx),更新了表里的create_time字段,导致了第2个问题。

总结下来是因为mybatis-plus框架通过save(xxx)updateById(xxx)新增和更新,如果实体对象xxx的属性有值,
默认会取属性里的值新增或更新到数据库表的字段里。既然建表时采取了利用数据库特性来设置create_timeupdate_time
那么我们期望使用mybatis-plus时,不处理xxx实体类的createTimeupdateTime字段,即不进行赋值操作,或者
是即便因代码原因赋了值,转换成sql进行insert或update时,不处理create_timeupdate_time字段。

解决方法,配置实体类字段上的@TableField注解

@TableField(value = "create_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
private LocalDateTime createTime;

@TableField(value = "update_time", insertStrategy = FieldStrategy.NEVER, updateStrategy = FieldStrategy.NEVER)
private LocalDateTime updateTime;

配置insertStrategyupdateStrategyFieldStrategy.NEVER,表示新增和更新时不处理该字段,即生成sql语句中不包含该字段。

备忘

`create_time` timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
`update_time` timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',

使用这种方式自动更新创建时间、更新时间确实很方便,代码里无需每次设置实体类的字段值,sql语句无需设置字段值。
有3个值得注意的点:

  1. 使用持久层框架时配置保存实体时忽略实体对象里的字段
    避免持久层框架(如JPA、MyBatis等)在新增/更新时生成的sql语句里包含这2个时间字段,导致自动更新失效,不符合预期。

  2. 时间不一致问题
    这2个时间字段更新值为数据库当前时间,有可能数据库服务器的时间跟应用服务器的时间不一致,或者程序处理可能有一定耗时,
    程序里的当前时间可能比数据库表最后更新的时间早,有时可能需求是记录前者。

  3. update_time值未变化问题
    on update CURRENT_TIMESTAMP是数据有变化时更新字段值为当前时间,它有个问题是,如果数据行每个字段都未发生变更,
    那么update_time的值然是以前的值没有变化,不会更新。有可能在界面上进行了操作、或上游、或第三方调用了接口,只是未
    修改该表对应实体的字段,接口调用成功后发现update_time值未变化,这会让人疑惑。而有时值未修改但有操作过,仍需要记录修改时间。

posted @ 2023-06-29 22:56  cdfive  阅读(1764)  评论(0编辑  收藏  举报