Mybatis 结果集映射

Mybatis 是支持普通的sql查询,存储过程和高级映射的持久层框架,Mybats消除了几乎所有的jdbc 代码和参数的手动设置以及结果集的检索,mybatis使用简单的xml或注解用于配置和原始映射,将接口和java的pojo 映射为数据库中的记录。

xml 配置

<mapper namespace="mapper.UserMapper">
	<select id="getUser" resultType="user" parameterType="_int">
		select *
		from user where id=#{id ,
		typeHandler=springMybatis.typeHandler.LongTypehandler}
	</select>

	<select id="getUserMap" resultType="map" parameterType="_int">
		select *
		from user where id=#{id ,
		typeHandler=springMybatis.typeHandler.LongTypehandler}
	</select>
</mapper>

typeHandler

public class LongTypehandler extends BaseTypeHandler<String> {

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
			throws SQLException {
		// TODO Auto-generated method stub
		ps.setString(i, parameter);
	}

	@Override
	public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
		// TODO Auto-generated method stub
		 return rs.getString(columnName);
	}

	@Override
	public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		// TODO Auto-generated method stub
		 return rs.getString(columnIndex);
	}

	@Override
	public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		// TODO Auto-generated method stub
		 return cs.getString(columnIndex);
	}

}

测试方法

	@Test
	public void getUserServiceTest() throws IOException {
		try(SqlSession session = sessionFactory.openSession()){
			UserMapper userMapper = session.getMapper(UserMapper.class);
			User user = userMapper.getUser("18");
			System.out.println(user);
		}
	}
	
	@Test
	public void objectFactoryTest() {
		try(SqlSession session = SessionFactory.openSession()){
			UserMapper mapper = session.getMapper(UserMapper.class);
			Map<String,String> userMap = mapper.getUserMap("18");
			System.out.println(userMap);
		}

以上两个测试方法分别将结果集映射为实体类和map集合 , typeHandler将String类型的参数转换为了int类型。mybatis是怎样替我们进行转换的呢,我们从查询的入口开始,逐步分析一下。

根据mapped.xml标签类型以及返回值类型分别使用不同的策略执行

//mapper代理对象判断执行哪种类型的sql
//根据xml中的标签判断,如果是select,执行以下逻辑
if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
      	//如果方法返回void,并且设置了Resulthandler
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
      	//如果返回多个值, -- 通过判断返回类型是否是Collection或者数组
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
      	//返回单个值
        result = executeForMap(sqlSession, args);
      } else {
      	//其他情况,所以即使mapper接口方法设置返回值为void,
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    }

取得相应的参数,rowBounds,resultHandler等回调sqlSession的select方法

  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
  	//根据id也是方法名取得MappedStatement对象
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    //判断是否配置了resultMap或者resultType,针对注解
    if (void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName() 
          + " needs either a @ResultMap annotation, a @ResultType annotation," 
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    //取得sql语句用的参数,会过滤掉resultHandler,rowBounds等一些属性
    Object param = method.convertArgsToSqlCommandParam(args);
    //是否配置了分页
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }

未配置分页使用默认RowBounds对象

  public void select(String statement, Object parameter, ResultHandler handler) {
    select(statement, parameter, RowBounds.DEFAULT, handler);
  }

根据statement取得mappedStatement,statement就是类名+方法名,mappedStatement对象封装了xml中某个节点对象比如:< select >,
具体的执行逻辑交给了执行器Executor

  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
    //根据statement取得mappedStatement,statement就是类名+方法名
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

执行器会先尝试从一级缓存中取出,如果配置了resulthandler是不会走缓存的,因为你可能在resultHandler对结果集做其他操作

  @SuppressWarnings("unchecked")
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //尝试从缓存中取出,配置了resultHandler不走缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
      	//是否是callable,添加到callable缓存
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
      	//通过数据库查询取值
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

取得Statement对象
configuration.newStatementHandler取得一个RoutingStatementHandler,RoutingStatementHandler起一个路由的作用,可以根据不同的statement type取得相应的StatementHandler

