Mybatis系列第12篇:掌握緩存為查詢提速!

文章轉(zhuǎn)載自:http://www.itsoku.com/article/257

Mybatis系列目標(biāo):從入門開始開始掌握一個高級開發(fā)所需要的Mybatis技能。

這是mybatis系列第12篇彩掐,源碼位于文章尾部构舟!

什么是緩存?

緩存就是存儲數(shù)據(jù)的一個地方(稱作:Cache),當(dāng)程序要讀取數(shù)據(jù)時,會首先從緩存中獲取侄榴,有則直接返回胖眷,否則從其他存儲設(shè)備中獲取,緩存最重要的一點就是從其內(nèi)部獲取數(shù)據(jù)的速度是非诚则颍快的,通過緩存可以加快數(shù)據(jù)的訪問速度。比如我們從db中獲取數(shù)據(jù)麦撵,中間需要經(jīng)過網(wǎng)絡(luò)傳輸耗時,db server從磁盤讀取數(shù)據(jù)耗時等,如果這些數(shù)據(jù)直接放在jvm對應(yīng)的內(nèi)存中免胃,訪問是不是會快很多音五。

mybatis中的緩存

通常情況下mybatis會訪問數(shù)據(jù)庫獲取數(shù)據(jù),中間涉及到網(wǎng)絡(luò)通信羔沙,數(shù)據(jù)庫從磁盤中讀取數(shù)據(jù)躺涝,然后將數(shù)據(jù)返回給mybatis,總的來說耗時還是挺長的扼雏,mybatis為了加快數(shù)據(jù)查詢的速度坚嗜,在其內(nèi)部引入了緩存來加快數(shù)據(jù)的查詢速度。

mybatis中分為一級緩存和二級緩存诗充。

一級緩存是SqlSession級別的緩存苍蔬,在操作數(shù)據(jù)庫時需要構(gòu)造 sqlSession對象,在對象中有一個數(shù)據(jù)結(jié)構(gòu)(HashMap)用于存儲緩存數(shù)據(jù)蝴蜓,不同的sqlSession之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互相不影響的碟绑。

二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句茎匠,多個SqlSession可以共用二級緩存格仲,二級緩存是跨SqlSession的。

下面我們詳細(xì)說一下一級緩存和二級緩存的各種用法和注意點诵冒。

一級緩存

一級緩存是SqlSession級別的緩存
每個SqlSession都有自己單獨的一級緩存
多個SqlSession之間的一級緩存是相互隔離的凯肋,互不影響,
mybatis中一級緩存是默認(rèn)自動開啟的

一級緩存工作原理
在同一個SqlSession中去多次去執(zhí)行同樣的查詢汽馋,每次執(zhí)行的時候會先到一級緩存中查找侮东,如果緩存中有就直接返回,如果一級緩存中沒有相關(guān)數(shù)據(jù)豹芯,mybatis就會去db中進(jìn)行查找苗桂,然后將查找到的數(shù)據(jù)放入一級緩存中,第二次執(zhí)行同樣的查詢的時候告组,會發(fā)現(xiàn)緩存中已經(jīng)存在了煤伟,會直接返回。
一級緩存的存儲介質(zhì)是內(nèi)存木缝,是用一個HashMap來存儲數(shù)據(jù)的便锨,所以訪問速度是非常快的我碟。

一級緩存案例

案例sql腳本

DROP DATABASE IF EXISTS `javacode2018`;
CREATE DATABASE `javacode2018`;

USE `javacode2018`;

DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
  id int AUTO_INCREMENT PRIMARY KEY COMMENT '用戶id',
  name VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用戶名',
  age SMALLINT NOT NULL DEFAULT 1 COMMENT '年齡'
) COMMENT '用戶表';
INSERT INTO t_user VALUES (1,'路人甲Java',30),(2,'張學(xué)友',50),(3,'劉德華',50);

下面是查詢用戶信息放案,返回一個list

<select id="getList1" resultType="com.javacode2018.chat05.demo9.model.UserModel" parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

對應(yīng)的mapper接口方法

List<UserModel> getList1(Map<String, Object> paramMap);

測試用例

com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest1

/**
 * 一級緩存測試
 *
 * @throws IOException
 */
