深入了解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决定的类型,这很容易理解。但是换成接口就觉得不一样了。

这是由于接口方法方式多了返回值,所以我们会认为返回的一定是这个类型。实际上是错的。

特殊情况

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

评论 18 您还未登录,请先 登录 后发表或查看评论
MyBatis 目录(?)[-] mybatis实战教程mybatis in action之一开发环境搭建 mybatis实战教程mybatis in action之二以接口的方式编程 mybatis实战教程mybatis in action之三实现数据的增删改查 mybatis实战教程mybatis in action之四实现关联数据的查询 mybatis实战教程mybatis in action之五与spring3集成附源码 mybatis实战教程mybatis in action之六与Spring MVC 的集成 mybatis实战教程mybatis in action之七实现mybatis分页源码下载 mybatis实战教程mybatis in action之八mybatis 动态sql语句 mybatis实战教程mybatis in action之九mybatis 代码生成工具的使用 mybatis SqlSessionDaoSupport的使用附代码下载 转自:http://www.yihaomen.com/article/java/302.htm (读者注:其实这个应该叫做很基础的入门一下下,如果你看过Hibernate了那这个就非常的简单) (再加一条,其实大家可以看官方的教程更好些:http://mybatis.github.io/mybatis-3/,而且如果英文不是很好的那就看中文的:http://mybatis.github.io/mybatis-3/zh/sqlmap-xml.html) 写在这个系列前面的话: 以前曾经用过ibatis,这是mybatis的前身,当时在做项目时,感觉很不错,比hibernate灵活。性能也比hibernate好。而且也比较轻量级,因为当时在项目中,没来的及做很很多笔记。后来项目结束了,我也没写总结文档。已经过去好久了。但最近突然又对这个ORM 工具感兴趣。因为接下来自己的项目中很有可能采用这个ORM工具。所以在此重新温习了一下 mybatis, 因此就有了这个系列的 mybatis 教程. 什么是mybatis MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录. orm工具的基本思想 无论是用过的hibernate,mybatis,你都可以法相他们有一个共同点: 1. 从配置文件(通常是XML配置文件中)得到 sessionfactory. 2. 由sessionfactory 产生 session 3. 在session 中完成对数据的增删改查和事务提交等. 4. 在用完之后关闭session 。 5. 在java 对象和 数据库之间有做mapping 的配置文件,也通常是xml 文件。 mybatis实战教程(mybatis in action)之一:开发环境搭建 mybatis 的开发环境搭建,选择: eclipse j2ee 版本,mysql 5.1 ,jdk 1.7,mybatis3.2.0.jar包。这些软件工具均可以到各自的官方网站上下载。 首先建立一个名字为 MyBaits 的 dynamic web project 1. 现阶段,你可以直接建立java 工程,但一般都是开发web项目,这个系列教程最后也是web的,所以一开始就建立web工程。 2. 将 mybatis-3.2.0-SNAPSHOT.jar,mysql-connector-java-5.1.22-bin.jar 拷贝到 web工程的lib目录. 3. 创建mysql 测试数据库和用户表,注意,这里采用的是 utf-8 编码 创建用户表,并插入一条测试数据 程序代码 程序代码 Create TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userName` varchar(50) DEFAULT NULL, `userAge` int(11) DEFAULT NULL, `userAddress` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; Insert INTO `user` VALUES ('1', 'summer', '100', 'shanghai,pudong'

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:酷酷鲨 设计师:CSDN官方博客 返回首页

打赏作者

isea533

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值