深入淺出MyBatis:「映射器」全了解

本篇文章是「深入淺出MyBatis:技術(shù)原理與實踐」書籍的總結(jié)筆記鲫竞。

上一篇總結(jié)了MyBatis的配置,詳細說明了各個配置項寄疏,其中提到了映射器陕截,它是MyBatis最強大的工具艘策,也是使用最多的工具渊季。

通過映射器,可以很容易的進行數(shù)據(jù)的增刪改查操作荷并,我們抽象下進行這些操作的關(guān)鍵點:傳遞查詢參數(shù)源织、組裝各種場景下的查詢條件谈息、關(guān)聯(lián)查詢凛剥、將查詢結(jié)果映射為Java Bean對象或集合等犁珠。另外犁享,可以通過延遲加載、緩存提高數(shù)據(jù)查詢的性能桨吊。

本篇就按照這個思路進行總結(jié)屏积,首先列舉下映射器的主要元素炊林,每個元素提供的配置項和作用渣聚,然后重點介紹參數(shù)僧叉、結(jié)果映射瓶堕、延遲加載隘道、緩存、動態(tài)SQL等功能。

映射器的主要元素

映射器是由Java接口和XML文件(或注解)共同組成的谭梗,Java接口主要定義調(diào)用者接口忘晤,XML文件是配置映射器的核心文件,包括以下元素:

  • select 查詢語句激捏,可以自定義參數(shù)设塔,返回結(jié)果集;
  • insert 插入語句远舅,返回一個整數(shù)闰蛔,表示插入的條數(shù)图柏;
  • update 更新語句序六,返回一個整數(shù),表示更新的條數(shù)爆办;
  • delete 刪除語句难咕,返回一個整數(shù),表示刪除的條數(shù)距辆;
  • sql 允許定義一部分SQL,然后再各個地方引用暮刃;
  • resultMap 用來描述從數(shù)據(jù)庫結(jié)果集中來加載對象跨算,還可以配置關(guān)聯(lián)關(guān)系;
  • cache 給定命名空間的緩存配置椭懊;

增诸蚕、刪、改氧猬、查操作

查找

執(zhí)行select語句前背犯,需要定義參數(shù),執(zhí)行后盅抚,也提供了強大的映射規(guī)則或自動映射漠魏,將返回的結(jié)果集綁定到j(luò)ava bean中。

select元素有很多配置項,下面簡單說明下:

  • paramterType:傳入的參數(shù)類型,可以是基本類型阱驾、map衙伶、自定義的java bean;
  • resultType:返回的結(jié)果類型徐伐,可以是基本類型、自定義的java bean;
  • resultMap:它是最復(fù)雜的元素瞧毙,可以配置映射規(guī)則、級聯(lián)、typeHandler等宙彪,與ResultType不能同時存在矩动;
  • flushCache:在調(diào)用SQL后,是否要求清空之前查詢的本地緩存和二級緩存您访,主要用于更新緩存铅忿,默認為false;
  • useCache:啟動二級緩存的開關(guān)灵汪,默認只會啟動一級緩存檀训;
  • timeout:設(shè)置超時參數(shù),等超時的時候?qū)伋霎惓O硌裕瑔挝粸槊耄?/li>
  • fetchSize:獲取記錄的總條數(shù)設(shè)定峻凫;

比如根據(jù)米聊號獲取用戶信息:

<select id="findByMiliao" parameterType="string" resultType="User">
        select
        u.*
        from mxt_user u
        where u.miliao=#{miliao}
</select>

上一篇介紹配置時,有個設(shè)置項autoMappingBehavior览露,默認為自動映射沒有定義嵌套結(jié)果集映射的結(jié)果集荧琼;還有設(shè)置項mapUnderscoreToCamelCase,設(shè)置為true時差牛,會自動將以「下劃線」命名的數(shù)據(jù)庫字段名命锄,自動映射為以「駝峰式」命名的POJO。