@Test
public void level1CacheTest1() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查詢
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
        //第二次查詢
        List<UserModel> userModelList2 = mapper.getList1(null);
        log.info("{}", userModelList2);
        log.info("{}", userModelList1 == userModelList2);
    }
}

上面的代碼在同一個SqlSession中去執(zhí)行了2次獲取用戶列表信息,2次查詢結(jié)果分別放在userModelList1userModelList2矫俺,最終代碼中也會判斷這兩個集合是否相等吱殉,下面我們運(yùn)行一下看看會訪問幾次db掸冤?

運(yùn)行輸出

01:15.312 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
01:15.340 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
01:15.364 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 3
01:15.364 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50)]
01:15.367 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50)]
01:15.367 [main] INFO  c.j.chat05.demo9.Demo9Test - true

從輸出中可以看出看到,sql只輸出了一次友雳,說明第一次會訪問數(shù)據(jù)庫稿湿,第二次直接從緩存中獲取的,最后輸出了一個true押赊,也說明兩次返回結(jié)果是同一個對象饺藤,第二次直接從緩存中獲取數(shù)據(jù)的,加快了查詢的速度流礁。

清空一級緩存的3種方式

同一個SqlSession中查詢同樣的數(shù)據(jù)涕俗,mybatis默認(rèn)會從一級緩存中獲取,如果緩存中沒有神帅,才會訪問db再姑,那么我們?nèi)绾稳デ蹇找患壘彺婺兀瑥?qiáng)制讓查詢?nèi)ピL問db呢找御?

讓一級緩存失效有3種方式:

  1. SqlSession中執(zhí)行增元镀、刪、改操作萎坷,此時sqlsession會自動清理其內(nèi)部的一級緩存
  2. 調(diào)用SqlSession中的clearCache方法清理其內(nèi)部的一級緩存
  3. 設(shè)置Mapper xml中select元素的flushCache屬性值為true凹联,那么執(zhí)行查詢的時候會先清空一級緩存中的所有數(shù)據(jù)沐兰,然后去db中獲取數(shù)據(jù)

上面方式任何一種都會讓當(dāng)前SqlSession中的一級緩存失效哆档,進(jìn)而去db中獲取數(shù)據(jù),下面我們來分別演示這3中情況住闯。

方式1:增刪改讓一級緩存失效

當(dāng)執(zhí)行增刪改操時瓜浸,mybatis會將當(dāng)前SqlSession一級緩存中的所有數(shù)據(jù)都清除。

案例代碼:

com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest2

/**
 * 增刪改使一級緩存失效
 *
 * @throws IOException
 */
@Test
public void level1CacheTest2() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查詢
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
        //新增一條數(shù)據(jù)
        mapper.insert1(UserModel.builder().id(100).name("路人").age(30).build());
        //第二次查詢
        List<UserModel> userModelList2 = mapper.getList1(null);
        log.info("{}", userModelList2);
        log.info("{}", userModelList1 == userModelList2);
    }
}

上面同一個SqlSession中執(zhí)行了3個操作比原,同樣的查詢執(zhí)行了2次插佛,2次查詢中間夾了一個插入操作。

運(yùn)行輸出

21:55.097 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
21:55.135 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
21:55.159 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 3
21:55.159 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50)]
21:55.161 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==>  Preparing: INSERT INTO t_user (id,name,age) VALUES (?,?,?) 
21:55.162 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==> Parameters: 100(Integer), 路人(String), 30(Integer)
21:55.165 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - <==    Updates: 1
21:55.166 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
21:55.166 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
21:55.167 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
21:55.168 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
21:55.168 [main] INFO  c.j.chat05.demo9.Demo9Test - false

從輸出中可以看出2次查詢都訪問了db量窘,并且兩次查詢的結(jié)果是不一樣的雇寇,兩個集合也不相等,插入數(shù)據(jù)讓緩存失效是可以理解的蚌铜,插入操作可能會改變數(shù)據(jù)庫中的數(shù)據(jù)锨侯,所以如果再從緩存中去獲取,可能獲取到的數(shù)據(jù)和db中的數(shù)據(jù)不一致的情況冬殃,mybatis為了避免這種情況囚痴,在執(zhí)行插入操作的時候,會將SqlSession中的一級緩存清空审葬。當(dāng)然刪除和修改也同樣會改變db中的數(shù)據(jù)深滚,如果在同一個SqlSession中去執(zhí)行刪除或者修改數(shù)據(jù)的時候奕谭,mybatis也一樣會清除一級緩存中的所有數(shù)據(jù),刪除和修改大家自己可以寫2個例子試試痴荐,看看是否也會清理一級緩存中的數(shù)據(jù)血柳。

