自定义MyBatis通用枚举类型处理器 --- 是真的通用

2024-02-04 10:56:13 浏览数 (2)

自定义 MyBatis 通用枚举类型解析器

在使用MyBatis的过程中,我们经常会使用到枚举类型的数据, 一般在保存数据时只是想将枚举类型的code值存入到数据库中,查询时希望能自动根据code值映射出对应的枚举对象出现,而不是查询出code值然后再手动根据code值找到对应的枚举对象的转换

官方注册方案

官方方案:https://mybatis.org/mybatis-3/zh_CN/configuration.html#typeHandlers 无法对所有枚举类型进行通用注册(有可能是没找到正确的方式,如果有,恳请大家指导)

自动注册方案

实现思路如下:

1. 自定义注解用于标识枚举字段code值(可以使用Jackson自带的@JsonValue注解,也可以单独自定义注解),注解标识的字段类型非固定类型,可为`Integer`、`Long`、`String`等其他基本类型或其他类型(其他类型请多测试) 2. 自定义枚举类型处理器MyBatisEnumTypeHandler.java 继承自org.apache.ibatis.type.BaseTypeHandler,用于处理枚举类型数据的保存和查询使用

代码语言:java复制
@Slf4j
public class MyBatisEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

    private final Class<E> type;
    public MyBatisEnumTypeHandler(Class<E> type) {
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        try {
            Field field = EnumValueMarkerFinder.find(type);
            Object val = field.get(parameter);
            if (jdbcType == null) {
                ps.setObject(i, val);
            } else {
                ps.setObject(i, val, jdbcType.TYPE_CODE);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object s = rs.getObject(columnName);
        return findTargetEnum(s, type);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Object s = rs.getObject(columnIndex);
        return findTargetEnum(s, type);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object s = cs.getObject(columnIndex);
        return findTargetEnum(s, type);
    }

    private E findTargetEnum(Object val, Class<E> type) {
        if (val == null) {
            return null;
        }
        try {
            Field field = EnumValueMarkerFinder.find(type);
            for (E enumConstant : type.getEnumConstants()) {
                Object o = field.get(enumConstant);
                if (val.equals(o)) {
                    return enumConstant;
                }
            }
        } catch (IllegalAccessException e) {
            log.error("Handle enum failed...", e);
        }
        return null;
    }
}

3. 接下来,怎么将自定义的枚举类型处理器用于处理所有枚举类型的数据?

4. 为了实现所有的枚举都自动注册通用类型转换器,这里需要自定义一个配置类CustomizeMyBatisConfiguration.java并实现org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer接口

1. 实现该接口后,可以获取到org.apache.ibatis.session.Configuration配置类, 2. 使用Configuration配置类获取到`TypeHandlerRegistry`注册器, 3. 再使用TypeHandlerRegistry注册器将需要处理的枚举类类型解析器注册进去

代码语言:java复制
public class CustomizeMyBatisConfiguration implements ConfigurationCustomizer{ 
    public void customize(Configuration configuration) {
        // 将自定义的通用枚举类型处理器`MyBatisEnumTypeHandler`注册进去
        // Class clazz = null; // 怎么获取到需要处理的枚举类,即字段中标了@JsonValue注解或自定义注解的枚举类? 
        configuration.getTypeHandlerRegistry().register(clazz, new MyBatisEnumTypeHandler<>(clazz));
    }
}

5. 获取所有需要注册到通用枚举类型处理器中的枚举类 1. 在 customize 方法中通过Spring框架中ClassPathScanningCandidateComponentProvider扫描器在`classpath`下扫描出指定包下的枚举类 2. 自定义一个类型过滤器com.kws.mybatis.config.CustomizeMyBatisConfiguration.EnumTypeFilter,用于在类路径扫描时,过滤出需要处理的枚举类(1.枚举类型 2.枚举类型中含有自定义注解字段)

代码语言:java复制
public static class EnumTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
        String typeName = metadataReader.getClassMetadata().getSuperClassName();
        if (!ENUM_TYPE.equals(typeName)) {
            return false;
        }
        try {
            Class<?> clazz = ClassUtils.forName(metadataReader.getClassMetadata().getClassName(), getClass().getClassLoader());
            return EnumValueMarkerFinder.hasAnnotation(clazz);
        } catch (ClassNotFoundException e) {
            log.error("EnumTypeFilter match failed. Class not found: "   metadataReader.getClassMetadata(), e);
        }
        return false;
    }
}

3. 过滤出需要处理的枚举类后,通过`TypeHandlerRegistry`将当前枚举类型使用通用的枚举类型处理器注册到类型处理器中 4. 具体注册代码如下

代码语言:java复制
@Slf4j
@Component
public class CustomizeMyBatisConfiguration implements ConfigurationCustomizer {
    /**
    * 可改成读取配置文件包路径.
    * 注意:
    * 如果需要从配置文件读取,直接通过@Value注解注入不会生效,
    * 需要实现EnvironmentAware接口,通过EnvironmentAware接口获取配置
    */
    private static final String BASE_SCAN_PACKAGE = "com.kws";
    public static final String ENUM_TYPE = "java.lang.Enum";

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void customize(Configuration configuration) {
        ClassPathScanningCandidateComponentProvider classPathScanning = new ClassPathScanningCandidateComponentProvider(false);
        classPathScanning.addIncludeFilter(new EnumTypeFilter());
        Set<BeanDefinition> enumsBeanDefinitions = classPathScanning.findCandidateComponents(BASE_SCAN_PACKAGE);
        if (CollectionUtils.isEmpty(enumsBeanDefinitions)) {
            return;
        }

        for (BeanDefinition bd : enumsBeanDefinitions) {
            try {
                log.info("====== register TypeHandler for Enum ======【{}】", bd.getBeanClassName());
                Class clazz = ClassUtils.forName(Objects.requireNonNull(bd.getBeanClassName()), getClass().getClassLoader());
                configuration.getTypeHandlerRegistry().register(clazz, new MyBatisEnumTypeHandler<>(clazz));
            } catch (Exception e) {
                log.error("====== Register Mybatis TypeHandler Failed. Enum:【{}】", bd.getBeanClassName(), e);
            }
        }
    }

    /**
    * 自定义枚举类型过滤器 <p>
    * 1.过滤枚举类型 <p>
    * 2.枚举类型字段必须打了枚举类型注解(或自定义注解) <p>
    *
    * @author kws
    * @date 2024-01-14 17:19
    */
    public static class EnumTypeFilter implements TypeFilter {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
            String typeName = metadataReader.getClassMetadata().getSuperClassName();
            if (!ENUM_TYPE.equals(typeName)) {
                return false;
            }
            try {
                Class<?> clazz = ClassUtils.forName(metadataReader.getClassMetadata().getClassName(), getClass().getClassLoader());
                return EnumValueMarkerFinder.hasAnnotation(clazz);
            } catch (ClassNotFoundException e) {
                log.error("EnumTypeFilter match failed. Class not found: "   metadataReader.getClassMetadata(), e);
            }
            return false;
        }
    }
}

完整代码已发布github

github: enum-mapping 模块:`enum-mapping-mybatis`

0 人点赞