Mybatis源码解析-类型转换处理

184 阅读4分钟

由于java类型和jdbc类型的差异,在操作数据库之前要把java类型转换成jdbc类型,拿到数据返回时要把jdbc类型转换成java类型,Mybatis作为持久层的ORM框架必然也做了类型之间的转换,本文将会来看一看Mybatis中是如何实现类型转换的。

@Test
void shouldSelectOneAuthorWithInlineParams() {
  try (SqlSession session = sqlMapper.openSession()) {
    Author author = session.selectOne(
        "org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthorWithInlineParams", new Author(101));
    assertEquals(101, author.getId());
  }
}

源码中上面的测试类作为测试入口

TypeHandler

在TypeHander中定义了类型转换的基础接口,其他接口都实现或继承此接口做具体功能实现。

/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

  /**
   * java类型转换为jdbc类型
   * @param ps
   * @param i
   * @param parameter
   * @param jdbcType
   * @throws SQLException
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * 
   * jdbc类型转换为java类型
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
   *
   * @return the result
   *
   * @throws SQLException
   *           the SQL exception
   */
  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的使用

TypeHandlerRegistry

TypeHandlerRegistry 保存了jabc类型到java类型之间需要处理器的关系。

public final class TypeHandlerRegistry {

  //JdbcType和TypeHandler的对应关系
  private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
  //java类型和jdbc类型以及jdbc类型需要的TypeHandler
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
  private final TypeHandler<Object> unknownTypeHandler;
  //所有的TypeHandler
  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;

在构造方法中注册了一些Mybatis提供的实现类

/**
 * The constructor that pass the MyBatis configuration.
 *
 * @param configuration a MyBatis configuration
 * @since 3.5.4
 */
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());

  register(Byte.class, new ByteTypeHandler());
  register(byte.class, new ByteTypeHandler());
  register(JdbcType.TINYINT, new ByteTypeHandler());

  register(Short.class, new ShortTypeHandler());
  register(short.class, new ShortTypeHandler());
  register(JdbcType.SMALLINT, new ShortTypeHandler());

  register(Integer.class, new IntegerTypeHandler());
  register(int.class, new IntegerTypeHandler());
  register(JdbcType.INTEGER, new IntegerTypeHandler());

  register(Long.class, new LongTypeHandler());
  register(long.class, new LongTypeHandler());

  register(Float.class, new FloatTypeHandler());
  register(float.class, new FloatTypeHandler());
  register(JdbcType.FLOAT, new FloatTypeHandler());

  register(Double.class, new DoubleTypeHandler());
  register(double.class, new DoubleTypeHandler());
  register(JdbcType.DOUBLE, new DoubleTypeHandler());

  register(Reader.class, new ClobReaderTypeHandler());
  register(String.class, new StringTypeHandler());
  register(String.class, JdbcType.CHAR, new StringTypeHandler());
  register(String.class, JdbcType.CLOB, new ClobTypeHandler());
  register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
  register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
  register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
  register(JdbcType.CHAR, new StringTypeHandler());
  register(JdbcType.VARCHAR, new StringTypeHandler());
  register(JdbcType.CLOB, new ClobTypeHandler());
  register(JdbcType.LONGVARCHAR, new StringTypeHandler());
  register(JdbcType.NVARCHAR, new NStringTypeHandler());
  register(JdbcType.NCHAR, new NStringTypeHandler());
  register(JdbcType.NCLOB, new NClobTypeHandler());

  register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
  register(JdbcType.ARRAY, new ArrayTypeHandler());

  register(BigInteger.class, new BigIntegerTypeHandler());
  register(JdbcType.BIGINT, new LongTypeHandler());

  register(BigDecimal.class, new BigDecimalTypeHandler());
  register(JdbcType.REAL, new BigDecimalTypeHandler());
  register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
  register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

  register(InputStream.class, new BlobInputStreamTypeHandler());
  register(Byte[].class, new ByteObjectArrayTypeHandler());
  register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
  register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
  register(byte[].class, new ByteArrayTypeHandler());
  register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
  register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
  register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
  register(JdbcType.BLOB, new BlobTypeHandler());

  register(Object.class, unknownTypeHandler);
  register(Object.class, JdbcType.OTHER, unknownTypeHandler);
  register(JdbcType.OTHER, unknownTypeHandler);

  register(Date.class, new DateTypeHandler());
  register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
  register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
  register(JdbcType.TIMESTAMP, new DateTypeHandler());
  register(JdbcType.DATE, new DateOnlyTypeHandler());
  register(JdbcType.TIME, new TimeOnlyTypeHandler());

  register(java.sql.Date.class, new SqlDateTypeHandler());
  register(java.sql.Time.class, new SqlTimeTypeHandler());
  register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

  register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

  register(Instant.class, new InstantTypeHandler());
  register(LocalDateTime.class, new LocalDateTimeTypeHandler());
  register(LocalDate.class, new LocalDateTypeHandler());
  register(LocalTime.class, new LocalTimeTypeHandler());
  register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
  register(OffsetTime.class, new OffsetTimeTypeHandler());
  register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
  register(Month.class, new MonthTypeHandler());
  register(Year.class, new YearTypeHandler());
  register(YearMonth.class, new YearMonthTypeHandler());
  register(JapaneseDate.class, new JapaneseDateTypeHandler());

  // issue #273
  register(Character.class, new CharacterTypeHandler());
  register(char.class, new CharacterTypeHandler());
}

TypeHandlerRegistry的注册

image.png

image.png

image.png

image.png 在解析配置文件的时候如果配置了handler将会进行注册

参数设置值类型转换

先来看下查询数据库前,Mybatis是怎么进行参数设置的

@Override
public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    MetaObject metaObject = 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)) { // issue #448 ask first for additional params
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          if (metaObject == null) {
            metaObject = configuration.newMetaObject(parameterObject);
          }
          value = metaObject.getValue(propertyName);
        }
        //获取参数类型对应的处理器
        //我们这里传参是int 拿到的是IntegerTypeHandler
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}

测试入参new Author(101)

    Author author = session.selectOne(
        "org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthorWithInlineParams", new Author(101));

sql查询的传参是int类型 代码26行拿到类型的处理器IntegerTypeHandler

image.png IntegerTypeHandler 类进行参数设置值

获取结果类型转化

拿到数据库返回结果,把结果集映射成java类型是怎么处理的

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
    String columnPrefix) throws SQLException {
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (!autoMapping.isEmpty()) {
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
      //拿到结果值
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        //最终通过反射设置结果
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

DefaultResultSetHandler类的applyAutomaticMappings()方法处理结果。代码第8行获取value,15行设置值

结果集第一个是int 类型,所以这里最后调用IntegerTypeHandler获取结果

image.png 最后反射设置值

image.png