方式2:SqlSession.clearCache清理一級緩存

SqlSession.clearCache()方法會將當(dāng)前SqlSession一級緩存中的所有數(shù)據(jù)清除。

案例代碼:

com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest3

/**
 * 調(diào)用sqlSession.clearCache()清理一級緩存
 *
 * @throws IOException
 */
@Test
public void level1CacheTest3() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查詢
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
        //調(diào)用clearCache方法清理當(dāng)前SqlSession中的緩存
        sqlSession.clearCache();
        //第二次查詢
        List<UserModel> userModelList2 = mapper.getList1(null);
        log.info("{}", userModelList2);
        log.info("{}", userModelList1 == userModelList2);
    }
}

運(yùn)行輸出

31:21.937 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
31:21.966 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
31:21.985 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
31:21.985 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
31:21.988 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
31:21.988 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
31:21.989 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
31:21.989 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
31:21.990 [main] INFO  c.j.chat05.demo9.Demo9Test - false

從輸出中可以看出蹬昌,2次同樣的查詢都訪問了db混驰。

方式3:Select元素的flushCache置為true

將Mapper xml中select元素的flushCache屬性置為true的時候,每次執(zhí)行這個select元素對應(yīng)的查詢之前皂贩,mybatis會將當(dāng)前SqlSession中一級緩存中的所有數(shù)據(jù)都清除栖榨。

案例代碼

新增一個select元素的查詢,將flushCache元素置為true明刷,注意:select元素這個屬性的默認(rèn)值是false婴栽。

<select id="getList2" flushCache="true" resultType="com.javacode2018.chat05.demo9.model.UserModel" parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

對應(yīng)測試用例

com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest4

/**
 * 將Mapper xml中select元素的flushCache屬性置為true的時候,每次執(zhí)行這個select元素對應(yīng)的查詢之前辈末,mybatis會將當(dāng)前SqlSession中一級緩存中的所有數(shù)據(jù)都清除愚争。
 *
 * @throws IOException
 */
@Test
public void level1CacheTest4() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查詢1:getList1
        log.info("查詢1 start");
        log.info("查詢1:getList1->{}", mapper.getList1(null));
        //查詢2:getList1
        log.info("查詢2 start");
        log.info("查詢2:getList1->{}", mapper.getList1(null));
        //查詢3:getList2
        log.info("查詢3 start");
        log.info("查詢3:getList2->{}", mapper.getList2(null));
        //查詢4:getList2
        log.info("查詢4 start");
        log.info("查詢4:getList2->{}", mapper.getList2(null));
        //查詢5:getList1
        log.info("查詢5 start");
        log.info("查詢5:getList1->{}", mapper.getList1(null));
    }
}

注意上面的代碼,代碼中有5次查詢挤聘,第1次轰枝、第2次、第5次查詢調(diào)用的都是getList1组去,這個查詢對應(yīng)的mapper xml中的select元素的flushCache屬性沒有設(shè)置鞍陨,默認(rèn)是false;而第3次和第4次查詢調(diào)用的是getList2从隆,getList2這個查詢對應(yīng)的mapper xml中的select(id=getList2)诚撵,它的flushCache屬性設(shè)置的是true,說明第3和第4次查詢會清空當(dāng)前一級緩存中所有數(shù)據(jù)键闺。

