字節(jié)跳動(dòng)飛書內(nèi)推!
北京机隙、杭州蜘拉、武漢、廣州有鹿、深圳旭旭、上海,六大城市等你來投葱跋。
感興趣的朋友可以私我咨詢&內(nèi)推持寄,也可以通過鏈接直接投遞源梭!
海量HC,極速響應(yīng)稍味,快來和我成為同事吧废麻。
今日頭條、抖音模庐、Tik Tok也可以內(nèi)推~
點(diǎn)擊進(jìn)入我的博客
MyBatis詳解1.概述
MyBatis詳解2.MyBatis使用入門
MyBatis詳解3.MyBatis配置詳解
MyBatis詳解4.映射器Mapper
MyBatis詳解5.動(dòng)態(tài)SQL
MyBatis詳解6.MyBatis技術(shù)內(nèi)幕
MyBatis詳解7.插件
MyBatis詳解8.集成Spring
1 概述
MyBatis是針對(duì)映射器構(gòu)造的SQL構(gòu)建的輕量級(jí)框架烛愧,并且通過配置生成對(duì)應(yīng)的JavaBean返回給調(diào)用者,而這些配置主要便是映射器赖欣。
元素名稱 | 描述 | 備注 |
---|---|---|
select | 查詢語句屑彻,最常用、最復(fù)雜的元素之一 | 可以自定義參數(shù),返回結(jié)果集等 |
insert | 插入語句 | 執(zhí)行后返回一個(gè)整數(shù)顶吮,代表插入的條數(shù) |
update | 更新語句 | 執(zhí)行后返回一個(gè)整數(shù)社牲,代表更新的條數(shù) |
delete | 刪除語句 | 執(zhí)行后返回一個(gè)整數(shù),代表刪除的條數(shù) |
定義參數(shù)映射關(guān)系 | 即將被刪除的元素悴了,不建議大家使用 | |
sql | 允許定義一部分的SQL搏恤,然后在各個(gè)地方引用 | |
resultMap | 用來描述從數(shù)據(jù)庫結(jié)果集中來加載對(duì)象,它是它將提供映射規(guī)則最復(fù)雜湃交、最強(qiáng)大的元素 | 它提供映射規(guī)則 |
cache | 給定命名空間的緩存配置 | |
cache-ref | 其他命名空間緩存配置的引用 |
2 引入映射器
通過文件類路徑引入
<mappers>
<mapper resource="mappers/TbNameMapper.xml"/>
</mappers>
通過包名引入映射器
public interface TbNameMapper {
@Select("SELECT * FROM tb_name WHERE id = #{id}")
UserDomain select(Long id);
}
<mappers>
<package name="com.ankeetc.spring.mapper"/>
</mappers>
用類注冊(cè)引入映射器
<mappers>
<mapper class="com.ankeetc.spring.mapper.TbNameMapper"/>
</mappers>
3 select元素
3.1 select元素的配置元素
元素 | 說明 | 備注 |
---|---|---|
id | 它和 Mapper的命名空間組合起來是唯一的熟空,提供給MyBatis調(diào)用 | 如過命名空間和id組合起來不唯一將拋出異常 |
parameterType | 可以給出類的全命名,也可以給出類的別名搞莺,但使用別名必須是MyBatis內(nèi)部定義或者自定義的 | 可以選擇JavaBean息罗、Map等復(fù)雜的參數(shù)類型傳遞給SQL |
parameterMap | 即將廢棄的元素,我們不再討論它 | |
resultType | 定義類的全路徑才沧,在允許自動(dòng)匹配的情況下迈喉,結(jié)果集將通過JavaBean的規(guī)范映射;也可以定義為int温圆、 double等參數(shù)挨摸;也可以使用別名,但是要符合別名規(guī)范岁歉;不能和 resultMap同時(shí)使用 | 常用參數(shù)之一 |
resultMap | 它是映射集的引用得运,將執(zhí)行強(qiáng)大的映射功能 | 可以配置映射規(guī)則、級(jí)聯(lián)锅移、typeHandler等 |
flushCache | 它的作用是在調(diào)用SQL后熔掺,是否要求清空之前查詢的本地緩存和二級(jí)緩存 | 布爾值,默認(rèn)值為 false |
useCache | 啟動(dòng)二級(jí)緩存的開關(guān)非剃,是否將此次結(jié)果緩存 | 布爾值瞬女,默認(rèn)值為tue |
timeout | 設(shè)置超時(shí)參數(shù),等超時(shí)的時(shí)候?qū)伋霎惓E耍瑔挝粸槊?/td> | 默認(rèn)值是數(shù)據(jù)庫廠商提供的JDBC驅(qū)動(dòng)所設(shè)置的秒數(shù) |
fetchSize | 獲取記錄的總條數(shù)設(shè)定 | 默認(rèn)值是數(shù)據(jù)庫廠商提供的JDBC驅(qū)動(dòng)所設(shè)置的條數(shù) |
statementType | 告訴MyBatis使用哪個(gè)JDBC的Statement工作,取值為STATEMENT、PREPARED疯坤、CallableStatement | 默認(rèn)為PREPARED |
resultSetType | 這是對(duì)JDBC的resultSet接口而言报慕,它的值包括 FORWARD_ONLY(游標(biāo)允許向前訪問)、 SCROLL_SENSITIVE(雙向滾動(dòng)压怠,但不及時(shí)更新眠冈,就是如果數(shù)據(jù)庫里的數(shù)據(jù)修改過,并不在resultSet中反應(yīng)出來)菌瘫、 SCROLL_ INSENSITIVE(雙向滾動(dòng)蜗顽,并及時(shí)跟蹤數(shù)據(jù)庫的更新,以便更改 resultSet中的數(shù)據(jù)) | 默認(rèn)值是數(shù)據(jù)庫廠商提供的JDBC驅(qū)動(dòng)所設(shè)置的 |
databaseId | 參考第三章 | |
resultOrdered | 這個(gè)設(shè)置僅適用于嵌套結(jié)果集select語句雨让。如果為true雇盖,就是假設(shè)包含了嵌套結(jié)果集或者是分組了。當(dāng)返回一個(gè)主結(jié)果行的時(shí)候栖忠,就不能對(duì)前面結(jié)果集的引用崔挖。這就確保了在獲取嵌套的結(jié)果集的時(shí)候不至于導(dǎo)致內(nèi)存不夠用 | 布爾值,默認(rèn)為false |
resultSets | 適合于多個(gè)結(jié)果集的情況庵寞,它將列出執(zhí)行SQL后每個(gè)結(jié)果集的名稱狸相,每個(gè)名稱之間用逗號(hào)分隔 | 很少使用 |
3.2 自動(dòng)映射
mybatis-config.xml文件中<settings>
標(biāo)簽有一個(gè)參數(shù)autoMapping Behavior,當(dāng)它不設(shè)置為NONE的時(shí)候捐川,MyBatis會(huì)提供自動(dòng)映射的功能脓鹃,只要返回的SQL列名和JavaBean的屬性一致,MyBatis就會(huì)幫助我們回填這些字段而無需任何配置古沥,它可以在很大程度上簡化我們的配置工作瘸右。
自動(dòng)映射策略
自動(dòng)映射可以在settings元素中配置autoMappingBehavior屬性值來設(shè)置其策略:
- NONE,取消自動(dòng)映射渐白。
- PARTIAL(默認(rèn)值)尊浓,只會(huì)自動(dòng)映射,沒有定義嵌套結(jié)果集映射的結(jié)果集纯衍。
- FULL栋齿,會(huì)自動(dòng)映射任意復(fù)雜的結(jié)果集(無論是否嵌套),在性能上會(huì)下降襟诸。
下劃線轉(zhuǎn)駝峰
如果你的數(shù)據(jù)庫是規(guī)范命名的瓦堵,即每一個(gè)單詞都用下劃線分隔,POJO采用駝峰式命名方法歌亲,那么可以在<settings>
設(shè)置 mapUnderscoreToCamelCase為true菇用,這樣就可以實(shí)現(xiàn)從數(shù)據(jù)庫到POJO的自動(dòng)映射了。
3.3 傳遞多個(gè)參數(shù)
使用Map傳遞多個(gè)參數(shù)
可是使用Map來傳遞多個(gè)參數(shù)陷揪,如下所示惋鸥。這種方式的不足之處是業(yè)務(wù)關(guān)聯(lián)性不強(qiáng)杂穷,造成可讀性下降。
public interface TbNameMapper {
UserDomain select(Map<String, Object> map);
}
<!-- parameterType也可以縮寫為map -->
<select id="select" resultType="com.ankeetc.spring.domain.UserDomain" parameterType="java.util.Map" >
SELECT * FROM tb_name WHERE id = #{id}
</select>
使用注解傳遞多個(gè)參數(shù)
可是使用@Param
來實(shí)現(xiàn)傳遞多個(gè)參數(shù)卦绣,此時(shí)無需在XML中配置parameterType耐量。
public interface TbNameMapper {
UserDomain select(@Param("id") Long id);
}
<!-- 無須parameterType -->
<select id="select" resultType="com.ankeetc.spring.domain.UserDomain" >
SELECT * FROM tb_name WHERE id = #{id}
</select>
也可以使用JavaBean傳遞多個(gè)參數(shù)
public interface TbNameMapper {
UserDomain select(UserDomain userDomain);
}
<select id="select" resultType="com.ankeetc.spring.domain.UserDomain" parameterType="com.ankeetc.spring.domain.UserDomain">
SELECT * FROM tb_name WHERE id = #{id}
</select>
3.4 使用resultMap映射結(jié)果集
如果自動(dòng)映射滿足不了需求,此時(shí)需要通過resultMap來映射滤港±妊眩可以創(chuàng)建一個(gè)<resultMap>
元素,然后通過<select>
中的resultMap屬性來引用resultMap溅漾,后面會(huì)詳細(xì)分析<resultMap>
元素山叮。
4 insert元素
4.1 insert元素的配置元素
insert的很多配置元素是和select一樣的,就不再贅述添履,下面列出select中沒有的屬性
屬性 | 說明 | 備注 |
---|---|---|
keyProperty | 表示以哪個(gè)列作為屬性的主鍵屁倔,不能和keyColumn同時(shí)使用 | 設(shè)置哪個(gè)鍵為主鍵,聯(lián)合主鍵可以用逗號(hào)將其隔開 |
keyColumn | 表示第幾列是主鍵缝龄,不能和keyProperty同時(shí)使用 | 聯(lián)合主鍵可以用逗號(hào)將其隔開 |
useGeneratedKeys | 這會(huì)令MyBatis使用JDBC的getGeneratedKeys方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵汰现,但是使用它就必須要給keyProperty或者 keyColumn賦值 | 布爾值,默認(rèn)為false |
lang | 自定義語言,可使用第三方語言 | 使用得較少叔壤,不做介紹 |
4.2 主鍵回填
- 設(shè)置useGeneratedKeys=true
- 設(shè)置keyProperty為需要回填的bean的屬性
<insert id="insert" parameterType="com.ankeetc.spring.domain.UserDomain" useGeneratedKeys="true" keyProperty="id" >
INSERT INTO tb_name (name) VALUES (#{name})
</insert>
public static void func() {
// 獲取主鍵值
UserDomain userDomain = new UserDomain("LOL");
tbNameMapper.insert(userDomain);
System.out.println(userDomain.getId());
}
4.3 使用selectKey元素
在insert元素中可以使用selectKey元素來對(duì)回填的主鍵進(jìn)行修改
<insert id="insert" parameterType="com.ankeetc.spring.domain.UserDomain" useGeneratedKeys="true" keyProperty="id" >
<!-- 在原有id的基礎(chǔ)上加2 -->
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT (max(id) + 2) AS newId FROM tb_name
</selectKey>
INSERT INTO tb_name (name) VALUES (#{name})
</insert>
5 update和delete元素
這兩個(gè)元素和insert元素基本一樣瞎饲,都會(huì)返回一個(gè)整數(shù)表示此次執(zhí)行影響的行數(shù)。
6 參數(shù)
6.1 參數(shù)配置
我們可以傳入一個(gè)簡單的參數(shù)如基本類型和字符串炼绘;也可以傳入JavaBean嗅战;還可以指定特定的類型,以確定使用哪個(gè) typeHandler處理它們俺亮。定義參藪屬性的時(shí)候驮捍,MyBatis不允許換行。
#{age, javaType=int, jdbcType=NUMERIC}
- 指定typeHandler:
#{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler}
- 數(shù)值型的參數(shù)設(shè)置其保存的精度:
#{price, javaType=double, jdbcType=NUMERIC, numericScale=2}
6.2 支持存儲(chǔ)過程
- 存儲(chǔ)過程存在3種參數(shù)脚曾,通過指定mode屬性來確定是哪一種參數(shù):IN东且、OUT、INOUT本讥。
- 當(dāng)參數(shù)設(shè)置為OUT珊泳、INOUT的時(shí)候,MyBatis會(huì)將存儲(chǔ)過程返回的結(jié)果設(shè)置到你制定的參數(shù)中拷沸。
- 當(dāng)你返回的是一個(gè)游標(biāo)(JdbcType=CURSOR)的時(shí)候色查,還需要去設(shè)置resultMap以便MyBatis將存儲(chǔ)過程的參數(shù)映射到對(duì)應(yīng)的類型。
6.3 特殊字符串替換和處理
有時(shí)候我們需要的是傳遞SQL語句的本身撞芍,而不是SQL所需要的參數(shù)秧了,MyBatis也對(duì)這樣的場景進(jìn)行了支持,這些是Hibernate難以做到的序无。例如验毡,我們需要傳遞變量columns="name, value"
給SQL衡创,讓其組裝成為SQL語句,而不想被像普通參數(shù)一樣被處理晶通,這個(gè)時(shí)候可以用${param}
來實(shí)現(xiàn)钧汹。
public static void func() {
UserDomain userDomain = tbNameMapper.select("name, value", 18L);
}
public interface TbNameMapper {
UserDomain select(@Param("columns") String columns, @Param("id") Long id);
}
<select id="select" resultType="com.ankeetc.spring.domain.UserDomain">
SELECT ${columns} FROM tb_name WHERE id = #{id}
</select>
7 sql元素
sql元素的意義,在于我們可以定義一串SQL語句的組成部分录择,其他的語句可以通過引
用來使用它。這樣就可以實(shí)現(xiàn)一處定義多處引用碗降,大大減少工作量隘竭。
<sql id="sql1">
name, value
</sql>
<select id="select" resultType="com.ankeetc.spring.domain.UserDomain">
SELECT
<include refid="sql1"/>
FROM tb_name WHERE id = #{id}
</select>
8 resultMap結(jié)果映射集
resultMap的作用是定義映射規(guī)則、級(jí)聯(lián)的更新讼渊、定制類型轉(zhuǎn)化器等动看,主要作用是一個(gè)結(jié)果集的映射關(guān)系。
8.1 resultMap元素里的元素
- constructor元素用于配置構(gòu)造方法爪幻,一個(gè)POJO可能不存在無參數(shù)的構(gòu)造方法菱皆,這個(gè)時(shí)候我們就可以使用 constructor進(jìn)行配置。
- id元素是表示哪個(gè)列是主鍵挨稿,允許多個(gè)主鍵構(gòu)成聯(lián)合主鍵仇轻。
- result是配置POJO到SQL列名的映射關(guān)系。
- 級(jí)聯(lián)屬性association奶甘、discriminator篷店、collection后面詳細(xì)介紹
8.2 使用POJO存儲(chǔ)結(jié)果集
我們?cè)趕elect中使用map存儲(chǔ)結(jié)果集,這樣雖然可以匹配所有結(jié)果集臭家,但會(huì)造成可讀性的下降疲陕,最好的方式是使用POJO。我們可以在select元素中使用resultMap屬性引用我們定義好的resultMap元素钉赁。
<!-- id是resultMap的唯一標(biāo)識(shí)蹄殃,type是對(duì)應(yīng)POJO的全路徑名 -->
<resultMap id="base_result_map" type="com.ankeetc.spring.domain.UserDomain">
<id property="id" column="id" javaType="Long" jdbcType="BIGINT"/>
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<result property="value" column="value" javaType="Integer" jdbcType="INTEGER"/>
</resultMap>
<select id="select" resultMap="base_result_map">
SELECT * FROM tb_name WHERE id = #{id}
</select>
8.3 級(jí)聯(lián)屬性
在數(shù)據(jù)庫中包含著一對(duì)一、一對(duì)多你踩、多對(duì)多的關(guān)系诅岩,比如一個(gè)數(shù)據(jù)庫中有一張班級(jí)表:
- 一對(duì)一的關(guān)系:班級(jí)表里每個(gè)班級(jí)都有一個(gè)班主任
- 一對(duì)多的關(guān)系:每個(gè)班級(jí)有多個(gè)學(xué)生
- 多對(duì)多的關(guān)系:一個(gè)老師既可以是多個(gè)班級(jí)的老師,一個(gè)班級(jí)也可以有多個(gè)老師(在實(shí)際中姓蜂,多對(duì)多的關(guān)系應(yīng)用不多按厘,因?yàn)樗鼤?huì)增加理解和關(guān)聯(lián)的復(fù)雜度,更好的方法是用一對(duì)多的關(guān)系把它分解為雙向關(guān)系)
MyBatis中3種級(jí)聯(lián)
- association钱慢,代表一對(duì)一關(guān)系逮京,比如每個(gè)班級(jí)都有一個(gè)班長
- collection,代表一對(duì)多關(guān)系束莫,比如每個(gè)班級(jí)有多個(gè)學(xué)生
- discriminator懒棉,鑒別器草描,它可以根據(jù)實(shí)際選擇采用哪個(gè)類作為實(shí)例,允許你根據(jù)特定的條件去關(guān)聯(lián)不同的結(jié)果集策严。
association與collection
現(xiàn)在有兩張表分別是班級(jí)表和學(xué)生表穗慕,我們?cè)诎嗉?jí)表的Mapper文件中,通過association映射了班級(jí)和班長一對(duì)一的關(guān)系妻导;通過collection映射了班級(jí)和學(xué)生的一對(duì)多關(guān)系逛绵。此時(shí)我們通過ClassMapper的select方法,能夠自動(dòng)裝填對(duì)應(yīng)的班長和學(xué)生信息倔韭。
CREATE TABLE `class` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`grade` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `student` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`sex` int(11) NOT NULL DEFAULT '0',
`class_id` bigint(20) NOT NULL DEFAULT '0',
`monitor` int(11) NOT NULL DEFAULT '0' COMMENT '0不是班長术浪,1是班長',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
<!-- 這是班級(jí)表 -->
<mapper namespace="com.ankeetc.spring.mapper.ClassMapper" >
<resultMap id="base_result_map" type="com.ankeetc.spring.domain.ClassDomain">
<id property="id" column="id" javaType="Long" jdbcType="BIGINT" />
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<!-- 班級(jí)與班長的一對(duì)一關(guān)系 -->
<association property="classMonitor" column="id" select="com.ankeetc.spring.mapper.StudentMapper.selectClassMonitor"/>
<!-- 班級(jí)與學(xué)生的一對(duì)一關(guān)系 -->
<collection property="students" column="id" select="com.ankeetc.spring.mapper.StudentMapper.selectByClassId"/>
</resultMap>
<select id="select" resultMap="base_result_map">
SELECT * FROM class WHERE id = #{id}
</select>
</mapper>
<!-- 這是學(xué)生表 -->
<mapper namespace="com.ankeetc.spring.mapper.StudentMapper" >
<resultMap id="base_result_map" type="com.ankeetc.spring.domain.StudentDomain">
<id property="id" column="id" javaType="Long" jdbcType="BIGINT" />
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<result property="sex" column="sex" javaType="Integer" jdbcType="INTEGER"/>
<result property="classId" column="class_id" javaType="Long" jdbcType="BIGINT"/>
<result property="monitor" column="monitor" javaType="Integer" jdbcType="INTEGER"/>
</resultMap>
<select id="selectByClassId" resultMap="base_result_map">
SELECT * FROM student WHERE class_id = #{classId}
</select>
<select id="selectClassMonitor" resultMap="base_result_map">
SELECT * FROM student WHERE class_id = #{classId} AND monitor = 1 LIMIT 1
</select>
</mapper>
discriminator鑒別器
鑒別器級(jí)聯(lián)是在特定的條件下去使用不同的POJO,比如在上例中寿酌,我們可以根據(jù)班級(jí)中的年級(jí)屬性(grade)進(jìn)行判斷去關(guān)聯(lián)是小學(xué)還是初中胰苏,然后分別進(jìn)行不同的映射。discriminator相當(dāng)于Java語言中的switch語句醇疼。
<mapper namespace="com.ankeetc.spring.mapper.ClassMapper" >
<resultMap id="base_result_map" type="com.ankeetc.spring.domain.ClassDomain">
<id property="id" column="id" javaType="Long" jdbcType="BIGINT" />
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
<association property="classMonitor" column="id" select="com.ankeetc.spring.mapper.StudentMapper.selectClassMonitor"/>
<collection property="students" column="id" select="com.ankeetc.spring.mapper.StudentMapper.selectByClassId"/>
<!-- 通過discriminator進(jìn)行分配 -->
<discriminator javaType="Integer" column="grade">
<case value="1" resultMap="primary"/>
<case value="2" resultMap="junior_high"/>
</discriminator>
</resultMap>
<!-- 如果grade=1則會(huì)使用這個(gè)resultMap硕并,返回的實(shí)際類型是PrimaryClassDomain -->
<resultMap id="primary" type="com.ankeetc.spring.domain.PrimaryClassDomain" extends="base_result_map">
<result property="grade" column="grade" javaType="Integer" jdbcType="INTEGER"/>
</resultMap>
<!-- 如果grade=2則會(huì)使用這個(gè)resultMap,返回的實(shí)際類型是PrimaryClassDomain -->
<resultMap id="junior_high" type="com.ankeetc.spring.domain.PrimaryClassDomain" extends="base_result_map">
<result property="grade" column="grade" javaType="Integer" jdbcType="INTEGER"/>
</resultMap>
<select id="select" resultMap="base_result_map">
SELECT * FROM class WHERE id = #{id}
</select>
</mapper>
級(jí)聯(lián)的優(yōu)缺點(diǎn)
級(jí)聯(lián)的優(yōu)勢(shì):能夠方便快捷地獲取數(shù)據(jù)秧荆。
級(jí)聯(lián)的缺點(diǎn):有時(shí)候我們并不需要獲取所有的數(shù)據(jù)倔毙;有一個(gè)關(guān)聯(lián)我們就要多執(zhí)行一次SQL,這樣會(huì)造成SQL執(zhí)行過多導(dǎo)致性能下降辰如,這就是N+1的問題普监,為了解決這個(gè)問題我們應(yīng)該考慮采用延遲加載的功能。
全局延遲加載
為了處理N+1的問題琉兜,My Batis引入了延遲加載的功能凯正。延遲加載功能的意義在于,開始并不取出級(jí)聯(lián)數(shù)據(jù)豌蟋,只有當(dāng)使用它了才發(fā)送SQL去取回?cái)?shù)據(jù)廊散。MyBatis的配置中有兩個(gè)全局的參數(shù)lazyLoadingEnabled和aggressiveLazyLoading。
- lazyLoadingEnabled的含義是是否開啟延遲加載功能梧疲;
- aggressiveLazyLoading的含義是對(duì)任意延遲屬性的調(diào)用會(huì)使帶有延遲加載屬性的對(duì)象完整加載允睹;反之,每種屬性將按需加載幌氮。
局部延遲加載
由于全局配置的靈活性較差缭受,MyBatis提供了局部延遲加載的功能。我們可以在association和 collection元素上加入屬性值fetchType就可以了该互。它有兩個(gè)取值范圍米者,eager和lazy。它的默認(rèn)值取決于你在配置文件settings的配置。假如我們沒有配置它蔓搞,那么它們就是eager一旦你配置了它們胰丁,那么全局的變量就會(huì)被它們所覆蓋,這樣我們就可以靈活地指定哪些東西可以立即加載喂分,哪些東西可以延遲加載锦庸。
9 緩存cache
目前流行的緩存服務(wù)器有MongoDB、Redis蒲祈、Ehcache等甘萧,緩存將數(shù)據(jù)保存在內(nèi)存中,在讀取的時(shí)候無需再從磁盤讀入梆掸,因此具備快速讀取和使用的特點(diǎn)幔嗦。如果緩存命中率高,那么可以極大地提高系統(tǒng)的性能沥潭;如果緩存命中率很低,那么緩存就不存在使用的意義了嬉挡,所以使用緩存的關(guān)鍵就是提高命中钝鸽。
9.1 一級(jí)系統(tǒng)緩存
- MyBatis對(duì)緩存提供支持,在沒有配置的默認(rèn)的情況下庞钢,它只開啟一級(jí)緩存(一級(jí)緩存只是相對(duì)于同一個(gè) SqlSession而言)拔恰,所以在參數(shù)和SQL完全一樣的情況下,我們使用同一個(gè) SqlSession對(duì)象調(diào)用同一個(gè)Mapper的方法基括,往往只執(zhí)行一次SQL颜懊。
- 使用SqlSession第一次查詢后,MyBatis會(huì)將其放在緩存中风皿。以后再查詢的時(shí)候河爹,如果沒有聲明需要刷新且緩存沒超時(shí),SqlSession都只會(huì)取出當(dāng)前緩存的數(shù)據(jù)桐款,而不會(huì)再次發(fā)送SQL到數(shù)據(jù)庫咸这。
- 如果使用的是不同的SqlSession對(duì)象,因?yàn)椴煌腟qlSession都是相互隔離的魔眨,所以用相同的 Mapper媳维、參數(shù)和方法,它還是會(huì)再次發(fā)送SQL到數(shù)據(jù)庫去執(zhí)行遏暴。
public static void func() throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 在此處執(zhí)行了兩次select操作侄刽,只有依次數(shù)據(jù)庫訪問操作
studentMapper.selectClassMonitor(1L);
studentMapper.selectClassMonitor(1L);
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
==> Preparing: SELECT * FROM student WHERE class_id = ? AND monitor = 1 LIMIT 1
==> Parameters: 1(Long)
<== Columns: id, name, sex, class_id, monitor
<== Row: 2, lucas, 20, 1, 1
<== Total: 1
<!--使用標(biāo)準(zhǔn)log來實(shí)現(xiàn)日志打印-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
9.2 二級(jí)系統(tǒng)緩存
一級(jí)緩存在各個(gè)SqlSession間是相互隔離的。為了克服這個(gè)問題朋凉,我們往往需要配置二級(jí)緩存州丹,使得緩存在SqlsessionFactory層面上能夠提供給各個(gè)SqISession對(duì)象共享,二級(jí)緩存默認(rèn)是不開啟的侥啤。
- 二級(jí)緩存的時(shí)候要求返回的POJO必須是可序列化的,也就是要求實(shí)現(xiàn)Serializable接口当叭。
- 配置的緩存的方法很簡單茬故,只需要在Mapper.xml文件中加入
<cache/>
磺芭。
public static void func() throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
try (SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession()) {
StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
// 如果沒有<cache/>標(biāo)簽,這條語句將會(huì)執(zhí)行兩次select操作
studentMapper1.selectClassMonitor(1L);
studentMapper2.selectClassMonitor(1L);
sqlSession1.commit();
sqlSession2.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
cache的默認(rèn)配置
- 映射語句文件中的所有select語句將會(huì)被緩存钾腺。
- 映射語句文件中的所有insert讥裤、update和delete語句會(huì)刷新緩存。
- 緩存會(huì)使用默認(rèn)的Least Recently Used(LRU己英,最近最少使用的)算法來收回。
- 根據(jù)時(shí)間表损肛,比如No Flush Interval,(NFI治拿,沒有刷新間隔),緩存不會(huì)以任何時(shí)
間順序來刷新 - 緩存會(huì)存儲(chǔ)列表集合或?qū)ο螅o論查詢方法返回什么)的1024個(gè)引用见坑。
- 緩存會(huì)被視為是read/write(可讀/可寫)的緩存,意味著對(duì)象檢索不是共享的捏检,且可以安全地被調(diào)用者修改荞驴,不干擾其他調(diào)用者或線程所做的潛在修改。
配置緩存屬性
- eviction:代表的是緩存回收策略贯城,LRU(最近最少使用的戴尸,移除最長時(shí)間不用的對(duì)象)、FFO(先進(jìn)先出冤狡,按對(duì)象進(jìn)入緩存的順序來移除它們)孙蒙、SOFT(軟引用,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象)悲雳、WEAK(弱引用挎峦,更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象)。
- flushInterval:刷新間隔時(shí)間合瓢,單位為亳秒,這里配置的是100秒刷新坦胶,如果不配置它,那么當(dāng)SQL被執(zhí)行的時(shí)候才會(huì)去刷新緩存。
- size:引用數(shù)目顿苇,一個(gè)正整數(shù)峭咒,代表緩存最多可以存儲(chǔ)多少個(gè)對(duì)象,設(shè)置過大會(huì)導(dǎo)致內(nèi)存溢出纪岁。
- readOnly:只讀凑队,意味著緩存數(shù)據(jù)只能讀取而不能修改,這樣設(shè)置的好處是我們可以快速讀取緩存幔翰,缺點(diǎn)是我們沒有辦法修改緩存漩氨,默認(rèn)不允許我們修改。
9.3 自定義緩存
系統(tǒng)緩存是MyBatis應(yīng)用機(jī)器上的本地緩存遗增,但是在分布式系統(tǒng)上叫惊,需要使用各類不同的緩存服務(wù)器,這是需要實(shí)現(xiàn)MyBatis為我們提供的接口org.apache.ibatis.cache.Cache
做修,緩存接口簡介如下所示:
// 獲取緩存編號(hào)
String getId();
// 保存key value
void putObject(Object key, Object value);
// 根據(jù)key獲取value
Object getObject(Object key);
// 根據(jù)key移除對(duì)象
Object removeObject(Object key);
// 清空緩存
void clear();
// 獲取緩存大小
int getSize();
// 獲取緩存對(duì)讀寫鎖
ReadWriteLock getReadWriteLock();
我們?cè)谟成淦魃系膇nsert霍狰、delete、select饰及、update等元素蚓耽,可以配置useCache和 flushCache兩個(gè)屬性來控制緩存的使用旋炒,useCache表示是否需要使用緩存瘫镇,而flushCache表示插入后是否需要刷新緩存铣除。