Mybatis源码阅读9 --- ResultMap-处理返回值

sql语句无非增删改查,对于返回结果,增删改只需知道成功与否,查询就稍稍复杂,我们来看下mybatis如何处理的。在sql的select必须要定义返回resultType或者resultMap,resultType就是一个class对象(HashMap,自定义Java类如City),若返回类型稍稍复杂些就需要ResultMap了,ResultMap的元素:constructor、id、result、association、collection、discriminator。最基本的是id和result:

    
        
        
        
        
        
    

id和result就是select出的column映射到java对象属性的过程,除了property和column,还有另外三个属性:javaType、jdbcType、typeHandler,javaType和jdbcType一般不需指定(我还不清楚需要指定的情况),先瞧一眼构建出来的ResultMap:

Mybatis源码阅读9 --- ResultMap-处理返回值_第1张图片

没啥特殊的,构建完直接塞到configuration中去,构建select的MappedStatement时再从configuration取出来,最后在处理ResultSet时再使用(这里能看见一个MappedStatement可对应多个resultMap,暂时没有想到这种使用场景):

Mybatis源码阅读9 --- ResultMap-处理返回值_第2张图片

先从简单的(只定义id和property的resultMap:cityResultMap,方法->cityDao.getById)开始,继续debug(上图红框),发现会创建一个resultMap的type对象:

Mybatis源码阅读9 --- ResultMap-处理返回值_第3张图片

创建完对象就该给对象赋值了:

Mybatis源码阅读9 --- ResultMap-处理返回值_第4张图片

赋值前先check是否要自动mapping,AutoMappingBehavior有Null、Partial、None三个枚举值,默认为Partial,在cityDao.getById调用中,applyAutomaticMappings中并没有做什么,在applyPropertyMappings中,通过propertyMapping挨个通过metaObject给rowValue字段赋值并返回了rowValue,最终结果保存到这里了:

Mybatis源码阅读9 --- ResultMap-处理返回值_第5张图片

整个流程还是比较简单:拿到resultMap对象->创建返回对象->propertyMappings赋值->typeHandler取值->通过metaObject将返回值set到返回的对象->返回对象保存到DefaultResultHandler中并最终返回List对象。在看下带构造函数的resultMap:

    
        
            
            
            
        
        
        
    

何时回使用呢?当有些字段我并不想提供get方法时(听起来有道理,实际上还没碰见这种case),当resultMap有constructor元素时,创建的返回对象就是调用了带参数的构造函数:

Mybatis源码阅读9 --- ResultMap-处理返回值_第6张图片

继续看association,它的作用是处理1对1或者说拥有一个XXX,比如一个city,有一个country属性,association有两种方式:Nested Select和Nested Results,先看下nested select:

    
        
        
        
        
        
    

CityCountry类中加了一个Country属性,Country类对应country表,选择几个字段,不用全部一一对应:

Mybatis源码阅读9 --- ResultMap-处理返回值_第7张图片

sql的定义:

    

    

其是就是调用了select两次,association元素和result元素一样,都被构建为ResultMapping,通过ResultMapping获取返回值:

Mybatis源码阅读9 --- ResultMap-处理返回值_第8张图片

getNestedQueryMappingValue方法中又调用了Executor的selectList方法去查询:

Mybatis源码阅读9 --- ResultMap-处理返回值_第9张图片

继续看下nested results,先看resultMap定义:

   
        
        
        
        
        
    

    
        
        
        
        
        
            
            
            
            
        
    

    
        
        
        
        
        
        
    

3个,只是写法不太一样,简单起见,countryResultMap中只映射了不几个字段:

    
        
        
        
        
    

使用resultMap的select语句:



    

    

3个left join把city中对应的country也查出来了,city表和country表都有name、code,为了区分就把country查询的结果加了country_,前两个区别就是resultMap不一样,最后一个join了country表两次,是为了验证resultMap中的columnPrefix属性,具体看下代码:

Mybatis源码阅读9 --- ResultMap-处理返回值_第10张图片

处理返回结果时判断resultMap是否有nestedResultMaps(这个if为什么为true可看下resultMap构建),看下这个方法:

Mybatis源码阅读9 --- ResultMap-处理返回值_第11张图片

额,不太清楚具体何用,如discrimatedResultMap、rowKey,resultOrdered,继续debug到了getRowValue方法:

Mybatis源码阅读9 --- ResultMap-处理返回值_第12张图片

创建返回结果(CityCountry),应用propertyMapping赋值属性(applyPropertyMappings),给nestedResultMapping赋值(applyNestedResultMapping),在applyNestedResultMapping方法中又通过getRowValue把Country对象返回了,整个流程还有很多细节不是很清楚,接着看下一个resultMap,association中的child定义了country的映射:

Mybatis源码阅读9 --- ResultMap-处理返回值_第13张图片

和第一个resultMap一样,在构建association的ResultMapping时又生成了ResultMap对象,这里我们的association属性并没有resultMap,那这个ResultMapping的nestResultMap就是新构建的ResultMap了,后续流程没有其它区别,继续最后一个,在有columnPrefix时:

Mybatis源码阅读9 --- ResultMap-处理返回值_第14张图片

mappedColumnNames会加上columnPrefix,这样就和select xxx as prefix_xxx对应了。

association来解决“has-one”问题,使用nested select会导致n+1问题(查询一个集合-select * from city,这里一条sql所以为+1,这条sql会导致n个select * from country执行,n为city结果数)。使用nested resultMap可以吧resultmap可以把resultMap的定义写在association内部,但内部的resultMap是不能复用的。

collection是来解决“has-many” 问题,collection和association基本类似,有nested select和nested resultMap:

    
        
        
        
        
        
        
    

    
        
        
        
        
        
        
    

先看第一个resultMap,javaType可以不指定,column为select语句查的column并把该结果作为getCityByCode的参数,ofType声明返回的list的类型,但可以不定义它(第一个resultMap就没有用到它),执行流程和association的nested select一模一样,我们看下第二个resultmap,由于1对多,那么select查出来的结果会有n条,那就需要把这1条(CountryCity)保存起来,并把n条(City)存到CountryCity的list对象中,直接定位到第二个countryResultMap2的代码执行处:

Mybatis源码阅读9 --- ResultMap-处理返回值_第15张图片

while循环处,先生成了CacheKey并通过该key获取partialObject,第一次循环partialObject为null,之后会获取rowValue(Country对象)且storeObject,下一次循环就是处理1对N的N(City对象)。CacheKey的生成用到了resultMap中的id元素:

Mybatis源码阅读9 --- ResultMap-处理返回值_第16张图片

因为1对多,但查询结果也是多条,那如何区分多条结果中重复的那个1呢,因为表中的主键(对应resultMap的id)一般是唯一的,所以根据id对应的列名(id或者其他,这里就是code)及该列的值生成CacheKey:

Mybatis源码阅读9 --- ResultMap-处理返回值_第17张图片

这样就是区分多条中重复的那条记录了。

最后再看看discriminator,先举个栗子说明使用场景,Animal类有3个字段,Dog类继承Animal且有一个独有字段,Cat类也继承Animal且有一个独有字所以有5个字段(a,b,c,d,e),我们只定义一个表存储所以Animal对象,那就用字段f表示这条数据类型(如:1代表dog,2代表cat),基于此种情况(现实中可有?)我们想查询时直接生成相应的对象(Cat、Dog),discriminator就是来解决这一的问题,先看下怎么用:

       
        
        
        
            
            
            
                
                
            
            
        
    

    
        
    

    
        
    

    
        
    


    

还拿老朋友City来说明,我们的resultMap返回CityBase对象,CityBase有id和name属性,discriminator根据CountryCode的值返回不同的resultMap,或者说用discriminator的resultMap替换了cityResultMap1,4个case中,chnResult使用extends,usaResult没有extends,fra把result属性定义在子元素中,最后一个的resultMap定义了autoMapping为false,相应的Java对象就不贴了,CityUSA、CityFrance、CityChina、CityDEU都继承了CityBase,最后在select语句中,我们查出5个city(Tokyo为了说明没有匹配的case时),定位到构建Discriminator的代码处:

Mybatis源码阅读9 --- ResultMap-处理返回值_第18张图片

FRA只定义了resultType,不过也给生成了resultMap(其是三个case都生成了resultMap):

Mybatis源码阅读9 --- ResultMap-处理返回值_第19张图片

extends的作用:

Mybatis源码阅读9 --- ResultMap-处理返回值_第20张图片

继续看Discriminator在处理返回结果时:

Mybatis源码阅读9 --- ResultMap-处理返回值_第21张图片

这个方法就是把原来的resultMap替换掉,上图箭头处先根据resultset和discriminator定义的column查询出value(这里查出JPN),而JPN并没有对应的ResultMap,那就返回原来的ResultMap了,当然其他几个个结果都匹配到case,返回了相应的ResultMap,另外注意到autoMapping,它的作用是当查出来的列并没有在resultMap中定义时是否自动映射到Java对象中,当然前提是Java类有此属性

你可能感兴趣的:(mybatis)