最終效果應(yīng)該是查詢1訪問db拿去數(shù)據(jù)寿烟,然后將其丟到一級緩存中,查詢2會直接從一級緩存中拿到數(shù)據(jù)辛燥,而查詢3走的是getList2筛武,發(fā)現(xiàn)flushCache為true,會先清空一級緩存中所有數(shù)據(jù)挎塌,也就是此時查詢1放入緩存的數(shù)據(jù)會被清理掉徘六,然后查詢3會訪問db獲取數(shù)據(jù),然后丟到緩存中勃蜘;而查詢4走的是getList2硕噩,發(fā)現(xiàn)flushCache為true,會先清空緩存缭贡,所以3放入一級緩存的數(shù)據(jù)會被清空炉擅,然后導(dǎo)致查詢4也會訪問db辉懒,查詢5去一級緩存中查詢數(shù)據(jù),因為查詢1和2放入緩存的數(shù)據(jù)都被查詢3清空了谍失,所以導(dǎo)致查詢5發(fā)現(xiàn)一級緩存中沒有數(shù)據(jù)眶俩,也會訪問db去獲取數(shù)據(jù)。

我們來運(yùn)行一下看看是否和我們分析的一致快鱼。

運(yùn)行輸出

20:10.872 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢1 start
20:11.164 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
20:11.195 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
20:11.216 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
20:11.216 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢1:getList1->[UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
20:11.218 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢2 start
20:11.218 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢2:getList1->[UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
20:11.219 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢3 start
20:11.219 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==>  Preparing: SELECT id,name,age FROM t_user 
20:11.219 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==> Parameters: 
20:11.222 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - <==      Total: 4
20:11.222 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢3:getList2->[UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
20:11.222 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢4 start
20:11.223 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==>  Preparing: SELECT id,name,age FROM t_user 
20:11.223 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==> Parameters: 
20:11.225 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - <==      Total: 4
20:11.225 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢4:getList2->[UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
20:11.225 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢5 start
20:11.225 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
20:11.225 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
20:11.230 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
20:11.230 [main] INFO  c.j.chat05.demo9.Demo9Test - 查詢5:getList1->[UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]

大家認(rèn)真看一下上面的輸出颠印,查詢1/3/4/5訪問了db,查詢2從緩存中獲取的抹竹,和我們上面分析的過程一致线罕。

一級緩存使用總結(jié)

  1. 一級緩存是SqlSession級別的,每個人SqlSession有自己的一級緩存窃判,不同的SqlSession之間一級緩存是相互隔離的
  2. mybatis中一級緩存默認(rèn)是自動開啟的
  3. 當(dāng)在同一個SqlSession中執(zhí)行同樣的查詢的時候钞楼,會先從一級緩存中查找,如果找到了直接返回袄琳,如果沒有找到會去訪問db询件,然后將db返回的數(shù)據(jù)丟到一級緩存中,下次查詢的時候直接從緩存中獲取
  4. 一級緩存清空的3種方式:
  • SqlSession中執(zhí)行增刪改會使一級緩存失效唆樊;
  • 調(diào)用SqlSession.clearCache方法會使一級緩存失效宛琅;
  • Mapper xml中的select元素的flushCache屬性置為true,那么執(zhí)行這個查詢會使一級緩存失效)

二級緩存

二級緩存的使用

一級緩存使用上存在局限性逗旁,必須要在同一個SqlSession中執(zhí)行同樣的查詢嘿辟,一級緩存才能提升查詢速度,如果想在不同的SqlSession之間使用緩存來加快查詢速度痢艺,此時我們需要用到二級緩存了仓洼。

二級緩存是mapper級別的緩存介陶,每個mapper xml有個namespace堤舒,二級緩存和namespace綁定的,每個namespace關(guān)聯(lián)一個二級緩存哺呜,多個SqlSession可以共用二級緩存舌缤,二級緩存是跨SqlSession的。

二級緩存默認(rèn)是沒有開啟的某残,需要我們在mybatis全局配置文件中進(jìn)行開啟:

<settings>
    <!-- 開啟二級緩存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

上面配置好了以后国撵,還需要在對應(yīng)的mapper xml加上下面配置,表示這個mapper中的查詢開啟二級緩存:

<cache/>

配置就這么簡單玻墅。

一二級緩存共存時查詢原理

一二級緩存如果都開啟的情況下介牙,數(shù)據(jù)查詢過程如下:

  1. 當(dāng)發(fā)起一個查詢的時候,mybatis會先訪問這個namespace對應(yīng)的二級緩存澳厢,如果二級緩存中有數(shù)據(jù)則直接返回环础,否則繼續(xù)向下
  2. 查詢一級緩存中是否有對應(yīng)的數(shù)據(jù)囚似,如果有則直接返回,否則繼續(xù)向下
  3. 訪問db獲取需要的數(shù)據(jù)线得,然后放在當(dāng)前SqlSession對應(yīng)的一級緩存中饶唤,并且在本地內(nèi)存中的另外一個地方存儲一份(這個地方我們就叫TransactionalCache)
  4. 當(dāng)SqlSession關(guān)閉的時候,也就是調(diào)用SqlSession的close方法的時候贯钩,此時會將TransactionalCache中的數(shù)據(jù)放到二級緩存中募狂,并且會清空當(dāng)前SqlSession一級緩存中的數(shù)據(jù)。

二級緩存案例

mybatis全局配置文件開啟二級緩存配置

<settings>
    <!-- 開啟二級緩存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

mapper xml中使用cache元素開啟二級緩存

<!-- 啟用二級緩存 -->
<cache/>

<select id="getList1" resultType="com.javacode2018.chat05.demo9.model.UserModel" parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

測試用例

com.javacode2018.chat05.demo9.Demo9Test#level2CacheTest1

/**
 * 二級緩存測試
 *
 * @throws IOException
 */
@Test
public void level2CacheTest1() throws IOException {
    String mybatisConfig = "demo9/mybatis-config1.xml";
    this.before(mybatisConfig);
    for (int i = 0; i < 2; i++) {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<UserModel> userModelList1 = mapper.getList1(null);
            log.info("{}", userModelList1);
        }
    }
}

上面執(zhí)行了2次查詢角雷,每次查詢都是新的SqlSession祸穷,運(yùn)行一下看看效果。

執(zhí)行輸出

34:36.574 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.0
34:36.831 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
34:36.864 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
34:36.883 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
34:36.883 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]
34:36.894 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.5
34:36.895 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]

