mybatis-plus使用@EnumValue搭配shardingsphere报错“getObject with type”

一、背景

最近做项目时使用到了mybatis-plus和shardingsphere。只要在PO中使用了EnumValue注解,加载该PO时就会报“getObject with type”,不使用shardingsphere就不会出现这样的问题。查看报错的堆栈过后发现报错最后在shardingsphere的AbstractUnsupportedOperationResultSet抽象类。实际调用的是该抽象类的子类ShardingResultSet ,但是该子类没有重写父类的 getObject(final int columnIndex, final Class type) 方法,只重写了**getObject(final int columnIndex)**方法,但是在mybatis-plus的枚举类型转换时,调用的是 getObject(final int columnIndex, final Class type) 方法,所以在类型转换时会报错。

二、修改方案

先看下原来的调用层级:类型转换处理类调用CompositeEnumTypeHandler,CompositeEnumTypeHandler再根据当前枚举类是否使用了@EnumValue注解,如果使用了就调用MybatisEnumTypeHandler,如果没使用,就调用的EnumTypeHandler。
流程图

这里其实知道原因过后就简单了,因为知道getObject这个方法需要调用只有一个参数的方法。那么这里我们只需要重新定义一个TypeHandler就可以了。所以我们这里重新定义了一个自己的ShardingMybatisEnumTypeHandler,该handler在getNullableResult的方法时,调用的是**getObject(final int columnIndex)方法。但是这里还需要判断当前是否使用了shardingSphere,如果没使用,还是调用getObject(final int columnIndex, final Class type)**方法。ShardingMybatisEnumTypeHandler类相对于MybatisEnumTypeHandler,修改了getNullableResult方法,具体如下

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object value = rs.getObject(columnName);//原来为 rs.getObject(columnName, this.propertyType);
        if (null == value || rs.wasNull()) {
            return null;
        }
        return this.valueOf(value);
    }

三、如何让修改的TypeHandler生效

1、在@TableField中配置TypeHandler

这个方案失败了。枚举类型的处理类依然是CompositeEnumTypeHandler。

2、考虑直接在TypeHandlerRegistry注册该枚举的handler为自定义的handler处理类。

但是这个有个弊端,只要涉及使用@EnumValue注解的枚举都需要手动指定使用这个Handler,不太通用

3、不止重写MybatisEnumTypeHandler,还重写CompositeEnumTypeHandler类

3.1、修改CompositeEnumTypeHandler类的方法并新建ShardingMybatisEnumTypeHandler类

先看下CompositeEnumTypeHandler类的实现

public class CompositeEnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
	private static final Map<Class<?>, Boolean> MP_ENUM_CACHE = new ConcurrentHashMap<>();
    private static Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    private final TypeHandler<E> delegate;

    public CompositeEnumTypeHandler(Class<E> enumClassType) {
        if (enumClassType == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        if (CollectionUtils.computeIfAbsent(MP_ENUM_CACHE, enumClassType, MybatisEnumTypeHandler::isMpEnums)) {
            delegate = new MybatisEnumTypeHandler<>(enumClassType);
        } else {
            delegate = getInstance(enumClassType, defaultEnumTypeHandler);
        }
    }
......
}

从上面代码我们也看到了CompositeEnumTypeHandler在调用构造函数时,将MybatisEnumTypeHandler写死在了代码中。那么我们重新写一个ShardingCompositeEnumTypeHandler类,将上面的代码的MybatisEnumTypeHandler改为自己的ShardingMybatisEnumTypeHandler。

public class ShardingCompositeEnumTypeHandler<E extends Enum<E>> implements TypeHandler<E> {
 	private static final Map<Class<?>, Boolean> MP_ENUM_CACHE = new ConcurrentHashMap<>();
    private static Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    private final TypeHandler<E> delegate;

    public ShardingCompositeEnumTypeHandler(Class<E> enumClassType) {
        if (enumClassType == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        if (CollectionUtils.computeIfAbsent(MP_ENUM_CACHE, enumClassType, MybatisEnumTypeHandler::isMpEnums)) {
            delegate = new ShardingMybatisEnumTypeHandler<>(enumClassType);//原来为new MybatisEnumTypeHandler<>(enumClassType);
        } else {
            delegate = getInstance(enumClassType, defaultEnumTypeHandler);
        }
    }
......
}

3.2、使ShardingCompositeEnumTypeHandler生效

为了让我们的ShardingCompositeEnumTypeHandler在代码中生效,我们还需要在项目启动时,做一些其他操作。
我们可以通过代码发现,CompositeEnumTypeHandler是在MybatisConfiguration初始化的时候指定

    public MybatisConfiguration() {
        super();
        this.mapUnderscoreToCamelCase = true;
        typeHandlerRegistry.setDefaultEnumTypeHandler(CompositeEnumTypeHandler.class);
        languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);
    }

MybatisConfiguration还提供了setDefaultEnumTypeHandler方法,和getTypeHandlerRegistry。这里直接在项目启动时定义一个configurantion类。

//
@ConditionalOnProperty(prefix = "spring.shardingsphere", name = "enabled", havingValue = "true", matchIfMissing = true) //这个还是非常重要的,只在使用了sharding sphere的时候使用ShardingCompositeEnumTypeHandler
@Configuration
public class ShardingConfig implements ApplicationContextAware{
	private ApplicationContext applicationContext;

	void setApplicationContext(ApplicationContext applicationContext){
		this.applicationContext = applicationContext;
	}
	
	@PostConstruct
	public void init(){
		//获取sqlSessionFactory
		SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
		//获取typeHandlerRegistry
		TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
		//设置DefaultEnumTypeHandler
		typeHandlerRegistry.setDefaultEnumTypeHandler(ShardingCompositeEnumTypeHandler.class);
	}
}

四、总结

1、需要自定义一个TypeHandler
2、需要将该TypeHandler生效。直观的体现就是在TypeHandlerRegistry中查看allTypeHandlersMap对应的类的handler。如果当前的typeHandler没生效,那么就看下生效的handler在什么时候创建的。

MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。在使用 MyBatis-Plus 进行开发时,@EnumValue 注解通常用于自动填充枚举类型的字段值。然而,当你想要通过 HTTP 请求中的 @ResponseBody 提交 JSON 数据时,直接使用 @EnumValue 注解可能无法正常工作,因为它是设计来在 MyBatis-Plus 的自动填充机制中使用的,而不是直接与 Spring MVC 的请求处理交互。 要解决这个问题,你可以采取以下步骤: 1. 自定义枚举转换器:实现 `WebMvcConfigurer` 接口,并重写 `addFormatters` 方法,添加一个自定义的 `ConversionService`,用于处理枚举类型字段的转换。 2. 在自定义枚举转换器中,注册枚举类型的转换器,这样 Spring MVC 就能在请求处理过程中将字符串转换为对应的枚举对象。 下面是一个简单的示例代码: ```java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, YourEnumType>() { @Override public YourEnumType convert(String source) { return YourEnumType.valueOf(source); } }); } } ``` 在这个示例中,你需要将 `YourEnumType` 替换为你需要转换的枚举类的名称。这样,当 Spring MVC 接收到含有枚举类型字段的 JSON 数据时,会使用你的自定义转换器来将字符串转换为枚举对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值