一、什么是metaObjectHandler
MetaObjectHandler接口是mybatisPlus为我们提供的的一个扩展接口,我们可以利用这个接口在我们插入或者更新数据的时候,为一些字段指定默认值
使用方式如下:
1、在实体类上加入@TableField注解
@Getter
@Setter
public class AbstractBaseDO<T extends Model<T>> extends Model<T> implements Serializable {
/**
* 创建时间 新增时填充
*/
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
/**
* 修改时间 新增和更新时填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
/**
* 创建人ID 新增时更新
*/
@TableField(fill = FieldFill.INSERT)
private Long creatorId;
/**
* 修改人ID 新增和更新时填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long modifierId;
/**
* 逻辑删除字段 新增和更新时填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Integer isDeleted;
@Override
public String toString() {
return new ToStringBuilder(this)
.append("gmtCreate", gmtCreate)
.append("gmtModified", gmtModified)
.append("creatorId", creatorId)
.append("modifierId", modifierId)
.append("isDeleted", isDeleted)
.toString();
}
}
2、创建配置类实现MetaObjectHandler接口
@Slf4j
@Configuration
public class MetaObjectHandlerConfig implements MetaObjectHandler {
/**
*插入时自动填充
*/
@Override
public void insertFill(MetaObject metaObject) {
//获得用户上下文
UserContext dscUser = GlobalSessionContext.getDscUser();
//获得当前时间
Date date = Calendar.getInstance().getTime();
//创建时间
this.fillStrategy(metaObject, "gmtCreate", date);
//更新时间
this.fillStrategy(metaObject, "gmtModified", date);
//未删除
this.fillStrategy(metaObject, "isDeleted", CommonConstants.NON_DELETED);
//创建者
this.fillStrategy(metaObject, "creatorId", dscUser.getUserId());
//更新者
this.fillStrategy(metaObject, "modifierId", dscUser.getUserId());
}
/**
*修改时自动填充
*/
@Override
public void updateFill(MetaObject metaObject) {
//获得用户上下文
UserContext dscUser = GlobalSessionContext.getDscUser();
//获得当前时间
Date date = Calendar.getInstance().getTime();
//强制更新时间
this.setFieldValByName("gmtModified", date, metaObject);
//更新者
this.fillStrategy(metaObject, "modifierId",dscUser.getUserId());
}
}
二、失效场景及解决方案
1、使用mybatis-plus的boolean update(Wrapper updateWrapper)链式更新方法时失效
1)示例代码
/**
*更新属性
*/
@Override
public Boolean expireHistoryTask(Long tenantId, Long produceInfoId) {
return this.update(new UpdateWrapper<TaskDetailInfoDO>().lambda()
.eq(TaskDetailInfoDO::getTenantId, tenantId)
.in(TaskDetailInfoDO::getProduceInfoId, produceInfoId)
.eq(TaskDetailInfoDO::getIsDeleted, CommonConstants.NON_DELETED)
.set(TaskDetailInfoDO::getTaskStatus, TaskStatusEnum.EXPIRED.getCode())
}
2)失效原因
这种更新方法失效我们需要去看mbatis-plus的源码
private static void process(MappedStatement ms, Object parameterObject) {
if (parameterObject != null) {
TableInfo tableInfo = null;
Object entity = parameterObject;
if (parameterObject instanceof Map) {
Map<?, ?> map = (Map<?, ?>) parameterObject;
if (map.containsKey(Constants.ENTITY)) {
Object et = map.get(Constants.ENTITY);
if (et != null) {
entity = et;
tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
}
}
} else {
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
}
//tableInfo为空,则不自动填充
if (tableInfo != null) {
//到这里就应该转换到实体参数对象了,因为填充和ID处理都是争对实体对象处理的,不用传递原参数对象下去.
MetaObject metaObject = ms.getConfiguration().newMetaObject(entity);
if (SqlCommandType.INSERT == ms.getSqlCommandType()) {
populateKeys(tableInfo, metaObject, entity);
insertFill(metaObject, tableInfo);
} else {
updateFill(metaObject, tableInfo);
}
}
}
}
从上面的代码可以看出,当tableInfo为空,则不自动填充字段值。而继续看,tableInfo是从parameterObject中获取,而这parameterObject就是要更新的实体对象。
在看我们使用 update(Wrapper updateWrapper)更新的底层逻辑
/**
* 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
*
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
default boolean update(Wrapper<T> updateWrapper) {
//传入了一个空实体
return update(null, updateWrapper);
}
此时是传了一个空对象,这就导致了整个后续的填充都失效了。
3)解决方法
使用update的另一个方法
在更新是传入实体对象
@Override
public Boolean expireHistoryTask(Long tenantId, Long produceInfoId) {
return this.update(new TaskDetailInfoDO(), new UpdateWrapper<TaskDetailInfoDO>().lambda()
.eq(TaskDetailInfoDO::getTenantId, tenantId)
.in(TaskDetailInfoDO::getProduceInfoId, produceInfoId)
.eq(TaskDetailInfoDO::getIsDeleted, CommonConstants.NON_DELETED)
.set(TaskDetailInfoDO::getTaskStatus, TaskStatusEnum.EXPIRED.getCode())
);
}
2、填充时使用 MetaObjectHandler fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal) 方法
@Override
public void updateFill(MetaObject metaObject) {
//获得用户上下文
UserContext dscUser = GlobalSessionContext.getDscUser();
//更新者
this.fillStrategy(metaObject, "modifierId", dscUser.getUserId());
}
fillStartegy的源码时这样的
/**
* 填充策略,默认有值不覆盖,如果提供的值为null也不填充
*
* @param metaObject metaObject meta object parameter
* @param fieldName java bean property name
* @param fieldVal java bean property value of Supplier
* @since 3.3.0
*/
default MetaObjectHandler fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal) {
if (getFieldValByName(fieldName, metaObject) == null) {
setFieldValByName(fieldName, fieldVal, metaObject);
}
return this;
}
当需要更新的字段已经有值时,则默认不更新。需要强制更新字段时,使用 MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) 方法更新字段值。
@Override
public void updateFill(MetaObject metaObject) {
//获得用户上下文
UserContext dscUser = GlobalSessionContext.getDscUser();
//获得当前时间
Date date = Calendar.getInstance().getTime();
//更新时间
this.setFieldValByName("gmtModified", date, metaObject);
//更新者
this.setFieldValByName("modifierId", metaObject, dscUser.getUserId());
}