傳遞多個參數(shù)時偏化,有3種方式:

  • 使用Map參數(shù)脐恩;
  • 使用注解方式傳遞;
  • 使用java bean侦讨;

使用注解方式如下:

public List<Role> findRoleByNameAndNote(@Param("roleName") String rolename,
@Param("note") String note);

使用Map傳遞參數(shù)驶冒,會導(dǎo)致業(yè)務(wù)可讀性喪失,導(dǎo)致以后擴展和維護不方便韵卤,不建議骗污;如果參數(shù)個數(shù)<=5,建議使用注解的方式沈条,因為過多參數(shù)將給調(diào)用者帶來困難需忿;如果參數(shù)個數(shù)>5,建議使用JavaBean方式拍鲤;

使用resultMap映射結(jié)果集贴谎,后面會單獨介紹。

insert

屬性和select大部分都相同季稳, 說下3個不同的屬性:

  • keyProperty:指定哪個列是主鍵擅这,如果是聯(lián)合主鍵可以用逗號隔開;
  • keyColumn:指定第幾列是主鍵景鼠,不能和keyProperty共用仲翎;
  • useGeneratedKeys:是否使用自動增長痹扇,默認為false;

當useGeneratedKeys設(shè)為true時溯香,在插入的時候鲫构,會回填Java Bean的id值,通過返回的對象可獲取主鍵值玫坛。

如果想根據(jù)一些特殊關(guān)系設(shè)置主鍵的值结笨,可以在insert標簽內(nèi)使用selectKey標簽,比如:如果t_role沒有記錄湿镀,則需要設(shè)置為1炕吸,否則取最大id加2:

<insert id="insertRole" useGeneratedKeys="true" keyProperty="id" >
    <selectKey keyProperty="id" resultType="int" order="before">
        select if(max(id) is null,1,max(id)+2) as newId from t_role
    </selectKey> 
</insert>
update和delete

比較簡單,就不過多介紹了勉痴。

參數(shù)

上面已經(jīng)介紹了參數(shù)傳遞赫模,另外可以指定參數(shù)的類型去讓對應(yīng)的typeHandler處理它們。

#{age , javaType=int , jdbcType=NUMERIC }

還可以對一些數(shù)值型的參數(shù)設(shè)置其保存的精度

#{price, javaType=double , jdbcType=NUMERIC , numericScale=2 }

一般都是傳遞字符串蒸矛,設(shè)置的參數(shù)#{name}大部分情況下瀑罗,會創(chuàng)建預(yù)編譯語句,但有時候傳遞的是SQL語句本身雏掠,不是需要的參數(shù)斩祭,可以通過$符號表示,比如傳遞參數(shù)columns為"col1,col2,col3"乡话,可以寫成下面語句:

select ${columns} from t_tablename

但要注意sql的安全性停忿,防止sql注入。

sql元素

定義:

<sql id="role_columns">
    id,role_name,note
</sql>

使用:

<include refid="role_columns">
    <property name="prefix" value="r" />
</include>

結(jié)果映射

元素介紹

resultMap是MyBatis里面最復(fù)雜的元素蚊伞,它的作用是定義映射規(guī)則、級聯(lián)的更新吮铭、定制類型轉(zhuǎn)換器等时迫。

由以下元素構(gòu)成:

<resultMap>
    <constructor> <!-- 配置構(gòu)造方法 -->
        <idArg/>
        <arg/>
    </constructor>
    <id/> <!--指明哪一列是主鍵-->
    <result/> <!--配置映射規(guī)則-->
    <association/> <!--一對一-->
    <collection/> <!--一對多-->
    <discriminator> <!--鑒別器級聯(lián)-->
        <case/>
    </discriminator>
</resultMap>

有的實體不存在沒有參數(shù)的構(gòu)造方法,需要使用constructor配置有參數(shù)的構(gòu)造方法:

<resultMap id="role" type="com.xiaomi.kfs.mcc.core.domain.Role">
    <constructor>
        <idArg column="id" javaType="int"/>
        <arg column="role_name" javaType="string"/>
    </constructor>
