前言
必要性:
在实际开发中,设计数据库表时,通常都会添加公共字段,比如创建时间、操作人、更新时间等。
阿里巴巴规范:
建表规约9【强制】表必备三字段:id, gmt_create, gmt_modified。
说明:其中 id必为主键,类型为 unsigned bigint、单表时自增、步长为 1。gmt_create,gmt_modified的类型均为 date_time类型,前者现在时表示主动创建,后者过去分词表示被动更新。
MybatisPlus自动填充
Plus提供了MetaObjectHandler接口,实现公共字段自动写入。
public interface MetaObjectHandler {
/**
* 是否开启了插入填充
*/
default boolean openInsertFill() {
return true;
}
/**
* 是否开启了更新填充
*/
default boolean openUpdateFill() {
return true;
}
/**
* 插入元对象字段填充(用于插入时对公共字段的填充)
*
* @param metaObject 元对象
*/
void insertFill(MetaObject metaObject);
/**
* 更新元对象字段填充(用于更新时对公共字段的填充)
*
* @param metaObject 元对象
*/
void updateFill(MetaObject metaObject);
/**
* 通用填充
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
*/
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
if (Objects.nonNull(fieldVal) && metaObject.hasSetter(fieldName)) {
metaObject.setValue(fieldName, fieldVal);
}
return this;
}
/**
* get value from java bean by propertyName
*
* @param fieldName java bean property name
* @param metaObject parameter wrapper
* @return 字段值
*/
default Object getFieldValByName(String fieldName, MetaObject metaObject) {
return metaObject.hasGetter(fieldName) ? metaObject.getValue(fieldName) : null;
}
/**
* find the tableInfo cache by metaObject </p>
* 获取 TableInfo 缓存
*
* @param metaObject meta object parameter
* @return TableInfo
* @since 3.3.0
*/
default TableInfo findTableInfo(MetaObject metaObject) {
return TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
}
/**
* @param metaObject metaObject meta object parameter
* @return this
* @since 3.3.0
*/
default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal) {
return strictInsertFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldType, fieldVal)));
}
/**
* @param metaObject metaObject meta object parameter
* @return this
* @since 3.3.0
*/
default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
return strictInsertFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
}
/**
* @param metaObject metaObject meta object parameter
* @return this
* @since 3.3.0
*/
default MetaObjectHandler strictInsertFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
return strictFill(true, tableInfo, metaObject, strictFills);
}
/**
* @param metaObject metaObject meta object parameter
* @return this
* @since 3.3.0
*/
default <T, E extends T> MetaObjectHandler strictUpdateFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
return strictUpdateFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
}
/**
* @param metaObject metaObject meta object parameter
* @return this
* @since 3.3.0
*/
default <T, E extends T> MetaObjectHandler strictUpdateFill(MetaObject metaObject, String fieldName, Class<T> fieldType, E fieldVal) {
return strictUpdateFill(findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldType, fieldVal)));
}
/**
* @param metaObject metaObject meta object parameter
* @return this
* @since 3.3.0
*/
default MetaObjectHandler strictUpdateFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
return strictFill(false, tableInfo, metaObject, strictFills);
}
/**
* 严格填充,只针对非主键的字段,只有该表注解了fill 并且 字段名和字段属性 能匹配到才会进行填充(null 值不填充)
*
* @param insertFill 是否验证在 insert 时填充
* @param tableInfo cache 缓存
* @param metaObject metaObject meta object parameter
* @param strictFills 填充信息
* @return this
* @since 3.3.0
*/
default MetaObjectHandler strictFill(boolean insertFill, TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
if ((insertFill && tableInfo.isWithInsertFill()) || (!insertFill && tableInfo.isWithUpdateFill())) {
strictFills.forEach(i -> {
final String fieldName = i.getFieldName();
final Class<?> fieldType = i.getFieldType();
tableInfo.getFieldList().stream()
.filter(j -> j.getProperty().equals(fieldName) && fieldType.equals(j.getPropertyType()) &&
((insertFill && j.isWithInsertFill()) || (!insertFill && j.isWithUpdateFill()))).findFirst()
.ifPresent(j -> strictFillStrategy(metaObject, fieldName, i.getFieldVal()));
});
}
return this;
}
/**
* 填充策略,默认有值不覆盖,如果提供的值为null也不填充
*
* @param metaObject metaObject meta object parameter
* @param fieldName java bean property name
* @param fieldVal java bean property value of Supplier
* @return this
* @since 3.3.0
*/
default MetaObjectHandler fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal) {
if (getFieldValByName(fieldName, metaObject) == null) {
setFieldValByName(fieldName, fieldVal, metaObject);
}
return this;
}
/**
* 严格模式填充策略,默认有值不覆盖,如果提供的值为null也不填充
*
* @param metaObject metaObject meta object parameter
* @param fieldName java bean property name
* @param fieldVal java bean property value of Supplier
* @return this
* @since 3.3.0
*/
default MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {
if (metaObject.getValue(fieldName) == null) {
Object obj = fieldVal.get();
if (Objects.nonNull(obj)) {
metaObject.setValue(fieldName, obj);
}
}
return this;
}
}
测试案例
1、 数据库表中添加gmt_createg(创建时间)、mt_modified(最后更新时间)、oper_user(操作人账号)字段;
2、 实体类添加字段,使用@TableField设置字段填充策略;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String operUser;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime gmtModified;
/**
* 字段填充策略枚举类
*
* <p>
* 判断注入的 insert 和 update 的 sql 脚本是否在对应情况下忽略掉字段的 if 标签生成
* <if test="...">......</if>
* 判断优先级比 {@link FieldStrategy} 高
* </p>
*
* @author DDKK.COM 弟弟快看,程序员编程资料站
* @since 2017-06-27
*/
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}
1、 编写自定义MyMetaObjectHandler类,实现MetaObjectHandler接口;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时执行填充逻辑
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime.class, LocalDateTime.now()); // 自动填充插入时间
this.strictInsertFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());// 自动填充更新时间
this.strictInsertFill(metaObject, "operUser", String.class, "zhangsan");// 自动填充操作人
}
/**
* 更新时执行填充逻辑
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "operUser", String.class, "zhangsan_update");
}
}
1、 插入数据测试,发现已自动填充;
2、 更新测试,更新时会自动填充这些更新字段,但是如果这个更新字段已有值,则不能实现更新,所以需要把更新字段设置为null,或者设置当前信息(这样更新自动插入是否又没有什么使用意义了);
注意事项
- 填充原理是直接给entity的属性设置值!!!,自定义语句时无效。
- 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
- MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充
- 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
- 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入
- 要想根据注解FieldFill.xxx和字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
- 不需要根据任何来区分可以使用父类的fillStrategy方法
遇到的问题
先查询再更新时无法更新公共字段
因为默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充。所以先查询后更新,则无法自动填充更新值。
解决方式1:直接把metaObject中的公共字段设置为空,让plus去填充,不过这样自己就无法设置这些公共字段了。
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
//
metaObject.setValue("gmtModified",null);
metaObject.setValue("operUser",null);
this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "operUser", String.class, "zhangsan_update");
}
解决方式2:在mapper中添加自定义更新的方法,需要强制更新公共字段时使用这个,不需要时使用自带的update,使用时应抽取到自己定义的通用Mapper中。
default int updateByIdWithAutoFill(OrderTbl orderTbl) {
orderTbl.setGmtModified(LocalDateTime.now());
orderTbl.setOperUser("mappper_user");
return updateById(orderTbl);
}
逻辑删除时无法更新公共字段
deleteById时,只是设置了删除状态,没有自动填充更新值,此时应该设置更新时间和操作人。
解决方式1 :通过Sql 注入器解决,下篇介绍。。。
解决方式2 :自己写个通用方法进行处理。。。