MyBatis discriminator標(biāo)簽原理實(shí)例解析
一、什么業(yè)務(wù)情況會(huì)使用discriminator標(biāo)簽?
假設(shè)我們有一張user表:
使用查詢語(yǔ)句select * from user
有如下需求
當(dāng)用戶年齡為18歲時(shí)查詢結(jié)果顯示生日信息。
當(dāng)用戶年齡不為18歲時(shí)查詢結(jié)果不顯示生日信息。(顯示為空或NULL即可)
如果我們使用mybatis,第一個(gè)想到的解決辦法可能是在Java程序里把用戶信息查出來(lái),然后再根據(jù)年齡做if
判斷。但是這樣做有點(diǎn)繁瑣。還好mybatis提供了一個(gè)標(biāo)簽(<discriminator/>
)來(lái)解決如上的業(yè)務(wù)需求。discriminator
也就是偵察器、也叫鑒別器
二、discriminator使用
還是結(jié)合第一節(jié)的業(yè)務(wù)問(wèn)題來(lái)使用discriminator
標(biāo)簽
映射文件配置
mapper.xml配置內(nèi)容如下
<resultMap id="userMapForTestDiscriminator" type="user" autoMapping="false"> <!--關(guān)閉自動(dòng)映射,那么沒(méi)有指定的列名不會(huì)出現(xiàn)在結(jié)果集中--> <id property="id" column="id"/> <result property="username" column="username"/> <result property="age" column="age"/> <discriminator javaType="int" column="age"> <case value="18" resultType="user"> <result property="birthday" column="birthday"/> </case> </discriminator> </resultMap> <select id="selectDiscriminator" resultMap="userMapForTestDiscriminator"> select * from user limit 2 </select>
select標(biāo)簽的id為selectDiscriminator,并且返回結(jié)果集使用resultMap來(lái)接收。重點(diǎn)就在resultMap里配置了discriminator。先來(lái)解釋一下discriminator的作用:
discriminator有個(gè)子標(biāo)簽是case,并且指定偵察器鑒別的屬性為age。(它的用法很類似于Java中的switch)
當(dāng)查詢的結(jié)果集中age列數(shù)據(jù)等于case指定的value值時(shí)(在這個(gè)例子里就是當(dāng)結(jié)果集中age列為18時(shí))。則把case標(biāo)簽中的result標(biāo)簽加入到外部的resultMap標(biāo)簽中。反之——如果結(jié)果集中的age列值不為18,則不做任何操作。
換個(gè)更直觀的說(shuō)法來(lái)看。
- 當(dāng)結(jié)果集中的age列等于18時(shí)(本例中)實(shí)際上返回的resultMap標(biāo)簽相當(dāng)于
<resultMap id="userMapForTestDiscriminator" type="user" autoMapping="false"> <!--關(guān)閉自動(dòng)映射,那么沒(méi)有指定的列名不會(huì)出現(xiàn)在結(jié)果集中--> <id property="id" column="id"/> <result property="username" column="username"/> <result property="age" column="age"/> <result property="birthday" column="birthday"/> </resultMap>
- 當(dāng)結(jié)果集中的age列不等于18時(shí)(本例中)實(shí)際上返回的resultMap標(biāo)簽相當(dāng)于
<resultMap id="userMapForTestDiscriminator" type="user" autoMapping="false"> <!--關(guān)閉自動(dòng)映射,那么沒(méi)有指定的列名不會(huì)出現(xiàn)在結(jié)果集中--> <id property="id" column="id"/> <result property="username" column="username"/> <result property="age" column="age"/> </resultMap>
怎么樣?是不是和Java中的Switch用法一模一樣,當(dāng)滿足條件時(shí)就執(zhí)行case語(yǔ)句中的代碼。
Mapper接口配置
public interface UserMapper { List<User> selectDiscriminator(); }
測(cè)試
@Test public void test1() throws Exception { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.selectDiscriminator(); for (Object user : users) { System.out.println(user); } }
輸出結(jié)果:User(id=1, username=0.620544543088072, age=18, password=222, birthday=2002-10-27, author=null)
User(id=3, username=0.620544543088072, age=22, password=null, birthday=null, author=null)
從輸出結(jié)果中已經(jīng)可以看到,當(dāng)age不等于18的時(shí)候,birthdary字段沒(méi)有值。那么mybatis是如何實(shí)現(xiàn)這個(gè)功能的呢?下面來(lái)從源碼角度分析下。
總結(jié)
discriminator相當(dāng)于Java中的Switch,作用是可以動(dòng)態(tài)的控制resultMap
標(biāo)簽中的result
標(biāo)簽。
三、discriminator原理
mybatis初始化的時(shí)候會(huì)加載映射配置文件。
- 把所有的
XxxMapper.xml
文件解析為MappedStatement
對(duì)象保存在Configuration
對(duì)象中。以便于后續(xù)的調(diào)用。 - 而每個(gè)
XxxMapper.xml
中的resultMap
標(biāo)簽都會(huì)被解析為ResultMap
對(duì)象存儲(chǔ)在MappedStatement
對(duì)象當(dāng)中 - ResultMap標(biāo)簽中的每個(gè)
discriminator
標(biāo)簽都會(huì)被解析為Discriminator
對(duì)象存儲(chǔ)在ResultMap
對(duì)象當(dāng)中。
至此,Discriminator
所在的層級(jí)就是:
下面就從源碼的兩個(gè)方面揭開(kāi)discriminator的面紗。
初始化時(shí)——加載配置文件并把discriminator標(biāo)簽解析為Discriminator
對(duì)象存儲(chǔ)到內(nèi)存中。
執(zhí)行SQL時(shí)——如果檢測(cè)到有與該SQL匹配的Discriminator
對(duì)象,則調(diào)用Discriminator
對(duì)象的邏輯
Discriminator對(duì)象結(jié)構(gòu)
public class Discriminator { private ResultMapping resultMapping; private Map<String, String> discriminatorMap; }
Discriminator對(duì)象有兩個(gè)字段
- resultMapping記錄的是
<discriminator javaType="int" column="age">
中的age字段的相關(guān)信息(列名、jdbc類型、Java類型、是否嵌套等等。總之記錄的是鑒別器鑒別的那個(gè)列的列信息。而這個(gè)信息被封裝成了ResultMapping對(duì)象,比較簡(jiǎn)單感興趣的可以看下ResultMapping對(duì)象源碼) - discriminatorMap記錄的是
<case value="18" resultType="user">
case標(biāo)簽的信息。它是一個(gè)map結(jié)構(gòu),key是case中value屬性的值,value是resultmap的的唯一標(biāo)識(shí)。(mybatis會(huì)通過(guò)resultMap的唯一標(biāo)識(shí)去configuration對(duì)象中尋找對(duì)應(yīng)的resultMap)
初始化
看到這里,默認(rèn)讀者有看過(guò)一定的mybatis源碼。mybatis在啟動(dòng)時(shí)通過(guò)XMLMapperBuilder
來(lái)加載映射文件(xml文件)。其中包含了對(duì)ResultMap標(biāo)簽的解析過(guò)程。具體邏輯在XMLMapperBuilder#resultMapElement
方法中。代碼如下(只列出了有關(guān)Discriminator的邏輯)
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) { // 1. 解析配置文件的標(biāo)簽創(chuàng)建為Discriminator對(duì)象 Discriminator discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); // 2. 構(gòu)建ResultMapResolver對(duì)象,再調(diào)用resolve方法創(chuàng)建ResultMap對(duì)象,Discriminator就是在resolve方法中被ResultMap對(duì)象中的 // 說(shuō)白了是構(gòu)建ResultMap的輔助類。 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); return resultMapResolver.resolve(); }
調(diào)用processDiscriminatorElement
方法為Discriminator對(duì)象設(shè)置詳細(xì)的屬性。
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) { // 通過(guò) column javaType jdbcType typeHandler javaTypeClass typeHandlerClass // 來(lái)構(gòu)造Discriminator對(duì)象中的resultMapping字段 String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String typeHandler = context.getStringAttribute("typeHandler"); Class<?> javaTypeClass = resolveClass(javaType); Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); Map<String, String> discriminatorMap = new HashMap<>(); // 遍歷discriminator標(biāo)簽,把case節(jié)點(diǎn)的信息封裝為Discriminator對(duì)象中的discriminatorMap字段 for (XNode caseChild : context.getChildren()) { String value = caseChild.getStringAttribute("value"); String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType)); discriminatorMap.put(value, resultMap); } // 調(diào)用輔助類整整的構(gòu)建Discriminator對(duì)象 return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap); }
至此,Discriminator對(duì)象就被完整的創(chuàng)建出來(lái)了。它會(huì)被添加在ResultMap對(duì)象中緩存。后續(xù)程序執(zhí)行過(guò)程中,就能通過(guò)Configuration對(duì)象獲取MappedStatement對(duì)象,再通過(guò)MappedStatement對(duì)象獲取ResultMap對(duì)象,再通過(guò)ResultMap對(duì)象就可以獲取Discriminator對(duì)象啦!
需要注意的是,每個(gè)case標(biāo)簽都會(huì)被解析為一個(gè)結(jié)果集間接存入到configuration對(duì)象中。
執(zhí)行SQL時(shí)
在程序通過(guò)mybatis執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),會(huì)通過(guò)ResultSetHandler
對(duì)象來(lái)處理數(shù)據(jù)庫(kù)返回的結(jié)果集。ResultSetHandler
是mybatis的幾個(gè)核心對(duì)象之一。它在DefaultResultSetHandler#resolveDiscriminatedResultMap
方法中進(jìn)行對(duì)Discriminator的邏輯處理。方法代碼如下
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException { // 1 通過(guò)resultMap獲取鑒別器對(duì)象Discriminator Discriminator discriminator = resultMap.getDiscriminator(); while (discriminator != null) { // 2. 從鑒別器中獲取結(jié)果集中對(duì)應(yīng)的需要被鑒別的值。拿文章開(kāi)頭的業(yè)務(wù)舉例,在此就是獲取結(jié)果集中的age列的值,第一行age的值是18。也就是value的值為18。第二行age的值為22 final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix); final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value)); // 3. 判斷configuration對(duì)象中是否有指定的resultMap對(duì)象,后面會(huì)根據(jù)這個(gè)resultMap進(jìn)行映射結(jié)果集 if (configuration.hasResultMap(discriminatedMapId)) { resultMap = configuration.getResultMap(discriminatedMapId); } } return resultMap; }
代碼邏輯的大致步驟如下
- 通過(guò)resultMap獲取鑒別器對(duì)象Discriminator
- 從鑒別器中獲取結(jié)果集中對(duì)應(yīng)的需要被鑒別的值。拿文章開(kāi)頭的業(yè)務(wù)舉例,在此就是獲取結(jié)果集中的age列的值,第一行age的值是18。也就是value的值為18。第二行age的值為22
- 獲取case中的值,在本例中case會(huì)拿age的值和18做比較,如果比較不相等則不做處理,否則會(huì)把對(duì)應(yīng)的resultMap的值返回,交給
ResultSetHandler
去處理映射關(guān)系。
以上就是MyBatis discriminator標(biāo)簽原理實(shí)例解析的詳細(xì)內(nèi)容,更多關(guān)于MyBatis discriminator標(biāo)簽的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mybatis?plus實(shí)現(xiàn)分頁(yè)邏輯刪除
這篇文章主要為大家介紹了mybatis?plus實(shí)現(xiàn)分頁(yè)邏輯刪除的方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05SpringBoot項(xiàng)目?jī)?yōu)雅的全局異常處理方式(全網(wǎng)最新)
這篇文章主要介紹了SpringBoot項(xiàng)目?jī)?yōu)雅的全局異常處理方式(全網(wǎng)最新),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04手把手教你實(shí)現(xiàn)Java第三方應(yīng)用登錄
本文主要介紹了手把手教你實(shí)現(xiàn)Java第三方應(yīng)用登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08springboot 多數(shù)據(jù)源的實(shí)現(xiàn)(最簡(jiǎn)單的整合方式)
這篇文章主要介紹了springboot 多數(shù)據(jù)源的實(shí)現(xiàn)(最簡(jiǎn)單的整合方式),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11Jedis出現(xiàn)connection timeout問(wèn)題解決方法(JedisPool連接池使用實(shí)例)
這篇文章主要介紹了Jedis出現(xiàn)connection timeout問(wèn)題解決方法,使用Jedis的JedisPool連接池解決了這個(gè)問(wèn)題,需要的朋友可以參考下2014-05-05