目录
- 概述
 - 使用
 - TypeHandler
 - 使用方式
 - 在 select | update | insert 中加入
 
- 配置文件中指定
 
- 源码分析
 - 配置文件指定
 - Mapper
 
- 执行query
 - 如何转换
 
- 结束
 
概述
阅读此文 可以达到
spring boot mybatis TypeHandler源码如何初始化及如何调用的。
spring boot 版本为 2.7.17,mybatis(spring boot) 2.3.2
 TypeHandler 是 mybatis 中 java 对象和数据库 jdbc 之间进行类型转换的桥梁。
调用层级深,比较复杂,关键源码点,给出了详细的位置,打上断点,降低难度。
接口源码如下:
public interface TypeHandler<T> {
  // 从java对象到数据库类型的转换
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  // 下面三个是从数据库到java对象的转换
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
 
使用
TypeHandler
实现 TypeHandler 接口,也可以实现抽象类 BaseTypeHandler
代码如下:
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(Reason.class)
public class ResonJsonTypeHandler extends BaseTypeHandler<Reason> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Reason parameter, JdbcType jdbcType) throws SQLException {
        // 将java对象转换成jdbc中的类型
        ps.setString(i, JSON.toJSONString(parameter));
    }
    @Override
    public Reason getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return JSON.parseObject(rs.getString(columnName), Reason.class);
    }
    @Override
    public Reason getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return JSON.parseObject(rs.getString(columnIndex), Reason.class);
    }
    @Override
    public Reason getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return JSON.parseObject(cs.getString(columnIndex), Reason.class);
    }
}
 
注意:
- @MappedJdbcTypes 代表对应的数据库中字段的类型,json本质上也是字符串
 - @MappedTypes 代表要转换的 JavaBean 对象
 - java对象与json格式的互相转换需要 JSON 来完成
 
使用方式
在 select | update | insert 中加入
写在 xml 只有对应的 sql 会被转换
 
org.apache.ibatis.mapping.ResultMapping.Builder#resolveTypeHandler
配置文件中指定
如下配置,任何使用 Reason 的地方都会进行转换。
mybatis:
  type-handlers-package: com.fun.demo.mybatis.type.handler
 
由于 xml 与 mapper 都是会生成覆盖,所以选择配置文件中指定。
源码分析
配置文件指定
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    final Configuration targetConfiguration;
    XMLConfigBuilder xmlConfigBuilder = null;
....
    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 + "'");
      });
    }
....
 
scanClasses 方法进行加载自定义的 typeHandler
 org.mybatis.spring.SqlSessionFactoryBean#scanClasses
 
 org.apache.ibatis.mapping.ResultMapping.Builder#resolveTypeHandler
 
Mapper
在 Mapper 中自定义时,依然也是在 SqlSessionFactoryBean 的 buildSqlSessionFactory 方法。
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElements
org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode)
org.apache.ibatis.builder.xml.XMLMapperBuilder#resultMapElement(org.apache.ibatis.parsing.XNode, java.util.List<org.apache.ibatis.mapping.ResultMapping>, java.lang.Class<?>)
org.apache.ibatis.builder.MapperBuilderAssistant#buildResultMapping(java.lang.Class<?>, java.lang.String, java.lang.String, java.lang.Class<?>, org.apache.ibatis.type.JdbcType, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Class<? extends org.apache.ibatis.type.TypeHandler<?>>, java.util.List<org.apache.ibatis.mapping.ResultFlag>, java.lang.String, java.lang.String, boolean)
org.apache.ibatis.builder.BaseBuilder#resolveTypeHandler(java.lang.Class<?>, java.lang.Class<? extends org.apache.ibatis.type.TypeHandler<?>>)
 
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
 
具体的解析 /mapper 元素
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"));
    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 ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
      Class<?> enclosingType) {
.......
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
.....
  }
 

buildResultMappingFromContext
 private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
   String property;
   if (flags.contains(ResultFlag.CONSTRUCTOR)) {
     property = context.getStringAttribute("name");
   } else {
     property = context.getStringAttribute("property");
   }
   ......
   String nestedResultMap = context.getStringAttribute("resultMap",
       () -> processNestedResultMappings(context, Collections.emptyList(), resultType));
       ....
   String typeHandler = context.getStringAttribute("typeHandler");
   String resultSet = context.getStringAttribute("resultSet");
   String foreignColumn = context.getStringAttribute("foreignColumn");
	.....
 }
 
String typeHandler = context.getStringAttribute(“typeHandler”);
执行query
如何转换
org.apache.ibatis.executor.SimpleExecutor#doQuery
 
org.apache.ibatis.session.Configuration#newStatementHandler
org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler
org.apache.ibatis.executor.statement.PreparedStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
org.apache.ibatis.executor.statement.RoutingStatementHandler#query
org.apache.ibatis.logging.jdbc.PreparedStatementLogger#invoke
org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue
com.fun.demo.mybatis.type.handler.ResonJsonTypeHandler#getNullableResult(java.sql.ResultSet, java.lang.String)
 

 执行结果
 
结束
spring boot mybatis TypeHandler  如何初始化及如何调用,就解释的非常明白了,如有疑问,欢迎评论区留言。



















