深入了解MyBatis返回值

深入了解MyBatis返回值

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

我们先看resultTyperesultMap

resultType和resultMap

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

处理resultMapresultType的代码如下:

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setStatementResultMap</span>(
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        MappedStatement.Builder statementBuilder) {
    resultMap = applyCurrentNamespace(resultMap, <span class="hljs-keyword">true</span>);

    List<ResultMap> resultMaps = <span class="hljs-keyword">new</span> ArrayList<ResultMap>();
    <span class="hljs-keyword">if</span> (resultMap != <span class="hljs-keyword">null</span>) {
        String[] resultMapNames = resultMap.split(<span class="hljs-string">","</span>);
        <span class="hljs-keyword">for</span> (String resultMapName : resultMapNames) {
            <span class="hljs-keyword">try</span> {
                resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            } <span class="hljs-keyword">catch</span> (IllegalArgumentException e) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IncompleteElementException(<span class="hljs-string">"Could not find result map "</span> + resultMapName, e);
            }
        }
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (resultType != <span class="hljs-keyword">null</span>) {
        ResultMap.Builder inlineResultMapBuilder = <span class="hljs-keyword">new</span> ResultMap.Builder(
                configuration,
                statementBuilder.id() + <span class="hljs-string">"-Inline"</span>,
                resultType,
                <span class="hljs-keyword">new</span> ArrayList<ResultMapping>(),
                <span class="hljs-keyword">null</span>);
        resultMaps.add(inlineResultMapBuilder.build());
    }
    statementBuilder.resultMaps(resultMaps);

    statementBuilder.resultSetType(resultSetType);
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li></ul>

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

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">private</span> Object <span class="hljs-title">getRowValue</span>(ResultSetWrapper rsw, ResultMap resultMap) <span class="hljs-keyword">throws</span> SQLException {
    <span class="hljs-keyword">final</span> ResultLoaderMap lazyLoader = <span class="hljs-keyword">new</span> ResultLoaderMap();
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, <span class="hljs-keyword">null</span>);
    <span class="hljs-keyword">if</span> (resultObject != <span class="hljs-keyword">null</span> && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
        <span class="hljs-keyword">final</span> MetaObject metaObject = configuration.newMetaObject(resultObject);
        <span class="hljs-keyword">boolean</span> foundValues = resultMap.getConstructorResultMappings().size() > <span class="hljs-number">0</span>;
        <span class="hljs-keyword">if</span> (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, <span class="hljs-keyword">null</span>) || foundValues;
        }
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, <span class="hljs-keyword">null</span>) || foundValues;
        foundValues = lazyLoader.size() > <span class="hljs-number">0</span> || foundValues;
        resultObject = foundValues ? resultObject : <span class="hljs-keyword">null</span>;
        <span class="hljs-keyword">return</span> resultObject;
    }
    <span class="hljs-keyword">return</span> resultObject;
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">if</span> (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
    foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, <span class="hljs-keyword">null</span>) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, <span class="hljs-keyword">null</span>) || foundValues;</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

if中判断的是当前是否支持自动映射(可以配置),这一点很重要,如果不支持,那么没法使用resultType方式,必须用resultMap方式,如果支持resultType方式和resultMap方式可以同时使用

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

如果对象有resultMap,那么还会进行applyPropertyMappings方法。

也就是先处理resultType中自动映射的字段,在处理resultMap中的配置的字段,两者可以同时使用!

下面按照顺序分别说两种方式。

resultType方式

如果支持自动映射,那么会执行applyAutomaticMappings,这里面有metaObject参数。

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">final</span> MetaObject metaObject = configuration.newMetaObject(resultObject);</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">for</span> (String propName : readablePropertyNames) {
    caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>

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

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">final</span> List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

获取列名的时候:

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">for</span> (String columnName : columnNames) {
    <span class="hljs-keyword">final</span> String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
    <span class="hljs-keyword">if</span> (mappedColumns.contains(upperColumnName)) {
        mappedColumnNames.add(upperColumnName);
    } <span class="hljs-keyword">else</span> {
        unmappedColumnNames.add(columnName);
    }
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>

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

因为不管是属性名还是查询列都是大写的,所以只要列名和属性名大写一致,就会匹配上。

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

resultMap方式

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

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">for</span> (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
    <span class="hljs-keyword">final</span> String compositeColumn = compositeResultMapping.getColumn();
    <span class="hljs-keyword">if</span> (compositeColumn != <span class="hljs-keyword">null</span>) {
        resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
    }
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>

这里也转换为了大写。

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

MyBatis接口返回值

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

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

MapperMethod中的部分代码如下:

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">if</span> (method.returnsVoid() && method.hasResultHandler()) {
    executeWithResultHandler(sqlSession, args);
    result = <span class="hljs-keyword">null</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.returnsMany()) {
    result = executeForMany(sqlSession, args);
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (method.returnsMap()) {
    result = executeForMap(sqlSession, args);
} <span class="hljs-keyword">else</span> {
    Object param = method.convertArgsToSqlCommandParam(args);
    result = sqlSession.selectOne(command.getName(), param);
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li></ul>

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

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">this</span>.returnType = method.getReturnType();
<span class="hljs-keyword">this</span>.returnsVoid = <span class="hljs-keyword">void</span>.class.equals(<span class="hljs-keyword">this</span>.returnType);
<span class="hljs-keyword">this</span>.returnsMany = (configuration.getObjectFactory().isCollection(<span class="hljs-keyword">this</span>.returnType) || <span class="hljs-keyword">this</span>.returnType.isArray());</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>

可以看到,这些条件完全就是通过方法的返回值决定的。所以如果你写的返回值是数组或者集合,返回的结果就是多个。

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

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

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

<code class="language-java hljs  has-numbering"><span class="hljs-keyword">public</span> <T> T <span class="hljs-title">selectOne</span>(String statement, Object parameter) {
    List<T> list = <span class="hljs-keyword">this</span>.<T>selectList(statement, parameter);
    <span class="hljs-keyword">if</span> (list.size() == <span class="hljs-number">1</span>) {
        <span class="hljs-keyword">return</span> list.get(<span class="hljs-number">0</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (list.size() > <span class="hljs-number">1</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> TooManyResultsException(<span class="hljs-string">"Expected one result (or null) to be returned by selectOne(), but found: "</span> + list.size());
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul>

注意看:

<code class="language-java hljs  has-numbering">List<T> list = <span class="hljs-keyword">this</span>.<T>selectList(statement, parameter);</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

实际上,不管查询一个还是多个结果,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决定的类型,这很容易理解。但是换成接口就觉得不一样了。

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

特殊情况

sdsd78sd90sd90s7sd7bd!@#JSLD

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值