• 深入了解MyBatis返回值


    深入了解MyBatis返回值

    想了解返回值,我们须要了解resultType,resultMap以及接口方法中定义的返回值。

    我们先看resultTyperesultMap

    resultType和resultMap

    大家应该都知道在MyBatis的<select>标签中有两种设置返回值的方式,各自是resultMapresultType

    处理resultMapresultType的代码例如以下:

    private void setStatementResultMap(
            String resultMap,
            Class<?> resultType,
            ResultSetType resultSetType,
            MappedStatement.Builder statementBuilder) {
        resultMap = applyCurrentNamespace(resultMap, true);
    
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                try {
                    resultMaps.add(configuration.getResultMap(resultMapName.trim()));
                } catch (IllegalArgumentException e) {
                    throw new IncompleteElementException("Could not find result map " + resultMapName, e);
                }
            }
        } else if (resultType != null) {
            ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
                    configuration,
                    statementBuilder.id() + "-Inline",
                    resultType,
                    new ArrayList<ResultMapping>(),
                    null);
            resultMaps.add(inlineResultMapBuilder.build());
        }
        statementBuilder.resultMaps(resultMaps);
    
        statementBuilder.resultSetType(resultSetType);
    }

    能够看到这里会优先处理resultMap可是也使用了resultType

    接下来看MyBatis获取数据后,假设处理一行结果(以简单数据为例。不考虑嵌套情况):

    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
            final MetaObject metaObject = configuration.newMetaObject(resultObject);
            boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
            if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
                foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
            }
            foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
            foundValues = lazyLoader.size() > 0 || foundValues;
            resultObject = foundValues ? resultObject : null;
            return resultObject;
        }
        return resultObject;
    }

    上面这段代码中重要的代码例如以下:

    if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;

    if中推断的是当前是否支持自己主动映射(能够配置),这一点非常重要,假设不支持,那么没法使用resultType方式,必须用resultMap方式。假设支持resultType方式和resultMap方式能够同一时候使用

    这里的基本逻辑是先对没有resultMap的属性自己主动映射赋值,通过applyAutomaticMappings实现。

    假设对象有resultMap。那么还会进行applyPropertyMappings方法。

    也就是先处理resultType中自己主动映射的字段。在处理resultMap中的配置的字段。两者能够同一时候使用!

    以下依照顺序分别说两种方式。

    resultType方式

    假设支持自己主动映射,那么会运行applyAutomaticMappings,这里面有metaObject參数。

    final MetaObject metaObject = configuration.newMetaObject(resultObject);

    我们看看创建metaObject最关键的一个地方,在Reflector类中:

    for (String propName : readablePropertyNames) {
        caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }

    这里将实体中的属性名,做了一个映射。是大写的相应实际的属性名。比如ID:id

    applyAutomaticMappings中的第一行,首先获取没有映射的列名:

    final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);

    获取列名的时候:

    for (String columnName : columnNames) {
        final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
        if (mappedColumns.contains(upperColumnName)) {
            mappedColumnNames.add(upperColumnName);
        } else {
            unmappedColumnNames.add(columnName);
        }
    }

    注意这里将列名转换为大写形式。同一时候保存了mappedColumnNames映射的列和unmappedColumnNames未映射的列。

    由于无论是属性名还是查询列都是大写的,所以仅仅要列名和属性名大写一致,就会匹配上。

    因此我们在写sql的时候。不须要对查询列的大写和小写进行转换。自己主动匹配是不区分大写和小写的。

    resultMap方式

    这样的方式也非常简单,上面提到了mappedColumnNames,在推断是否为映射列的时候,使用mappedColumns.contains(upperColumnName)进行推断,mappedColumns是我们配置的映射的列。那是不是我们配置的时候必须大写呢?

    实际上不用,这里也不区分大写和小写,在<result column="xxx" ../>column也不区分大写和小写,看以下的代码:

    for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
        final String compositeColumn = compositeResultMapping.getColumn();
        if (compositeColumn != null) {
            resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
        }
    }

    这里也转换为了大写。

    到这里关于resultTyptresultMap就结束了。可是有一个简单的问题,非常多人不懂,是什么?看下个标题。

    MyBatis接口返回值

    接口返回值一般是一个结果,或者是List和数组。

    MyBatis怎样知道我想要返回一个结果还是多个结果?

    MapperMethod中的部分代码例如以下:

    if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
    } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
    } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
    }

    能够看到查询结果有4中情况,void,list(和array),map,one

    这里重要就是if的推断条件,这样的推断条件计算方法:

    this.returnType = method.getReturnType();
    this.returnsVoid = void.class.equals(this.returnType);
    this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());

    能够看到,这些条件全然就是通过方法的返回值决定的。

    所以假设你写的返回值是数组或者集合。返回的结果就是多个。

    假设返回值本身有多个,可是返回值写了一个POJO,不是集合或者数组时会怎样?

    答案是会报错TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size())

    无论是返回一个结果还是多个结果。MyBatis都是安装多个结果进行查询,selectOne是查询一个,selectList是查询多个,我们看看selectOne代码:

    public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

    注意看:

    List<T> list = this.<T>selectList(statement, parameter);

    实际上,无论查询一个还是多个结果,MyBatis都是先按多个结果进行查询。拿到list结果后在推断。

    假设是查询一个结果的情况,那么list最多仅仅能有一个返回值。通过上面代码的if else if esle能够非常明确的理解。

    resultTyp,resultMap和返回值多少有关系吗?

    没有不论什么关系。

    通过前面resultTyperesultMap的内容,我们应该知道。这个属性是配置JDBC查询结果怎样映射到一个对象上的。

    无论返回值是什么或者是几个,都是依照resultTyperesultMap生成返回结果。

    返回结果的类型由resultTyperesultMap决定。

    返回结果的类型

    返回结果的类型由resultTyperesultMap决定,是不是非常诧异???

    实际上就是这样的情况。。

    举个样例,有个实体CountryCountry2

    接口中List<Country> selectAll(),xml中的<select id="selectAll" resultType="Country2">.

    当你通过接口调用的时候,返回值是什么?你以为自己的List中的对象类型是Country,但他们实际上都是Country2

    假设接口方法为Country selectById(Integer id),xml中为<select id="selectById" resultType="Country2">,由于类型不一致,查询的时候才会报错:java.lang.ClassCastException: xx.Country2 cannot be cast to xx.Country

    为什么会这样呢?

    这是由于接口调用方式是对命名空间方式调用的封装

    当你通过命名空间方式调用的时候,返回结果的类型是什么?

    就是由resultTyperesultMap决定的类型,这非常easy理解。可是换成接口就觉得不一样了。

    这是由于接口方法方式多了返回值,所以我们会觉得返回的一定是这个类型。

    实际上是错的。

    特殊情况

    当使用纯注解方式时,接口的返回值类型能够起到作用,假设没有使用@ResultType注解指定返回值类型。那么就会使用这里写的返回值类型作为resultType

  • 相关阅读:
    windows7系统下升级到IE11时无法使用F12开发人员工具的解决办法
    微信公众号在线编辑器
    solr安装使用笔记
    在windows资源管理器添加进入当前目录dos窗口的快捷菜单
    spring mvc返回jsonp内容
    oracle最大连接数相关
    redis可视化管理工具Redis Desktop Manager
    Struts2远程代码执行漏洞预警
    postman请求数据库方法(Omysql)
    Selenium+java
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/7074570.html
Copyright © 2020-2023  润新知