• Mybatis—TypeHandler


      Mybatis在执行SQL语句之前和完成之后都将要处理JDBC类型与Java类型之间的转换,简言之TypeHandler主要的工作就是完成JDBC类型和Java类型之间的相互转换,具体情况如下:

    1. 执行SQL语句之前为PreparedStatement对象参数占位符赋值,具体调用PreparedStatement接口中提供的一系列setter方法,将Java类型转换为对应的JDBC类型;
    2. 执行完成SQL语句获取到ResultSet对象之后,调用ResultSet提供的getter方法将JDBC类型转换为Java类型。
      在这里插入图片描述

      TypeHandler使用模板方法设计模式,TypeHandler接口定义了相关设置参数和获取执行SQL语句完成后结果的一些方法,BaseTypeHandler抽象类实现了TypeHandler接口定义方法的默认实现,并重新定义了赋值和取值的方法,其实现逻辑交给具体实现类来实现。Mybatis提供了常规数据类型的TypeHandler,如果想要实现自定义TypeHandler,通过继承BaseTypeHandler抽象类即可。
      Mybatis中默认实现了很多TypeHandler,如StringTypeHandler、BigDecimalTypeHandler和BooleanTypeHandler等TypeHandler。从BigDecimalTypeHandler源代码可以看出,BigDecimalTypeHandler实现了BaseTypeHandler定义了四个抽象方法,BigDecimalTypeHandler#setNonNullParameter()方法调用PreparedStatement对象的setBigDecimal()方法将BigDecimal类型转换为对应的JDBC类型,并为参数占位符赋值。getNullableResult()方法调用ResultSet对象的getBigDecimal()方法将JDBC中的字符串类型转为BigDecimal类型,并返回列的值。

    public class BigDecimalTypeHandler extends BaseTypeHandler<BigDecimal> {
    
      @Override
      public void setNonNullParameter(
          PreparedStatement ps, int i, BigDecimal parameter, JdbcType jdbcType)
          throws SQLException {
        ps.setBigDecimal(i, parameter);
      }
    
      @Override
      public BigDecimal getNullableResult(ResultSet rs, String columnName)
          throws SQLException {
        return rs.getBigDecimal(columnName);
      }
    
      @Override
      public BigDecimal getNullableResult(ResultSet rs, int columnIndex)
          throws SQLException {
        return rs.getBigDecimal(columnIndex);
      }
    
      @Override
      public BigDecimal getNullableResult(CallableStatement cs, int columnIndex)
          throws SQLException {
        return cs.getBigDecimal(columnIndex);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    TypeHandlerRegistry

      TypeHandlerRegistry定义了JDBC类型、Java类型与TypeHandler之间的映射关系,如下源代码所示,

    public final class TypeHandlerRegistry {
    
      private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = 
        				new EnumMap<>(JdbcType.class);
      private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = 
        				new ConcurrentHashMap<>();
      private final TypeHandler<Object> unknownTypeHandler;
      private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = 
        				new HashMap<>();
    
      private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = 
        				Collections.emptyMap();
    
      private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    TypeHandlerRegistry使用Map数据结构来定义了JDBC类型、Java类型与TypeHandler之间的关系,分别使用jdbcTypeHandlerMap和typeHandlerMap属性存储对应的关系定义。
      TypeHandlerRegistry以构造方法的形式来完成默认JDBC类型、Java类型与TypeHandler关系的定义,并实现getTypeHandler和hasTypeHandler方法对外提供根据Java类型或者JDBC类型获取对应的TypeHandler对象,以及判断Java类型是否有对应的TypeHandler处理对象。

    public TypeHandlerRegistry(Configuration configuration) {
        this.unknownTypeHandler = new UnknownTypeHandler(configuration);
    
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
        // 省略其它代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实例

      以DefaultParameterHandler#setParameters为例,当通过SimpleExecutor类doQuery方法完成SQL语句执行之前,会先调用DefaultParameterHandler类的setParameters方法来完成SQL语句中占位符参数的赋值。

    @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance()
        	.activity("setting parameters")
        	.object(mappedStatement.getParameterMap().getId());
    	// 获取SQL中的占位符参数定义
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              Object value;
              String propertyName = parameterMapping.getProperty();
              if (boundSql.hasAdditionalParameter(propertyName)) { 
                value = boundSql.getAdditionalParameter(propertyName);
              } else if (parameterObject == null) {
                value = null;
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                // 如果类型处理器中有对应的typeHandler,直接赋值
                value = parameterObject;
              } else {
                // 如果没有对应的typeHandler,将参数转化为MetaObject反射工具类
                // 然后从元数据类中寻找当前参数的值
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
              }
              // 获取typeHandler类型处理器
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              // 获取JdbcType Java类型定义
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                // 调用typeHandler的setParameter方法完成占位符参数的赋值
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

      DefaultParameterHandler#setParameters源代码虽然篇幅很大,但是其核心逻辑并不复杂:

    1. 从BoundSql类中获取当前SQL语句中的占位符参数ParameterMapping定义,如果不为空就直接遍历ParameterMapping定义,为每一个占位符参数赋值;
    2. 如果占位符参数的ParameterMode不是OUT就进行赋值处理;
    3. 如果在类型处理器中找到对应的typeHandler,就直接赋值;
    4. 如果在没有找到对应的typeHandler,将参数转化为MetaObject反射工具类,然后从元数据类中寻找当前参数的值;
    5. 得到value值之后,再获取在ParameterMapping中定义的typeHandler类型处理器和Java类型;
    6. 调用typeHandler类型处理器的setParameter方法,将value值设置到当前的PreparedStatement对象。
  • 相关阅读:
    Mysql之表分区
    wps文件没有保存怎么恢复?
    Kubernetes学习笔记-StatefulSet:部署有状态的多副本应用(2)20220625
    华为云ROMA Connect亮相Gartner®全球应用创新及商业解决方案峰会,助力企业应用集成和数字化转型
    独享IP地址的层级划分和管理:打造稳定高效的网络架构
    Spring Cloud OpenFeign - - - > 契约配置
    代谢组学中秋特别篇:中药复肾汤治疗慢性肾衰竭机制探索
    Python库学习(十):Matplotlib绘画库
    第9章 IO流、第 10章 多线程
    2023-09-09 LeetCode每日一题(课程表)
  • 原文地址:https://blog.csdn.net/Jas000/article/details/127711232