注意上面第一行日志輸出:

Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.0

對這行做一個解釋:com.javacode2018.chat05.demo9.mapper.UserMapper是上面查詢訪問的mapper xml的namesapce的值勺三,去這個namespace對應(yīng)的二級緩存中去查詢數(shù)據(jù)粱哼,沒有查詢到,輸出中的0.0表示命中率檩咱,這次沒有命中揭措,所以命中率為0

然后就去db中訪問數(shù)據(jù)了,會將db中返回的數(shù)據(jù)放在一級緩存中刻蚯,第一次運(yùn)行完畢之后會自動調(diào)用SqlSession的close方法绊含,然后db中返回的數(shù)據(jù)會被丟到二級緩存中,第二次查詢的時候就直接從二級緩存中獲取到數(shù)據(jù)返回了炊汹,所以第二次查詢輸出如下:

34:36.894 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.5
34:36.895 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=100, name=路人, age=30)]

2次查詢都去訪問了二級緩存躬充,第二次命中了,命中率為1/2=0.5

清空或者跳過二級緩存的3種方式

當(dāng)二級緩存開啟的時候讨便,在某個mapper xml中添加cache元素之后充甚,這個mapper xml中所有的查詢都默認(rèn)開啟了二級緩存,那么我們?nèi)绾吻蹇栈蛘咛^二級緩存呢霸褒?
3種方式如下:

  1. 對應(yīng)的mapper中執(zhí)行增刪改查會清空二級緩存中數(shù)據(jù)
  2. select元素的flushCache屬性置為true伴找,會先清空二級緩存中的數(shù)據(jù),然后再去db中查詢數(shù)據(jù)废菱,然后將數(shù)據(jù)再放到二級緩存中
  3. select元素的useCache屬性置為false技矮,可以使這個查詢跳過二級緩存,然后去查詢數(shù)據(jù)

下面我們來演示一下每種方式對應(yīng)的效果殊轴。

方式1:增刪改會清除二級緩存中的數(shù)據(jù)

下面我們主要演示一下新增對二級緩存的影響衰倦。

案例代碼

com.javacode2018.chat05.demo9.Demo9Test#level2CacheTest2

/**
 * 增刪改會清除二級緩存中的數(shù)據(jù)
 *
 * @throws IOException
 */
