spring boot mybatis TypeHandler 源码如何初始化及调用

概述

阅读此文 可以达到 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

由于 xmlmapper 都是会生成覆盖,所以选择配置文件中指定。

源码分析

配置文件指定

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 如何初始化及如何调用,就解释的非常明白了,如有疑问,欢迎评论区留言。

相关推荐

  1. SpringMVC初始化学习

    2023-12-05 15:40:02       32 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-05 15:40:02       14 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-05 15:40:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-05 15:40:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-05 15:40:02       18 阅读

热门阅读

  1. Kubernetes 使用插件扩展 kubectl

    2023-12-05 15:40:02       42 阅读
  2. Spark_spark shell退出方式

    2023-12-05 15:40:02       34 阅读
  3. golang实现文件上传(高并发+分块+断点续传+加密)

    2023-12-05 15:40:02       40 阅读
  4. 131. 分割回文串

    2023-12-05 15:40:02       33 阅读
  5. Hadoop

    2023-12-05 15:40:02       24 阅读