MetaObjectHandler
参考【 https://baomidou.com/guides/auto-fill-field/】
在 MyBatis-Plus 中提供了一个便捷的自动填充功能。MetaObjectHandler 是一个用于处理实体对象的元对象的接口。它允许我们在插入、更新、删除等操作之前或之后,对实体对象进行自定义处理。通过 MetaObjectHandler,我们可以实现如自动填充创建时间、更新时间、删除标记等功能。
一、基操
1.1 实现 MetaObjectHandler接口
在上面的示例中,我们使用了 @Component 注解,确保MyMetaObjectHandler类被 Spring 管理。我们创建了一个名为MyMetaObjectHandler 的类,并实现了 MetaObjectHandler 接口。在insertFill方法中,我们自动填充了创建人和创建时间。在 updateFill方法中,我们自动更新数据库表中更新时间与更新人。
/**
* 通用字段处理器
*/
@Component
@Slf4j
public class CommonFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
UserBean userBean = LoginUtil.get();
if (userBean != null) {
// 创建人ID
setFieldValByName("createUserId", LoginUtil.get().getId(), metaObject);
// 创建人名称
setFieldValByName("createUserName", LoginUtil.get().getName(), metaObject);
// 创建时间
setFieldValByName("createTime", new Date(), metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
UserBean userBean = LoginUtil.get();
if (userBean != null) {
// 更新人ID
setFieldValByName("updateUserId", LoginUtil.get().getId(), metaObject);
// 更新人名称
setFieldValByName("updateUserName", LoginUtil.get().getName(), metaObject);
// 更新时间
setFieldValByName("updateTime", new Date(), metaObject);
}
}
}
在上面的示例中,将 CommonFieldHandler 类交由 Spring IoC容器进行管理。在insertFill方法中,自动填充了创建人和创建时间。在 updateFill方法中,自动填充更新人与更新时间。
1.2 使用 MetaObjectHandler
在实体类中,通过 @TableFile(fill = FieldFill.INSERT)
添加创建人、创建时间,通过 @TableField(fill = FieldFill.UPDATE)
添加更新人、更新时间字段,如下:
@Data
public class BaseBean implements Serializable {
private static final long serialVersionUID = -63039799721058887L;
@ApiModelProperty(value = "id")
@TableId(type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "创建人ID")
@TableField(fill = FieldFill.INSERT)
private Long createUserId;
@ApiModelProperty(value = "创建人名称")
@TableField(fill = FieldFill.INSERT)
private String createUserName;
@ApiModelProperty(value = "创建时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@ApiModelProperty(value = "更新人ID")
@TableField(fill = FieldFill.UPDATE)
private Long updateUserId;
@ApiModelProperty(value = "更新人名称")
@TableField(fill = FieldFill.UPDATE)
private String updateUserName;
@ApiModelProperty(value = "更新时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@TableField(fill = FieldFill.UPDATE)
private Date updateTime;
}
1.3 配置自动填充处理器
确保你的 MyMetaObjectHandler 类被 Spring 管理,可以通过 @Component
或 @Bean
注解等来实现。
1.4 使用事项
- 自动填充是直接给实体类的属性设置值。
- 如果属性没有值,入库时会是 null。
MetaObjectHandler
提供的默认方法策略是:如果属性有值则不覆盖,如果填充值为 null 则不填充。- 字段必须声明
@TableField
注解,并设置 fill 属性来选择填充策略。 - 使用
strictInsertFill
或strictUpdateFill
方法可以根据注解FieldFill.xxxx
、字段名和字段类型来区分填充逻辑。 - 如果不需区分,可以使用
fillStrategy
方法。 - 在
update(T entity, Wrapper<T> updateWrapper)
时,entity 不能为空,否则自动填充失效。 - 在
update(Wrapper<T> updateWrapper)
时不会自动填充,需要手动赋值字段条件。
1.5 FieldFill
public enum FieldFill {
DEFAULT, // 默认不处理
INSERT, // 插入填充字段
UPDATE, // 更新填充字段
INSERT_UPDATE // 插入和更新填充字段
}
二、源码解析
2.1 MetaObjectHandler源码
public interface MetaObjectHandler {
// 是否开启了插入填充
default boolean openInsertFill() {
return true;
}
// 是否开启了更新填充
default boolean openUpdateFill() {
return true;
}
// 插入元对象字段填充(用于插入时对公共字段的填充)
void insertFill(MetaObject metaObject);
// 更新元对象字段填充(用于更新时对公共字段的填充)
void updateFill(MetaObject metaObject);
/**
* 通用填充
*/
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
if (Objects.nonNull(fieldVal) && metaObject.hasSetter(fieldName)) {
metaObject.setValue(fieldName, fieldVal);
}
return this;
}
// 获取当前字段value
default Object getFieldValByName(String fieldName, MetaObject metaObject) {
return metaObject.hasGetter(fieldName) ? metaObject.getValue(fieldName) : null;
}
/**
* find the tableInfo cache by metaObject
*/
default TableInfo findTableInfo(MetaObject metaObject) {
return TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
}
/**
* @param metaObject metaObject meta object parameter
*/
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
*/
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
*/
default MetaObjectHandler strictInsertFill(TableInfo tableInfo, MetaObject metaObject, List<StrictFill<?, ?>> strictFills) {
return strictFill(true, tableInfo, metaObject, strictFills);
}
/**
* @param metaObject metaObject meta object parameter
*/
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
*/
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
*/
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也不填充
*/
default MetaObjectHandler fillStrategy(MetaObject metaObject, String fieldName, Object fieldVal) {
if (getFieldValByName(fieldName, metaObject) == null) {
setFieldValByName(fieldName, fieldVal, metaObject);
}
return this;
}
/**
* 严格模式填充策略,默认有值不覆盖,如果提供的值为null也不填充
*/
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;
}
}
2.2 扫描/获取所有MetaObjectHandler对象
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
/**
* 注入一个SqlSessionFactory Bean
*/
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 省略其他代码...
// TODO 注入填充器
this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
// 省略其他代码...
}
/**
* 检查spring容器里是否有对应的bean,有则进行消费
*
* @param clazz class
* @param consumer 消费
* @param <T> 泛型
*/
private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
consumer.accept(this.applicationContext.getBean(clazz));
}
}
}
2.3 MybatisParameterHandler
通过 Mybatis 提供的参数处理器 ParameterHandler
进行赋值,ParameterHandler 主要是解析 Sql 参数和用户传入参数,然后两者进行关联并设置到 PreparedStatement 中去。
MybatisParameterHandler 源码如下:
public class MybatisParameterHandler implements ParameterHandler {
public MybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {
// 省略其他代码...
// 查询的参数信息
this.parameterObject = processParameter(parameter);
}
public Object processParameter(Object parameter) {
/* 只处理插入或更新操作 */
if (parameter != null
&& (SqlCommandType.INSERT == this.sqlCommandType || SqlCommandType.UPDATE == this.sqlCommandType)) {
//检查 parameterObject
if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
return parameter;
}
Collection<Object> parameters = getParameters(parameter);
if (null != parameters) {
parameters.forEach(this::process);
} else {
process(parameter);
}
}
return parameter;
}
private void process(Object parameter) {
if (parameter != null) {
TableInfo tableInfo = null;
Object entity = parameter;
if (parameter instanceof Map) {
// 处理单参数使用注解标记的时候,尝试提取et来获取实体参数
Map<?, ?> map = (Map<?, ?>) parameter;
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(parameter.getClass());
}
if (tableInfo != null) {
//到这里就应该转换到实体参数对象了,因为填充和ID处理都是针对实体对象处理的,不用传递原参数对象下去.
MetaObject metaObject = this.configuration.newMetaObject(entity);
if (SqlCommandType.INSERT == this.sqlCommandType) {
populateKeys(tableInfo, metaObject, entity);
insertFill(metaObject, tableInfo);
} else {
updateFill(metaObject, tableInfo);
}
}
}
}
}