@Test
public void level2CacheTest2() throws IOException {
    String mybatisConfig = "demo9/mybatis-config1.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
    }
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //新增一條數(shù)據(jù)
        mapper.insert1(UserModel.builder().id(Integer.valueOf(System.nanoTime() % 100000 + "")).name("路人").age(30).build());
    }
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
    }
}

上面使用了3個不同的SqlSession,第一次和第三次都調(diào)用了getList1執(zhí)行查詢旁理,中間執(zhí)行了一個插入操作樊零,mybatis執(zhí)行插入的時候,會先清除當(dāng)前namespace對應(yīng)的二級緩存中的數(shù)據(jù)孽文,所以上面2次查詢最終都會訪問db驻襟,來運(yùn)行一下看看效果十性。

運(yùn)行輸出

23:00.620 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.0
23:00.900 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
23:00.924 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
23:00.948 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 3
23:00.948 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50)]
23:00.951 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==>  Preparing: INSERT INTO t_user (id,name,age) VALUES (?,?,?) 
23:00.953 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==> Parameters: 79600(Integer), 路人(String), 30(Integer)
23:00.955 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - <==    Updates: 1
23:00.959 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.0
23:00.959 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
23:00.959 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
23:00.961 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
23:00.961 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=79600, name=路人, age=30)]
23:00.961 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==>  Preparing: INSERT INTO t_user (id,name,age) VALUES (?,?,?) 
23:00.962 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==> Parameters: 94100(Integer), 路人(String), 30(Integer)
23:00.967 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - <==    Updates: 1

從輸出中可以看出,2次查詢都訪問了db塑悼。上面演示的是插入會清空二級緩存的數(shù)據(jù)劲适,同樣刪除和修改也會先清除當(dāng)前namespace對應(yīng)的二級緩存中的數(shù)據(jù)。

方式2:select元素的flushCache屬性置為true

當(dāng)將mapper xml中select元素的flushCache屬性置為true厢蒜,會先清空二級緩存中的數(shù)據(jù)霞势,然后再去db中查詢數(shù)據(jù),然后將數(shù)據(jù)再放到二級緩存中斑鸦。

代碼

<select id="getList2" flushCache="true" resultType="com.javacode2018.chat05.demo9.model.UserModel"
        parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

測試用例

com.javacode2018.chat05.demo9.Demo9Test#level2CacheTest3

/**
 * 當(dāng)將mapper xml中select元素的flushCache屬性置為true愕贡,會先清空二級緩存中的數(shù)據(jù),然后再去db中查詢數(shù)據(jù)巷屿,然后將數(shù)據(jù)再放到二級緩存中
 *
 * @throws IOException
 */
@Test
public void level2CacheTest3() throws IOException {
    String mybatisConfig = "demo9/mybatis-config1.xml";
    this.before(mybatisConfig);
    //先查詢2次getList1,getList1第二次會從二級緩存中拿到數(shù)據(jù)
    log.info("getList1查詢");
    for (int i = 0; i < 2; i++) {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<UserModel> userModelList1 = mapper.getList1(null);
            log.info("{}", userModelList1);
        }
    }
    //getList2的flushCache為true固以,所以查詢之前會先將對應(yīng)的二級緩存中的所有數(shù)據(jù)清空,所以二次都會訪問db
    log.info("getList2查詢");
    for (int i = 0; i < 2; i++) {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<UserModel> userModelList1 = mapper.getList2(null);
            log.info("{}", userModelList1);
        }
    }

    //二級緩存中沒有g(shù)etList1需要查找的數(shù)據(jù)了嘱巾,所以這次訪問getList1會去訪問db
    log.info("getList1查詢");
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
    }
}

運(yùn)行輸出

02:51.560 [main] INFO  c.j.chat05.demo9.Demo9Test - getList1查詢
02:51.842 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
02:51.871 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
02:51.891 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
02:51.892 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
02:51.905 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.5
02:51.906 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
02:51.906 [main] INFO  c.j.chat05.demo9.Demo9Test - getList2查詢
02:51.906 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.3333333333333333
02:51.907 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==>  Preparing: SELECT id,name,age FROM t_user 
02:51.907 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==> Parameters: 
02:51.909 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - <==      Total: 4
02:51.909 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
02:51.910 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.5
02:51.913 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==>  Preparing: SELECT id,name,age FROM t_user 
02:51.913 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - ==> Parameters: 
02:51.914 [main] DEBUG c.j.c.d.mapper.UserMapper.getList2 - <==      Total: 4
02:51.914 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
02:51.915 [main] INFO  c.j.chat05.demo9.Demo9Test - getList1查詢
02:51.915 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.4
02:51.915 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
02:51.915 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
02:51.917 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
02:51.917 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]

