MyBatis discriminator標簽原理實例解析
一、什么業(yè)務情況會使用discriminator標簽?
假設我們有一張user表:

使用查詢語句select * from user有如下需求
當用戶年齡為18歲時查詢結果顯示生日信息。
當用戶年齡不為18歲時查詢結果不顯示生日信息。(顯示為空或NULL即可)
如果我們使用mybatis,第一個想到的解決辦法可能是在Java程序里把用戶信息查出來,然后再根據(jù)年齡做if判斷。但是這樣做有點繁瑣。還好mybatis提供了一個標簽(<discriminator/>)來解決如上的業(yè)務需求。discriminator也就是偵察器、也叫鑒別器
二、discriminator使用
還是結合第一節(jié)的業(yè)務問題來使用discriminator標簽
映射文件配置
mapper.xml配置內容如下
<resultMap id="userMapForTestDiscriminator" type="user" autoMapping="false">
<!--關閉自動映射,那么沒有指定的列名不會出現(xiàn)在結果集中-->
<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標簽的id為selectDiscriminator,并且返回結果集使用resultMap來接收。重點就在resultMap里配置了discriminator。先來解釋一下discriminator的作用:
discriminator有個子標簽是case,并且指定偵察器鑒別的屬性為age。(它的用法很類似于Java中的switch)
當查詢的結果集中age列數(shù)據(jù)等于case指定的value值時(在這個例子里就是當結果集中age列為18時)。則把case標簽中的result標簽加入到外部的resultMap標簽中。反之——如果結果集中的age列值不為18,則不做任何操作。
換個更直觀的說法來看。
- 當結果集中的age列等于18時(本例中)實際上返回的resultMap標簽相當于
<resultMap id="userMapForTestDiscriminator" type="user" autoMapping="false">
<!--關閉自動映射,那么沒有指定的列名不會出現(xiàn)在結果集中-->
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="age" column="age"/>
<result property="birthday" column="birthday"/>
</resultMap>
- 當結果集中的age列不等于18時(本例中)實際上返回的resultMap標簽相當于
<resultMap id="userMapForTestDiscriminator" type="user" autoMapping="false">
<!--關閉自動映射,那么沒有指定的列名不會出現(xiàn)在結果集中-->
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="age" column="age"/>
</resultMap>
怎么樣?是不是和Java中的Switch用法一模一樣,當滿足條件時就執(zhí)行case語句中的代碼。
Mapper接口配置
public interface UserMapper {
List<User> selectDiscriminator();
}
測試
@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);
}
}
輸出結果: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)

從輸出結果中已經(jīng)可以看到,當age不等于18的時候,birthdary字段沒有值。那么mybatis是如何實現(xiàn)這個功能的呢?下面來從源碼角度分析下。
總結
discriminator相當于Java中的Switch,作用是可以動態(tài)的控制resultMap標簽中的result標簽。
三、discriminator原理
mybatis初始化的時候會加載映射配置文件。
- 把所有的
XxxMapper.xml文件解析為MappedStatement對象保存在Configuration對象中。以便于后續(xù)的調用。 - 而每個
XxxMapper.xml中的resultMap標簽都會被解析為ResultMap對象存儲在MappedStatement對象當中 - ResultMap標簽中的每個
discriminator標簽都會被解析為Discriminator對象存儲在ResultMap對象當中。
至此,Discriminator所在的層級就是:

下面就從源碼的兩個方面揭開discriminator的面紗。
初始化時——加載配置文件并把discriminator標簽解析為Discriminator對象存儲到內存中。
執(zhí)行SQL時——如果檢測到有與該SQL匹配的Discriminator對象,則調用Discriminator對象的邏輯
Discriminator對象結構
public class Discriminator {
private ResultMapping resultMapping;
private Map<String, String> discriminatorMap;
}
Discriminator對象有兩個字段
- resultMapping記錄的是
<discriminator javaType="int" column="age">中的age字段的相關信息(列名、jdbc類型、Java類型、是否嵌套等等??傊涗浀氖氰b別器鑒別的那個列的列信息。而這個信息被封裝成了ResultMapping對象,比較簡單感興趣的可以看下ResultMapping對象源碼) - discriminatorMap記錄的是
<case value="18" resultType="user">case標簽的信息。它是一個map結構,key是case中value屬性的值,value是resultmap的的唯一標識。(mybatis會通過resultMap的唯一標識去configuration對象中尋找對應的resultMap)
初始化
看到這里,默認讀者有看過一定的mybatis源碼。mybatis在啟動時通過XMLMapperBuilder來加載映射文件(xml文件)。其中包含了對ResultMap標簽的解析過程。具體邏輯在XMLMapperBuilder#resultMapElement方法中。代碼如下(只列出了有關Discriminator的邏輯)
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
// 1. 解析配置文件的標簽創(chuàng)建為Discriminator對象
Discriminator discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
// 2. 構建ResultMapResolver對象,再調用resolve方法創(chuàng)建ResultMap對象,Discriminator就是在resolve方法中被ResultMap對象中的
// 說白了是構建ResultMap的輔助類。
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
return resultMapResolver.resolve();
}
調用processDiscriminatorElement方法為Discriminator對象設置詳細的屬性。
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
// 通過 column javaType jdbcType typeHandler javaTypeClass typeHandlerClass
// 來構造Discriminator對象中的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標簽,把case節(jié)點的信息封裝為Discriminator對象中的discriminatorMap字段
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
discriminatorMap.put(value, resultMap);
}
// 調用輔助類整整的構建Discriminator對象
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
至此,Discriminator對象就被完整的創(chuàng)建出來了。它會被添加在ResultMap對象中緩存。后續(xù)程序執(zhí)行過程中,就能通過Configuration對象獲取MappedStatement對象,再通過MappedStatement對象獲取ResultMap對象,再通過ResultMap對象就可以獲取Discriminator對象啦!
需要注意的是,每個case標簽都會被解析為一個結果集間接存入到configuration對象中。
執(zhí)行SQL時
在程序通過mybatis執(zhí)行數(shù)據(jù)庫操作時,會通過ResultSetHandler對象來處理數(shù)據(jù)庫返回的結果集。ResultSetHandler是mybatis的幾個核心對象之一。它在DefaultResultSetHandler#resolveDiscriminatedResultMap方法中進行對Discriminator的邏輯處理。方法代碼如下
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
// 1 通過resultMap獲取鑒別器對象Discriminator
Discriminator discriminator = resultMap.getDiscriminator();
while (discriminator != null) {
// 2. 從鑒別器中獲取結果集中對應的需要被鑒別的值。拿文章開頭的業(yè)務舉例,在此就是獲取結果集中的age列的值,第一行age的值是18。也就是value的值為18。第二行age的值為22
final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
// 3. 判斷configuration對象中是否有指定的resultMap對象,后面會根據(jù)這個resultMap進行映射結果集
if (configuration.hasResultMap(discriminatedMapId)) {
resultMap = configuration.getResultMap(discriminatedMapId);
}
}
return resultMap;
}
代碼邏輯的大致步驟如下
- 通過resultMap獲取鑒別器對象Discriminator
- 從鑒別器中獲取結果集中對應的需要被鑒別的值。拿文章開頭的業(yè)務舉例,在此就是獲取結果集中的age列的值,第一行age的值是18。也就是value的值為18。第二行age的值為22
- 獲取case中的值,在本例中case會拿age的值和18做比較,如果比較不相等則不做處理,否則會把對應的resultMap的值返回,交給
ResultSetHandler去處理映射關系。
以上就是MyBatis discriminator標簽原理實例解析的詳細內容,更多關于MyBatis discriminator標簽的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot項目優(yōu)雅的全局異常處理方式(全網(wǎng)最新)
這篇文章主要介紹了SpringBoot項目優(yōu)雅的全局異常處理方式(全網(wǎng)最新),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
springboot 多數(shù)據(jù)源的實現(xiàn)(最簡單的整合方式)
這篇文章主要介紹了springboot 多數(shù)據(jù)源的實現(xiàn)(最簡單的整合方式),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11
Jedis出現(xiàn)connection timeout問題解決方法(JedisPool連接池使用實例)
這篇文章主要介紹了Jedis出現(xiàn)connection timeout問題解決方法,使用Jedis的JedisPool連接池解決了這個問題,需要的朋友可以參考下2014-05-05

