Mybatis的TypeHandler的一個坑
阿新 • • 發佈:2018-12-31
有一個需求是自動掃描專案中的列舉類,然後註冊列舉處理器,遇見了這個問題:列舉也都掃描到了,也註冊到configuration當中去了,但是,查詢的時候還是報錯了!
mapper.xml:
<resultMap id="PersonMap" type="Person"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="gender" property="gender" typeHandler="EnumHandler"/><!-- typeHandler="EnumHandler" --> <result column="addr_id" property="addrId"/> <association property="addr" resultMap="AddressMap" /> </resultMap> <resultMap id="AddressMap" type="Address"> <id column="a_id" property="id"/> <result column="addr" property="addr"/> <result column="status" property="status" typeHandler="EnumHandler"/><!-- typeHandler="EnumHandler" --> </resultMap>
跟蹤mapper的解析過程,首先會進行如下的操作:
如果沒有在配置檔案中顯示的配置typeHandler就直接返回,如果顯示的配置了,則首先從typeHandlerRegistry中根據typeHandler的型別來取。
這裡就有問題了:假如我們的多個bean用的是同一個typeHandler,顯然這裡就會出問題啊!因為無論typeHandler註冊了多少個bean,ALL_TYPE_HANDLERS_MAP中始終只有一個TypeHandler例項!
因此,如果存在多個bean共用用一個typeHandler的情況,則一定不要在配置檔案中顯式的手動指定。
那麼,問題又來了,如果不手動指定又該怎麼搞呢?從上面的程式碼其實也能看出來,如果根據bean的型別從TYPE_HANDLER_MAP這裡面獲取則是正確的。
接著往下看:
這裡是根據列實際的class來查詢typeHandler。這才是正確的邏輯。因此,只要提前把類和類對應的typeHandler註冊進來就ok了。
假如多個類用了同一個typeHandler,但是又想在mapper中顯式設定typeHandler又咋辦呢?那就只能反射了:
try{ Field field = TypeHandlerRegistry.class.getDeclaredField("ALL_TYPE_HANDLERS_MAP"); field.setAccessible(true); Map<Class<?>, TypeHandler<?>> map = (Map<Class<?>, TypeHandler<?>>)field.get(registry); map.remove(EnumHandler.class); }catch(Exception e){ e.printStackTrace(); }
附一個列舉自動掃描的程式碼:
protected static SqlSessionFactory buildSqlSessionFactory() throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
Configuration config = parser.getConfiguration();
scanEnums(config, basePackage);
parser.parse();
return new DefaultSqlSessionFactory(config);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
}
}
}
private static void scanEnums(Configuration configuration, String basePackage) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(Identifiable.class), basePackage);
Set<Class<? extends Class<?>>> mTypes = resolverUtil.getClasses();
for (Class<?> javaTypeClass : mTypes) {
System.out.println("scan enum:" + javaTypeClass);
registerEnumHandler(configuration, javaTypeClass);
}
}
private static void registerEnumHandler(org.apache.ibatis.session.Configuration configuration,
Class<?> javaTypeClass) {
if (javaTypeClass == Identifiable.class) {
return;
}
TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
registry.register(javaTypeClass, EnumHandler.class);
// try{
// Field field = TypeHandlerRegistry.class.getDeclaredField("ALL_TYPE_HANDLERS_MAP");
// field.setAccessible(true);
// Map<Class<?>, TypeHandler<?>> map = (Map<Class<?>, TypeHandler<?>>)field.get(registry);
// map.remove(EnumHandler.class);
// }catch(Exception e){
// e.printStackTrace();
// }
}
原始碼在這裡:https://github.com/xjs1919/enumhandler