</resultMap>

id指明主鍵列谓晌,result配置數(shù)據(jù)庫字段和POJO屬性的映射規(guī)則:

<resultMap id="role" type="com.xiaomi.kfs.mcc.core.domain.Role">
    <id property="id" column="id" />
    <result property="roleName" column="role_name" />
    <result property="note" column="note" />
</resultMap>

association掠拳、collection用于配置級聯(lián)關(guān)系的,分別為一對一和一對多纸肉,實際中溺欧,多對多關(guān)系的應(yīng)用不多,因為比較復(fù)雜柏肪,會用一對多的關(guān)系把它分解為雙向關(guān)系姐刁。

discriminator用于這樣一種場景:比如我們?nèi)ンw檢,男和女的體檢項目不同烦味,如果讓男生去檢查婦科項目聂使,是不合理的, 通過discriminator可以根據(jù)性別,返回不同的對象柏靶。

級聯(lián)關(guān)系的配置比較多弃理,就不在此演示了,可查看文檔進行了解屎蜓。

延遲加載

級聯(lián)的優(yōu)勢是能夠方便地獲取數(shù)據(jù)痘昌,但有時不需要獲取所有數(shù)據(jù),這樣會多執(zhí)行幾條SQL炬转,性能下降辆苔,為了解決這個問題,需要使用延遲加載返吻,只要使用相關(guān)級聯(lián)數(shù)據(jù)時姑子,才會發(fā)送SQL去取回數(shù)據(jù)。

在MyBatis的配置中有2個全局的參數(shù) lazyLoadingEnabled 和 aggressiveLazyLoading 测僵,第一個的含義是是否開啟延遲加載功能街佑,第二個的含義是對任意延遲加載屬性的調(diào)用,會使延遲加載的對象完整加載捍靠,否則只會按需加載沐旨。

再理解下aggressiveLazyLoading屬性,比如學(xué)生對象的關(guān)聯(lián)對象如下:

image

當訪問學(xué)生信息的時候榨婆,會根據(jù)鑒別器把健康的情況也會查找出來磁携;當訪問課程成績的時候,同時也會把學(xué)生證信息查找出來良风,因為在默認情況下谊迄,MyBatis是按層級延遲加載的。 但這不是我們需要的烟央,并不希望在訪問成績的時候统诺,去加載學(xué)生證的信息,可以設(shè)置aggressiveLazyLoading為false疑俭,按需進行延遲加載數(shù)據(jù)粮呢。

上面的2個屬性都是全局設(shè)置,也可以在association和collection元素上加上屬性值fetchType钞艇,它有兩個取值eager和lazy啄寡。

緩存

在沒有顯示配置緩存時,只開啟一級緩存哩照,一級緩存是相對于同一個SqlSession而言的挺物,在參數(shù)和SQL完全一樣的情況下,使用同一個SqlSession對象調(diào)用同一個Mapper的方法葡秒,只會執(zhí)行一次SQL姻乓。

如果是不同的SqlSession對象嵌溢,因為不同SqlSession是相互隔離的,即使用相同的Mapper蹋岩、參數(shù)和方法赖草,還是會再次發(fā)送SQL到數(shù)據(jù)庫去執(zhí)行。

二級緩存是SqlSessionFactory層面上的剪个,需要進行顯示配置秧骑,實現(xiàn)二級緩存的時候,要求POJO必須是可序列化的扣囊,只需要簡單配置即可:

<cache />

這樣很多設(shè)置是默認的乎折,有如下屬性可以配置:

  • eviction:代表緩存回收策略,可選值有LRU最少使用侵歇、FIFO先進先出骂澄、SOFT軟引用,WEAK弱引用惕虑;
  • flushInterval:刷新間隔時間坟冲,單位為毫秒,如果不配置溃蔫,當SQL被執(zhí)行時才會刷新緩存健提;
  • size:引用數(shù)目,代表緩存最多可以存儲多少對象伟叛,不宜設(shè)置過大私痹,設(shè)置過大會導(dǎo)致內(nèi)存溢出;
  • readOnly:只讀统刮,意味著緩存數(shù)據(jù)只能讀取不能修改紊遵;

