背景
在项目开发前的设计阶段,我们会根据需求分析、业务梳理的结果进行领域建模。
通常有2种方式:
- 实体设计优先
- 数据库设计优先
无论哪种方式,最终会创建数据库、数据表。
通常在每一张表,会设计2个时间字段,创建时间和修改时间,这样在查询数据时能够清晰的看到数据行
是什么时候创建、什么时候最后修改的,可用于数据审计、方便问题排查等。
这2个字段的设置又有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_time
、update_time
未按预期进行更新:
- 有时界面上操作了更新,
update_time
未改变, - 有时
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_time
、update_time
,
那么我们期望使用mybatis-plus
时,不处理xxx
实体类的createTime
、updateTime
字段,即不进行赋值操作,或者
是即便因代码原因赋了值,转换成sql进行insert或update时,不处理create_time
、update_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;
配置insertStrategy
、updateStrategy
为FieldStrategy.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个值得注意的点:
- 使用持久层框架时配置保存实体时忽略实体对象里的字段
避免持久层框架(如JPA、MyBatis等)在新增/更新时生成的sql语句里包含这2个时间字段,导致自动更新失效,不符合预期。 - 时间不一致问题
这2个时间字段更新值为数据库当前时间,有可能数据库服务器的时间跟应用服务器的时间不一致,或者程序处理可能有一定耗时,
程序里的当前时间可能比数据库表最后更新的时间早,有时可能需求是记录前者。 update_time
值未变化问题on update CURRENT_TIMESTAMP
是数据有变化时更新字段值为当前时间,它有个问题是,如果数据行每个字段都未发生变更,
那么update_time
的值然是以前的值没有变化,不会更新。有可能在界面上进行了操作、或上游、或第三方调用了接口,只是未
修改该表对应实体的字段,接口调用成功后发现update_time
值未变化,这会让人疑惑。而有时值未修改但有操作过,仍需要记录修改时间。