prepareStatement方法取得一个Statement对象,并且会根据用户设置对Statement做一些配置,比如查询超时时间 stmt.setQueryTimeout(timeout);
FetchSize(调用rs.next时就取得fetchSize个结果,这样下次就可以直接从缓存中获取了) ,stmt.setFetchSize(fetchSize);并且会对prepareStatement进行参数配置,如果自定义了typeHandler则会使用自定义的设置,如果未配置则使用默认设置

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
    //取得全局配置类
      Configuration configuration = ms.getConfiguration();
      //取得路由statementHandler,会根据不同的statement type取得相应的	StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //取得Statement 对象,并会设置执行超时时间,FetchSize,配置参数等
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

使用TypeHandler来设置参数,TypeHandler继承了BaseTypeHandler,BaseTypeHandler会先对参数值做null判断,如果为null,再判断是配置了jdbcType,如果配置了那么就设置属性值为null ps.setNull(i,jdbcType.TYPE_CODE); 未设置抛出异常,所以我们再可能为null的属性值必须加上{ id,jdbcType = varchar}

  public void setParameters(PreparedStatement ps) throws SQLException {
    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);
          }
          //取得BaseTypeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
          //配置参数
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

配置参数

  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
  //如果参数为null,并且没有配置jdbcType抛出异常,否则设置为空参数
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
        		"Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
        		"Cause: " + e, e);
      }
    } else {
    //由子类实现的方法
      setNonNullParameter(ps, i, parameter, jdbcType);
    }
  }

setNonNullParameter是由子类实现的方法,我们可以自定义自己的typehandler进行属性值配置
自定义typeHandler

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
			throws SQLException {
		// TODO Auto-generated method stub
		ps.setString(i, parameter);
	}

好了,以上我们取得了statement对象,并且对其进行了配置,下面可以进行sql执行了

调用了PreparedStatement的execute方法执行了sql,execute返回一个boolean值,如果是查询的话返回true,如果是更新或插入的话就返回false了;我们可以用getResultSet()取得是结果集,getUpdateCount()取得更新的记数

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //执行sql
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

处理结果集,做最终的映射操作了,getFirstResultSet取出第一个结果集封装在ResultSetWrapper中,取得所有的resultMap配置的java类型,调用handleResultSet循环映射赋值

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
    //多结果集
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    //取得第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    
    //取得配置得resultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    
    validateResultMapsCount(rsw, resultMapCount);
    
    //循环为配置的resultMap赋值
    while (rsw != null && resultMapCount > resultSetCount) {
    	//取得ResultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //映射赋值
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

handleResultSet 判断是否配置了自定义的ResultHandler

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
    	
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
    	  //是否配置了resultHandler
        if (resultHandler == null) {
        	//未配置使用默认Resulthandler
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
        	//使用自定义Resulthandler
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets)
    }
  }
  private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  //是否嵌套了resultMap
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      //处理嵌套的resultMap
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
    //处理普通的resultMap
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

handleRowValuesForSimpleResultMap 中使用skipRows方法处理了分页,
getRowValue取出结果集所映射的对象,使用的方式其实就是用的反射,先使用无参构造实例化对象,再用反射为每个查询表字段所对应的属性赋值,所以我们写的实体类必须要有无参构造,而get,set方法并不需要
storeObject方法使用resultType对所映射处理的java对象做处理

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext resultContext = new DefaultResultContext();
    //处理分页,根据rowBounds配置跳过相应行
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    //鉴别reusltmap
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      //取得结果集映射的对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }

resultHandler对象调用handleResult方法处理结果集,我们自定义resultHandler是必须重写handleResult对象了

  private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    resultHandler.handleResult(resultContext);
  }

如果没有使用自定义的resulthandler,那么就会根据mapperd的放回值来判断了,当返回值是集合时使用DefaultResultHandler,用list存储结果集

 public void handleResult(ResultContext context) {
    list.add(context.getResultObject());
  }

返回值是map时,使用map集合存储

  public void handleResult(ResultContext context) {
    // TODO is that assignment always true?
    final V value = (V) context.getResultObject();
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory);
    // TODO is that assignment always true?
    final K key = (K) mo.getValue(mapKey);
    mappedResults.put(key, value);
  }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值