在大型服務(wù)器上,可能會使用專用的緩存服務(wù)器侥蒙,比如Redis緩存癞蚕,可以通過實現(xiàn)org.apache.ibatis.cache.Cache接口很方便的實現(xiàn):

public interface Cache {
    String getId(); //緩存編號
    void putObject(Object var1, Object var2); //保存對象
    Object getObject(Object var1); //獲取對象
    Object removeObject(Object var1); //移除對象
    void clear(); //清空緩存
    int getSize(); //獲取緩存對象大小
    ReadWriteLock getReadWriteLock(); //獲取緩存的讀寫鎖
}

動態(tài)SQL

很多時候,需要根據(jù)不同的場景組裝查詢條件辉哥,MyBatis提供對SQL語句動態(tài)的組裝能力。

主要提供以下幾種元素:

  • if:判斷語句攒射,但條件分支判斷醋旦;
  • choose (when、otherwise):多條件分支判斷会放;
  • trim (where饲齐、set):處理一些SQL拼裝問題;
  • foreach:循環(huán)語句咧最,在in語句等列舉條件常用捂人;
  • bind:通過OGNL表達式去自定義一個上下文變量御雕,可以方便使用;

trim可以處理 and 和 逗號 拼接的問題滥搭,舉例如下:

<select id="findRoles" parameterType="string" >
  select id,role_name,note from t_role
  <trim prefix="where" prefixOverrides="and">
      <if test="roleName!=null and roleName!=''">
        and role_name like concat('%',#{roleName},'%')
      </if>
  </trim>
</select>

另外酸纲,可以使用set元素設(shè)置更新的字段列表:

<update id="updateRole" parameterType="role">
    update t_role
    <set>
        <if test="roleName!=null and roleName!=''">
            role_name=#{roleName},
        </if>
        <if test="note!=null and note!=''">
            note=#{note}
        </if>
    </set>
    where id=#{id}
</update>

下一篇會介紹MyBatis的解析和運行原理。

歡迎掃描下方二維碼瑟匆,關(guān)注我的個人微信公眾號 ~

情情說
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闽坡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子愁溜,更是在濱河造成了極大的恐慌疾嗅,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冕象,死亡現(xiàn)場離奇詭異代承,居然都是意外死亡,警方通過查閱死者的電腦和手機渐扮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門论悴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人席爽,你說我怎么就攤上這事意荤。” “怎么了只锻?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵玖像,是天一觀的道長。 經(jīng)常有香客問我齐饮,道長捐寥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任祖驱,我火速辦了婚禮握恳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捺僻。我一直安慰自己乡洼,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布匕坯。 她就那樣靜靜地躺著束昵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葛峻。 梳的紋絲不亂的頭發(fā)上锹雏,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音术奖,去河邊找鬼礁遵。 笑死轻绞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的佣耐。 我是一名探鬼主播政勃,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼晰赞!你這毒婦竟也來了稼病?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掖鱼,失蹤者是張志新(化名)和其女友劉穎然走,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戏挡,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡芍瑞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了褐墅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拆檬。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妥凳,靈堂內(nèi)的尸體忽然破棺而出竟贯,到底是詐尸還是另有隱情,我是刑警寧澤逝钥,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布屑那,位于F島的核電站,受9級特大地震影響艘款,放射性物質(zhì)發(fā)生泄漏持际。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一哗咆、第九天 我趴在偏房一處隱蔽的房頂上張望蜘欲。 院中可真熱鬧,春花似錦晌柬、人聲如沸姥份。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殿衰。三九已至,卻和暖如春盛泡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背娱颊。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工傲诵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凯砍,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓拴竹,卻偏偏與公主長得像悟衩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子栓拜,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容