第一次查詢訪問db憨琳,第二次查詢從二級緩存中獲取了數(shù)據(jù),第3和第4查詢訪問的是getList2旬昭,這個查詢會清空二級緩存中的數(shù)據(jù)篙螟,直接去db中查詢,查詢4執(zhí)行完畢之后问拘,二級緩存中只有第四次查詢的數(shù)據(jù)遍略,第5次查詢?nèi)etList1中獲取數(shù)據(jù),此時二級緩存中沒有骤坐,所以直接去db中獲取了绪杏。

方式3:select元素的useCache置為false跳過二級緩存,但是不會情況二級緩存數(shù)據(jù)

新增一個select3查詢纽绍,將useCache置為false蕾久,如下:

<select id="getList3" useCache="false" resultType="com.javacode2018.chat05.demo9.model.UserModel"
        parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

對應(yīng)的用例代碼

/**
 * select元素的useCache置為false跳過二級緩存,但是不會情況二級緩存數(shù)據(jù)
 *
 * @throws IOException
 */
@Test
public void level2CacheTest4() throws IOException {
    String mybatisConfig = "demo9/mybatis-config1.xml";
    this.before(mybatisConfig);
    //第一次查詢訪問getList1顶岸,會將數(shù)據(jù)丟到二級緩存中
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
    }

    //getList3對應(yīng)的select的useCache為false腔彰,會跳過二級緩存叫编,所以會直接去訪問db
    for (int i = 0; i < 2; i++) {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<UserModel> userModelList1 = mapper.getList3(null);
            log.info("{}", userModelList1);
        }
    }

    //下面的查詢又去執(zhí)行了getList1辖佣,由于上面的第一次查詢也是訪問getList1會將數(shù)據(jù)放在二級緩存中,所以下面的查詢會從二級緩存中獲取到數(shù)據(jù)
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
    }
}

注意上面有4次查詢搓逾,第一次查詢訪問getList1卷谈,會將數(shù)據(jù)丟到二級緩存中,而第二三次查詢訪問的是getList3霞篡,getList3對應(yīng)的select的useCache為false世蔗,會跳過二級緩存端逼,所以會直接去訪問db,第四次查詢也是訪問getList1污淋,由于第一次查詢也是訪問getList1會將數(shù)據(jù)放在二級緩存中顶滩,所以第4次查詢直接從二級緩存中獲取到了數(shù)據(jù),運(yùn)行輸出:

13:38.454 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.0
13:38.852 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
13:38.898 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
13:38.929 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
13:38.930 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
13:38.941 [main] DEBUG c.j.c.d.mapper.UserMapper.getList3 - ==>  Preparing: SELECT id,name,age FROM t_user 
13:38.942 [main] DEBUG c.j.c.d.mapper.UserMapper.getList3 - ==> Parameters: 
13:38.945 [main] DEBUG c.j.c.d.mapper.UserMapper.getList3 - <==      Total: 4
13:38.945 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
13:38.946 [main] DEBUG c.j.c.d.mapper.UserMapper.getList3 - ==>  Preparing: SELECT id,name,age FROM t_user 
13:38.946 [main] DEBUG c.j.c.d.mapper.UserMapper.getList3 - ==> Parameters: 
13:38.952 [main] DEBUG c.j.c.d.mapper.UserMapper.getList3 - <==      Total: 4
13:38.952 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]
13:38.957 [main] DEBUG c.j.chat05.demo9.mapper.UserMapper - Cache Hit Ratio [com.javacode2018.chat05.demo9.mapper.UserMapper]: 0.5
13:38.957 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=張學(xué)友, age=50), UserModel(id=3, name=劉德華, age=50), UserModel(id=5100, name=路人, age=30)]

