目录
一、背景
最近做项目时使用到了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在什么时候创建的。