1. 程式人生 > >Mybatis的TypeHandler的一個坑

Mybatis的TypeHandler的一個坑

有一個需求是自動掃描專案中的列舉類,然後註冊列舉處理器,遇見了這個問題:列舉也都掃描到了,也註冊到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