從輸出中可以看出前面3次查詢都訪問db了寸爆,最后一次查詢訪問的是二級緩存命中了數(shù)據(jù)礁鲁。4次查詢,第1次和第4次會訪問二級緩存赁豆,中間2次跳過了二級緩存仅醇,二級緩存命中了1次,所以最后一次輸出的命中率是0.5

總結(jié)

  1. 一二級緩存訪問順序:一二級緩存都存在的情況下魔种,會先訪問二級緩存析二,然后再訪問一級緩存,最后才會訪問db节预,這個順序大家理解一下
  2. 將mapper xml中select元素的flushCache屬性置為false叶摄,最終會清除一級緩存所有數(shù)據(jù),同時會清除這個select所在的namespace對應(yīng)的二級緩存中所有的數(shù)據(jù)
  3. 將mapper xml中select元素的useCache置為false安拟,會使這個查詢跳過二級緩存
  4. 總體上來說使用緩存可以提升查詢效率准谚,這塊知識掌握了,大家可以根據(jù)業(yè)務(wù)自行選擇

案例代碼

鏈接:https://pan.baidu.com/s/1vt-MAX3oJOu9gyxZAhKkbg 
提取碼:i8op

MyBatis系列

  1. MyBatis系列第1篇:MyBatis未出世之前我們那些痛苦的經(jīng)歷
  2. MyBatis系列第2篇:入門篇去扣,帶你感受一下mybatis獨特的魅力柱衔!
  3. MyBatis系列第3篇:Mybatis使用詳解(1)
  4. MyBatis系列第4篇:Mybatis使用詳解(2)
  5. Mybatis系列第5篇:Mapper接口多種方式傳參詳解、原理愉棱、源碼解析
  6. Mybatis系列第6篇:恕我直言唆铐,mybatis增刪改你未必玩得轉(zhuǎn)!
  7. Mybatis系列第7篇:各種查詢詳解
  8. Mybatis系列第8篇:自動映射,使用需謹(jǐn)慎奔滑!
  9. Mybatis系列第9篇:延遲加載艾岂、鑒別器、繼承怎么玩朋其?
  10. Mybatis系列第10篇:動態(tài)SQL王浴,這么多種你都會?
  11. Mybatis系列第11篇:類型處理器梅猿,這個你得會玩

更多好文章

  1. Java高并發(fā)系列(共34篇)
  2. MySql高手系列(共27篇)
  3. Maven高手系列(共10篇)
  4. 聊聊db和緩存一致性常見的實現(xiàn)方式
  5. 接口冪等性這么重要氓辣,它是什么?怎么實現(xiàn)袱蚓?

想了解更多精彩內(nèi)容請關(guān)注我的公眾號:路人甲Java

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钞啸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌体斩,老刑警劉巖梭稚,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異絮吵,居然都是意外死亡弧烤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蹬敲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扼褪,“玉大人,你說我怎么就攤上這事粱栖』敖剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵闹究,是天一觀的道長幔崖。 經(jīng)常有香客問我,道長渣淤,這世上最難降的妖魔是什么赏寇? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮价认,結(jié)果婚禮上嗅定,老公的妹妹穿的比我還像新娘。我一直安慰自己用踩,他們只是感情好渠退,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脐彩,像睡著了一般碎乃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惠奸,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天梅誓,我揣著相機(jī)與錄音,去河邊找鬼佛南。 笑死梗掰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嗅回。 我是一名探鬼主播及穗,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妈拌!你這毒婦竟也來了拥坛?” 一聲冷哼從身側(cè)響起蓬蝶,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤尘分,失蹤者是張志新(化名)和其女友劉穎猜惋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體培愁,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡著摔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了定续。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谍咆。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖私股,靈堂內(nèi)的尸體忽然破棺而出摹察,到底是詐尸還是另有隱情,我是刑警寧澤倡鲸,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布供嚎,位于F島的核電站,受9級特大地震影響峭状,放射性物質(zhì)發(fā)生泄漏克滴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一优床、第九天 我趴在偏房一處隱蔽的房頂上張望劝赔。 院中可真熱鬧,春花似錦胆敞、人聲如沸着帽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽启摄。三九已至,卻和暖如春幽钢,著一層夾襖步出監(jiān)牢的瞬間歉备,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工匪燕, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蕾羊,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓帽驯,卻偏偏與公主長得像龟再,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尼变,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345