前言
使用Mybatis-Plus的通用枚举功能(https://mp.baomidou.com/guide/enum.html)确实可以很优雅的方式将数据库字段直接映射到我们自定义的枚举类,美中不足的是,官方对于自定义枚举类必须要通过属性配置的方式来扫描枚举类的包路径,在一些非直接发布成Spring Application的场景下,就不够用了。
解决思路
通过查找源代码,mybatis-plus.typeEnumsPackage
的这个配置是直接反应到com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties
这个类里面的,可以对注入到Spring容器中的MybatisPlusProperties重新进行值的设置:
TypeEnumsPackageConfiguration.java
@Configuraton
public class TypeEnumsPackageConfiguration {
@AutoWried
public void configTypeEnumsPackage(MybatisPlusProperties mybatisPlusProperties) {
mybatisPlusProperties.setTypeEnumsPackage(String.join(";", mybatisPlusProperties.getTypeEnumsPackage(), "my.package.type.enums"));
}
}
解决方法
上面的解决思路试过,确实可以成功,但是不够“优雅”,再看源代码,发现官方提供了com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer
来进行MybatisPlusProperties的自定义设置,其实只要创建一个它的子类,就可以在通过实现customize()方法来自定义配置信息了,那既然要强调“优雅”,并且考虑多模块的复用,可以针对typeEnumsPackage的设定专门做一个抽象类,在子类中只配置扫描路径的方式来完成。
TypeEnumsPackageScanner.java
public abstract class TypeEnumsPackageScanner implements MybatisPlusPropertiesCustomizer {
@Override
public void customize(MybatisPlusProperties properties) {
properties.setTypeEnumsPackage(String.join(";", properties.getTypeEnumsPackage(), this.getTypeEnumsPackage()));
}
protected abstract String getTypeEnumsPackage();
}
TypeEnumsPackageScannerConfiguration.java (可在不同模块中分别配置)
@Configuration
public class TypeEnumsPackageScannerConfiguration {
@Bean
public TypeEnumsPackageScanner typeEnumsPackageScanner() {
return new TypeEnumsPackageScanner() {
@Override
protected String getTypeEnumsPackage() {
return "my.package.type.enums";
}
};
}
}
后续
虽然最终满足了需求,其实还是有些许不甘,同样是包扫描,为什么不提供一个类似于@MapperScan
的注解来指定呢,而且自定义枚举必须要实现Mybatis自有的一些接口或者注解,这个自定义枚举类提到api接口层的话,强行把Mybatis-plus的依赖也会加到api层,虽说最新的版本以及进行了优化,只需引入mybatis-plus-annotation
依赖即可,但是有洁癖的人应该受不了吧。
所以接下来准备自定义一个注解,来指定枚举类扫描,并且要做一个com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
的派生类,主要是要覆盖这段逻辑:
/**
* 查找标记标记EnumValue字段
*
* @param clazz class
* @return EnumValue字段
* @since 3.3.1
*/
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
if (clazz != null && clazz.isEnum()) {
String className = clazz.getName();
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
Optional<Field> optional = Arrays.stream(clazz.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(EnumValue.class))
.findFirst();
return optional.map(Field::getName).orElse(null);
}));
}
return Optional.empty();
}
以及参照com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean
注册MybatisEnumTypeHandler的逻辑,实现自定义注解与自定义EnumTypeHandler的绑定:
if (hasLength(this.typeEnumsPackage)) {
Set<Class<?>> classes;
if (typeEnumsPackage.contains(StringPool.STAR) && !typeEnumsPackage.contains(StringPool.COMMA)
&& !typeEnumsPackage.contains(StringPool.SEMICOLON)) {
classes = scanClasses(typeEnumsPackage, null);
if (classes.isEmpty()) {
LOGGER.warn(() -> "Can't find class in '[" + typeEnumsPackage + "]' package. Please check your configuration.");
}
} else {
classes = new HashSet<>();
String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Assert.notNull(typeEnumsPackageArray, "not find typeEnumsPackage:" + typeEnumsPackage);
Stream.of(typeEnumsPackageArray).forEach(typePackage -> {
try {
Set<Class<?>> scanTypePackage = scanClasses(typePackage, null);
if (scanTypePackage.isEmpty()) {
LOGGER.warn(() -> "Can't find class in '[" + typePackage + "]' package. Please check your configuration.");
} else {
classes.addAll(scanTypePackage);
}
} catch (IOException e) {
throw new MybatisPlusException("Cannot scan class in '[" + typePackage + "]' package", e);
}
});
}
// 取得类型转换注册器
TypeHandlerRegistry typeHandlerRegistry = targetConfiguration.getTypeHandlerRegistry();
classes.stream()
.filter(Class::isEnum)
.filter(MybatisEnumTypeHandler::isMpEnums)
.forEach(cls -> typeHandlerRegistry.register(cls, MybatisEnumTypeHandler.class));
}