前几天同事提的一个疑问,为啥能这么用?由于我也没这么用过,于是带着好奇心去探索了一下并整理了此文章。
场景
枚举类:
@Getter
@AllArgsConstructor
public enum PublishStatus {
PUBLISHED(1, "已发布"), CANCEL(2, "已取消"), DELETED(3, "已删除");
@EnumValue
private final int code;
private String name;
}
Entity实体类:
public class PublishInfo implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private PublishStatus status; //枚举类型
在Service里查询DB数据,如果表里这行记录的status=1,那么映射成Entity的status属性如下:

实现条件
1.mybatis-plus配置里指定枚举路径;
mybatis-plus:
type-enums-package: com.xxx.xxx.enums
mapper-locations: classpath*:mapper/**/*.xml
2.枚举类里使用@EnumValue注解标记要映射的枚举属性,如上。
3.Entity里使用该枚举做为属性类型,如上。
源码解析
这里只贴关键代码
public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// TODO 自定义枚举类扫描处理
if (hasLength(this.typeEnumsPackage)) {
// 取得类型转换注册器
TypeHandlerRegistry typeHandlerRegistry = targetConfiguration.getTypeHandlerRegistry();
classes.stream()
.filter(Class::isEnum)
.filter(MybatisEnumTypeHandler::isMpEnums)
.forEach(cls -> typeHandlerRegistry.register(cls, MybatisEnumTypeHandler.class));
}
return sqlSessionFactory;
}
扫描配置的枚举路径下所有class文件,并MybatisEnumTypeHandler::isMpEnums匹配是否有@EnumValue注解:
@Deprecated
public class MybatisEnumTypeHandler<E extends Enum<?>> extends BaseTypeHandler<Enum<?>> {
/**
* 判断是否为MP枚举处理
*
* @param clazz class
* @return 是否为MP枚举处理
* @since 3.3.1
*/
public static boolean isMpEnums(Class<?> clazz) {
return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
}
/**
* 查找标记标记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();
}
能匹配到的枚举类,会注册到TypeHandlerRegistry。
typeHandlerRegistry.register(cls, MybatisEnumTypeHandler.class)
注册的过程中,有一个关键代码 “c.newInstance(javaTypeClass)”,即MybatisEnumTypeHandler实例化时构造方法里传入了当前枚举类。
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
if (javaTypeClass != null) {
try {
Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
return (TypeHandler<T>) c.newInstance(javaTypeClass);
} catch (NoSuchMethodException ignored) {
// ignored
} catch (Exception e) {
throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
}
}
try {
Constructor<?> c = typeHandlerClass.getConstructor();
return (TypeHandler<T>) c.newInstance();
} catch (Exception e) {
throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
}
}
来看看MybatisEnumTypeHandler的构造方法:
public MybatisEnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
MetaClass metaClass = MetaClass.forClass(type, REFLECTOR_FACTORY);
String name = "value";
if (!IEnum.class.isAssignableFrom(type)) {
name = findEnumValueFieldName(this.type).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.type.getName())));
}
this.invoker = metaClass.getGetInvoker(name);
}
通过findEnumValueFieldName()方法获取@EnumValue注解标记要映射的枚举属性,结合上面场景name就等于枚举类PublishStatus的code属性。然后拿到MethodInvoder,结合上面场景,this.invoker等于枚举类PublishStatus的getCode()方法。
每次映射拿值时,就通过构造方法里赋值的this.invoker的invoke()方法,即枚举类PublishStatus的getCode()方法拿值。
private Object getValue(Object object) {
try {
return invoker.invoke(object, new Object[0]);
} catch (ReflectiveOperationException e) {
throw ExceptionUtils.mpe(e);
}
}
Mybatis填充属性值时,getValue()方法就能拿到 枚举类里对应的值了,并赋值给Entity属性值。
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
setNonNullParameter(ps, i, parameter, jdbcType);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
throws SQLException {
if (jdbcType == null) {
ps.setObject(i, this.getValue(parameter));
} else {
// see r3589
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
}
}
本文揭示了如何在Mybatis Plus中利用枚举类和@EnumValue注解实现数据库查询时的自动映射,包括配置、扫描过程和构造方法细节。
1411

被折叠的 条评论
为什么被折叠?



