java常量数据用枚举封装(七)mybatis优雅处理枚举类-通用TypeHandler

前言

经过前文问题的处理,现在对通用的TypeHandler也有一定的把握了。

思路

现在需要明确的内容是,我们要尽量接管TypeHandler的实例化过程,在实例化的时候,将枚举类型设置进TypeHandler中。
可知的是,mybatis提供了TypeHandlerRegistry,所以我们可以自行实例化我们想要的TypeHandler。

初次尝试

首先,编写通用的TypeHandler

通用TypeHandler-可正常使用


import com.will.cn.constants.ConstantEnumIFace;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author jeff
 * 第一个T,表示这个TypeHandler只为实现ConstantEnumIFace的类做数据转换
 * 第二个T,表示ConstantEnumIFace接口中的方法,返回的类型为T
 * 第三个T,表示这个TypeHandler只为T类型数据做数据转换
 */
public class ConstantEnumsTypeHandler<T extends ConstantEnumIFace<T>> extends BaseTypeHandler<T> {

    public ConstantEnumsTypeHandler(Class<T> tClass){
        theEnums =tClass.getEnumConstants()[0];
    }

    private T theEnums;

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getValue());
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int i =rs.getInt(columnName);
        if (0==i && rs.wasNull()) return null;
        return getValue(i);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int i =rs.getInt(columnIndex);
        if (0==i && rs.wasNull()) return null;
        return getValue(i);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int i =cs.getInt(columnIndex);
        if (0==i && cs.wasNull()) return null;
        return getValue(i);
    }

    T getValue(int value){
        return theEnums.theEnum(value);
    }
}

注册相应的TypeHandler

/**
 * @author jeff
 * @time 2023/4/30 22:19
 */
@Configuration
public class MybatisConfiguration implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        //从spring容器获取sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
        //获取typeHandler注册器
        TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
        //注册typeHandler
        typeHandlerRegistry.register(IsDefault.class, new ConstantEnumsTypeHandler(IsDefault.class));
        typeHandlerRegistry.register(UserStatus.class, new ConstantEnumsTypeHandler(UserStatus.class));
    }
}
测试

在这里插入图片描述
可以看到数据已经正常封装了
但这还不够简化,因为每添加一个枚举常量,我们就要去register一个TypeHandler,显然不够通用化。

这时就可以考虑扫包获取枚举实体类,然后使用Class.forName进行反射,获取类对象,再进行register

注册TypeHandler最终版

可以在springboot中加入配置项,配置枚举常量的包名
再在注册时,获取包下所有的class进行反射,解放双手


import com.will.cn.constants.ConstantEnumIFace;
import com.will.cn.handler.ConstantEnumsTypeHandler;
import lombok.Setter;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

import java.io.IOException;

/**
 * @author jeff
 * @time 2023/4/30 22:19
 */
@Configuration
@Setter
public class ConstantEnumConfiguration implements ApplicationContextAware, ResourceLoaderAware {

    @Value("${custom.constants.enum.path}")
    private String enumPath;


    private ResourceLoader resourceLoader;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        //从spring容器获取sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
        //获取typeHandler注册器
        TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
        //注册typeHandler
        try {
            ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
            MetadataReaderFactory metaReader = new CachingMetadataReaderFactory(resourceLoader);
            String s = "classpath*:" + enumPath.replace(".", "/") + "/*.class";
            Resource[] resources = resolver.getResources(s);
            for (Resource r : resources) {
                MetadataReader reader = metaReader.getMetadataReader(r);
                Class<?> aClass = Class.forName(reader.getClassMetadata().getClassName());
                if (ConstantEnumIFace.class.isAssignableFrom(aClass)) {
                    typeHandlerRegistry.register(aClass, ConstantEnumsTypeHandler.class);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

一定要注意的点!

注册TypeHandler的配置类,一定要确保在mybatis完全初始化之前加载!!!
因为如果让Mybatis完成扫包的话,它会对实体类做映射,而扫描到实体中的枚举类型时,会找有无相应的TypeHandler,无,则会映射到默认的EnumTypeHandler上。
后续如果在preparedSql中使用如 #{status} 取数据时,就会去找实体类映射的TypeHandler,而不会去找我们后面注册的TypeHandler
如果我们先它一步注册TypeHandlerMybatis在做映射时找到了对应的TypeHandler,就不会设置其为EnumTypeHandler

解决方案
提高配置类优先级

将配置类的位置放在启动类的下一层中,如:
在这里插入图片描述

优先加载注册TypeHandler的配置类

在启动类中注入该配置类

    @Resource  ConstantEnumConfiguration constantEnumConfiguration;
设置mybatis默认的EnumTypeHandler

Mybatis 默认的处理EnumTypeHandler为我们自定义的TypeHandler
弊端:实体类中所有的enum,都要实现我们的枚举顶级接口,不然会出现class cast异常

值得注意的点

可以看到在扫包过程中,注册TypeHandler时,直接使用的ConstantEnumsTypeHandler.class类对象,而不是自行new对象。
但是ConstantEnumsTypeHandler未提供无参构造,那为什么还能完成注册呢?
因为Mybatis在实例化TypeHandler时,会尝试获取以该类型的class对象为参数的构造器,如果有,则使用该构造器构造TypeHandler
在这里插入图片描述
要是Jackson实例化Deserializer的时候,也这么人性化就好了。。
解决Deserializer的问题花了我不少时间看jackson的源码呢。。

总结

到这里,关于系统中用枚举值替换所有常量的内容就结束了。
有空的话,我会考虑把这套内容,封装成starter包,开源给大家使用~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值