Mybatis查询参数解析与查询结果处理

查询入参解析

在编写*Mapper.xml时

<insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User">
    insert into user (id, user_name, pass_word, 
      real_name, name)
    values (#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR}, 
      #{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
  </insert>

一般会写一个parameterType和jdbcType
那么这个parameterType起什么作用?
那么看这个insert过程来研究
直接看关键方法
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    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)) { // 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 {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          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);
          }
        }
      }
    }
  }

ParameterMapping

public class ParameterMapping {

  private Configuration configuration;

  private String property;
  private ParameterMode mode;
  private Class<?> javaType = Object.class;
  private JdbcType jdbcType;
  private Integer numericScale;
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;

有两个重要的属性,分别是javaType,和jdbcType
javaType代表入参java类型
jdbcType是对应数据库类型
可以分别指定

 #{userName,javaType=java.lang.String, jdbcType=VARCHAR}

那么这两个type的作用是什么
用于根据入参和反参获取对用的TypeHandler
那么问题来了,什么是TypeHandler

TypeHandler

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Colunm name, when configuration useColumnLabel is false
   * @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;

}


其中 setParameter 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet(根据列名或者索引位置获取) 或 CallableStatement(根据存储过程获取) 中取出数据转换为 java 对象。

主要是实现 从resultSet中根据columnName或者columnIndex来获取数据
当我们在javabean中自定义了枚举类型或者其它某个类型,但是在数据库中存储时往往需要转换成数据库对应的类型,并且在从数据库中取出来时也需要将数据库类型转换为javabean中的对应类型。比如:javabean中字段类型为Date,数据库中存储的是varchar类型;javabean中字段类型是Enum,数据库中存储的是String或者Integer。

因为有大量类似数据的转换,手动转换类型进行存储和查询已经过于麻烦。MyBatis为我们提供了解决办法:TypeHandler类型处理器。

MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了大部分基本类型的类型处理器,所以对于基本类型可以直接处理,当我们需要处理其他类型的时候就需要自定义类型处理器。

MyBatis 内置的 TypeHandler

在 MyBatis 的 TypeHandlerRegistry 类型中,可以看到内置的类型处理器。内置处理器比较多,这里整理常见的一些。
BooleanTypeHandler:用于 java 类型 boolean,jdbc 类型 bit、boolean
ByteTypeHandler:用于 java 类型 byte,jdbc 类型 TINYINT
ShortTypeHandler:用于 java 类型 short,jdbc 类型 SMALLINT
IntegerTypeHandler:用于 INTEGER 类型
LongTypeHandler:用于 long 类型
FloatTypeHandler:用于 FLOAT 类型
DoubleTypeHandler:用于 double 类型
StringTypeHandler:用于 java 类型 string,jdbc 类型 CHAR、VARCHAR
ArrayTypeHandler:用于 jdbc 类型 ARRAY
BigDecimalTypeHandler:用于 java 类型 BigDecimal,jdbc 类型 REAL、DECIMAL、NUMERIC
DateTypeHandler:用于 java 类型 Date,jdbc 类型 TIMESTAMP
DateOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 DATE
TimeOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 TIME
对于常见的 Enum 类型,内置了 EnumTypeHandler 进行 Enum 名称的转换和 EnumOrdinalTypeHandler 进行 Enum 序数的转换。这两个类型处理器没有在 TypeHandlerRegistry 中注册,如果需要使用必须手动配置。

自定义 TypeHandler

自定义类型处理器是通过实现 org.apache.ibatis.type.TypeHandler 接口实现的。这个接口定义了类型处理器的基本功能,接口定义如下所示。
其中 setParameter 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet(根据列名或者索引位置获取) 或 CallableStatement(根据存储过程获取) 中取出数据转换为 java 对象。
实际开发中,我们可以继承 org.apache.ibatis.type.BaseTypeHandler 类型来实现自定义类型处理器。这个类型是抽象类型,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法,如下所示。继承 BaseTypeHandler 类型可以极大地降低开发难度。
Mybatis查询参数解析与查询结果处理_第1张图片
实现BaseTypeHandler 指定泛型就是对应的javaType
把TypeHandler配置到程序中有三种方法:
1.在Mapper.xml中声明

<result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.EnumTypeHandler"/>

2.在mybatis配置文件中设置

<typeHandlers>
        <typeHandler handler="com.xxx.handler.EnumTypeHandler"/>
    </typeHandlers>

3.在springboot的yml配置文件中设置类型处理器所在的包名

mybatis:
  type-handlers-package: com.xxx.handler

注册TypeHandler可以指定jdbcType也可以不指定
org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.reflect.Type, org.apache.ibatis.type.JdbcType, org.apache.ibatis.type.TypeHandler)

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);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

mybatis内置了很多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());

    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());
  }

匹配TypeHandler

private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // #591
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }

先通过javaType获取对应的,所有的handler,在根据jdbcType进一步匹配。也就是说,不同 javaType,jdbcType可以指定不同的handler。如果没注册对应的jdbcType,那么使用key为null(通用)的handler处理

parameterType作用

前面说了每个字段可以指定javaType,和jdbcType
那么parmeterType的作用就是自动指定javaType,例如

 <insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User">
    insert into user (id, user_name, pass_word, 
      real_name, name)
    values (#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
      #{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
  </insert>
public class User {
    private Integer id;

    private String userName;

    private String passWord;

    private String realName;

    private String name;


那么这些字段的javaType会自动匹配到语句的字段上,不需要挨个指定。

不指定javaType和jdbcType

不指定javaType,会自动根据参数类型进行推断

  private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
    TypeHandler<?> handler;
    if (parameter == null) {
      handler = OBJECT_TYPE_HANDLER;
    } else {
      handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
      // check if handler is null (issue #270)
      if (handler == null || handler instanceof UnknownTypeHandler) {
        handler = OBJECT_TYPE_HANDLER;
      }
    }
    return handler;
  }

也就是说javaType不指定也没什么关系,只是后面每次执行都要判断一次,从效率的角度看,还是在xml中能够指定的好,指定jdbcType后也会对入参类型进行检查,如果类型步匹配会报错。

 nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: 
····

不指定jdbcType

根据前面的分析。mybatis不会自动推断jdbcType,如果不指定就会尝试使用jdbcType对应默认的typeHandler处理。因此dbcType还是有必要指定的

查询结果解析

resultMap处理

对结果转换处理时根据resultMap进行转换的

 <resultMap id="BaseResultMap" type="com.mybatis.cache.mybatiscache.domain.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="user_name" jdbcType="VARCHAR" property="userName" />
    <result column="pass_word" jdbcType="VARCHAR" property="passWord" />
    <result column="real_name" jdbcType="VARCHAR" property="realName" />
    <result column="name" jdbcType="VARCHAR" property="name" />
  </resultMap>
 <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from user
    where id = #{id,jdbcType=INTEGER}
  </select>

可见,对于结果的处理,也是根据jdbcType和JavaType进行处理
type实体类解析出每个字段的javaType,同时指定了jdbcType

ResultSetHandler

org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

  ·····
  }

获取ResultMap和行记录进行处理
先看org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

先介绍下rowBounds是mybatis提供的分页工具用法如下

定义:List<A> testLimit(RowBounds rowBounds); 
使用:testMapper.testLimit(new RowBounds(0, 10)) 

先根据RowBounds的offset跳过行
对于每一行数据,调用getRowValue进行处理

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //根据type创建实例
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix)
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnP
    return rowValue;
  }

创建实例之后,进入applyPropertyMappings

 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);

          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

简化来看就是根据propertyMappings处理resultSet中的数据。根据javaType和jdbcType完成数据转换
完成之后设置值到对象上
处理完一行记录后

 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);

ResultHandler

public class DefaultResultHandler implements ResultHandler<Object> {

  private final List<Object> list;

  public DefaultResultHandler() {
    list = new ArrayList<>();
  }

  @SuppressWarnings("unchecked")
  public DefaultResultHandler(ObjectFactory objectFactory) {
    list = objectFactory.create(List.class);
  }

  @Override
  public void handleResult(ResultContext<?> context) {
    list.add(context.getResultObject());
  }

  public List<Object> getResultList() {
    return list;
  }

}

默认的实现就是将结果放到list中
最终返回的就是这个list中的数据。

resultType处理

resultType是sql映射文件中定义一行返回值类型,返回值有基本类型,对象类型,List类型,Map类型等。

基本类型

resultType=基本类型(使用resultSet的每一行的第一列数据作为jdbcType,javaType就是指定的基本类型)

<select id="selectAll" resultType="java.lang.Integer">
    select
    <include refid="Base_Column_List" />
    from user
  </select>

Mybatis查询参数解析与查询结果处理_第2张图片

这样的返回值是每一行的第一列Id值

List类型

: resultType=List中元素的类型

    List<User> selectAll();
 <select id="selectAll" resultType="com.mybatis.cache.mybatiscache.domain.User">
    select
    <include refid="Base_Column_List" />
    from user
  </select>

Map类型

对于map类型那么缺失了javaType,那么返回的字段类型都是根据jdbcTyp推断来的
单条记录:resultType =map

    Map selectAll();
 <select id="selectAll" resultType="java.util.Map">
    select
    <include refid="Base_Column_List" />
    from user
  </select>
               多条记录:resultType =Map中value的类型
    List<Map> selectAll();

对象类型:

对于对象类型resultType直接写对象的全类名就可以了
不过需要注意的是对于返回对象没有resultMapping,mybatis会自动建立映射,但是只有resultType指定的类中的字段与resultSet中的字段完全对上的时候才会生成对应映射

你可能感兴趣的:(mybatis,mybatis,java,数据库)