Mybatis的TypeHandler注册流程
最近在项目中用到了自定义的枚举类typeHandler,参考了网上的代码,定义的枚举类处理器如下:
@MappedTypes({ BaseCodeEnum.class, UserType.class, UserStatus.class, Gender.class }) public class CodeEnumTypeHandler<E extends Enum<?> & BaseCodeEnum> extends BaseTypeHandler<BaseCodeEnum> { private Class<E> type; public CodeEnumTypeHandler(Class<E> type) { if (type == null) { throw new IllegalArgumentException("Type argument cannot be null."); } this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, BaseCodeEnum parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } @Override public BaseCodeEnum getNullableResult(ResultSet rs, String columnName) throws SQLException { int code = rs.getInt(columnName); return rs.wasNull() ? null : CodeEnumUtil.codeOf(type, code); } @Override public BaseCodeEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int code = rs.getInt(columnIndex); return rs.wasNull() ? null : CodeEnumUtil.codeOf(type, code); } @Override public BaseCodeEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int code = cs.getInt(columnIndex); return cs.wasNull() ? null : CodeEnumUtil.codeOf(type, code); } }
最开始使用的mybatis-spring-boot-starter版本为2.1.0,发现在自定义的TypeHandler上使用@MappedTypes注解标注需要处理的Java枚举类型,并在spring配合文件中如下配置并不生效:
mybatis:
mapper-locations: classpath:/mapper/*
type-aliases-package: com.xx.**.entity
type-handlers-package: com.xx.mybatis.handler
需要在mapper.xml文件中指定TypeHandler才可以进行正确的存储与读取
<resultMap type="com.xx.UserEntity" id="userResultMap"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="status" column="status" typeHandler="com.xx.mybatis.handler.CodeEnumTypeHandler"/> </resultMap>
由于某些原因,需要将mybatis-spring-boot-starter版本升级为2.1.4,发现在使用该版本时,代码不能正常的运行,追踪代码发现,在读取数据时,拿到的TypeHandler的目标java类型不正确,为了解决这个问题,追踪了mybatis的TypeHandler注册流程,发现注册的过程可以正常的进行,试着把mapper.xml文件中的TypeHandler去掉以后,发现代码可以正常运行
<resultMap type="com.xx.UserEntity" id="userResultMap"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="status" column="status"/> </resultMap>
为此,下载了mybatis-spring-boot-starter的源码进行阅读,相关的代码入口在SqlSessionFactoryBean.java
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); // 具体的代码逻辑在次方法内部 this.sqlSessionFactory = buildSqlSessionFactory(); }
buildSqlSessionFactory涉及到的内容较大,这里只对TypeHandler的处理部分做记录:
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { ...... // spring配置文件中配置的mybatis.type-handlers-package,扫描给定目录下的TypeHandler实现类,进行注册 if (hasLength(this.typeHandlersPackage)) { scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) .forEach(targetConfiguration.getTypeHandlerRegistry()::register); // 注册处理流程 } if (!isEmpty(this.typeHandlers)) { Stream.of(this.typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'"); }); } targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler); if (this.mapperLocations != null) { if (this.mapperLocations.length == 0) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found."); } else { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); // 对mapper.xml文件进行处理,解析resultMap、sql等,配置文件中的TypeHandler的处理流程在这个流程中 xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'"); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified."); } ...... return this.sqlSessionFactoryBuilder.build(targetConfiguration); }
下面关注一下register方法的内部逻辑:
public void register(Class<?> typeHandlerClass) { boolean mappedTypeFound = false; // 获取TypeHandler处理的java类型 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { // 对每个java类型注册TypeHandler register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { register(getInstance(null, typeHandlerClass)); } } public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) { // 关键的代码在getInstance内部 register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass)); } public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { register((Type) javaType, typeHandler); } private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { register(javaType, null, typeHandler); } } // register最终调用的为该方法 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<>(); } map.put(jdbcType, handler); typeHandlerMap.put(javaType, map); } // 注意:mapper.xml文件中获取的TypeHandler是从该map中进行获取的, // 可以发现,当TypeHandler的@MapperTypes有多个时,该map中的数据前面的会被后面的更新掉,所以mapper.xml文件中获取得到的TypeHandler均为同一个 // 导致枚举类型获取实例会出现获取不到、赋值(调用set方法)的时候会出现类型不匹配的问题 allTypeHandlersMap.put(handler.getClass(), handler); }
接下来看一下mapper.xml文件中关于TypeHandler的解析,具体逻辑在XMLMapperBuilder的parse方法中:
public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析逻辑 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); // 可参考parameterMap的解析,其他元素中的TypeHandler处理类似 parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } } private void parameterMapElement(List<XNode> list) { for (XNode parameterMapNode : list) { String id = parameterMapNode.getStringAttribute("id"); String type = parameterMapNode.getStringAttribute("type"); Class<?> parameterClass = resolveClass(type); List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter"); List<ParameterMapping> parameterMappings = new ArrayList<>(); for (XNode parameterNode : parameterNodes) { String property = parameterNode.getStringAttribute("property"); String javaType = parameterNode.getStringAttribute("javaType"); String jdbcType = parameterNode.getStringAttribute("jdbcType"); String resultMap = parameterNode.getStringAttribute("resultMap"); String mode = parameterNode.getStringAttribute("mode"); String typeHandler = parameterNode.getStringAttribute("typeHandler"); Integer numericScale = parameterNode.getIntAttribute("numericScale"); ParameterMode modeEnum = resolveParameterMode(mode); Class<?> javaTypeClass = resolveClass(javaType); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale); parameterMappings.add(parameterMapping); } builderAssistant.addParameterMap(id, parameterClass, parameterMappings); } } public ParameterMapping buildParameterMapping( Class<?> parameterType, String property, Class<?> javaType, JdbcType jdbcType, String resultMap, ParameterMode parameterMode, Class<? extends TypeHandler<?>> typeHandler, Integer numericScale) { resultMap = applyCurrentNamespace(resultMap, true); // Class parameterType = parameterMapBuilder.type(); Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType); // 获取TypeHandler TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); return new ParameterMapping.Builder(configuration, property, javaTypeClass) .jdbcType(jdbcType) .resultMapId(resultMap) .mode(parameterMode) .numericScale(numericScale) .typeHandler(typeHandlerInstance) .build(); } protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) { if (typeHandlerType == null) { return null; } // javaType ignored for injected handlers see issue #746 for full detail // 获取TypeHandler TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType); if (handler == null) { // not in registry, create a new one handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType); } return handler; } public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) { // 参见上面的说明,可以指定获取的针对不同类型的Enum,获取得到的handler是同一个,会出现类型不匹配的问题 return allTypeHandlersMap.get(handlerType); }
posted on 2021-05-10 14:57 maoshiling 阅读(1145) 评论(0) 编辑 收藏 举报