MyBatis 緩存
1. 前言
頻繁地查詢必然會給數(shù)據(jù)庫帶來巨大的壓力,為此 MyBatis 提供了豐富的緩存功能。緩存可以有效的提升查詢效率、緩解數(shù)據(jù)庫壓力,提高應(yīng)用的穩(wěn)健性。
MyBatis 的緩存有兩層,默認(rèn)情況下會開啟一級緩存,并提供了開啟二級緩存的配置。本小節(jié)我們將一起學(xué)習(xí) MyBatis 的緩存,充分地了解和使用它。
2. 一級緩存
MyBatis 一級緩存是默認(rèn)開啟的,緩存的有效范圍是一個會話內(nèi)。一個會話內(nèi)的 select 查詢語句的結(jié)果會被緩存起來,當(dāng)在該會話內(nèi)調(diào)用 update、delete 和 insert 時,會話緩存會被刷新,以前的緩存會失效。
2.1 使用一級緩存
下面,我們以一個簡單的例子來看看 MyBatis 的一級緩存是如何工作的。
package com.imooc.mybatis.cache;
import com.imooc.mybatis.mapper.UserMapper;
import com.imooc.mybatis.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
@SuppressWarnings({"Duplicates"})
public class CacheTest1 {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
// 得到 mapper
UserMapper userMapper = session.getMapper(UserMapper.class);
// 查詢得到 user1
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
// 查詢得到 user2
User user2 = userMapper.selectUserById(1);
// 通過 == 判斷 user1 和 user2 是否指向同一內(nèi)存區(qū)間
System.out.println(user1 == user2);
session.commit();
session.close();
}
}
結(jié)果:
User{id=1, username='peter-gao', age=180, score=1000}
true
在這個例子中,我們連續(xù)兩次調(diào)用了 userMapper 的 selectUserById 方法,但是在程序輸出中,user1 和 user2 卻指向了同一塊內(nèi)存區(qū)域。這就是 MyBatis 緩存的作用,當(dāng)?shù)诙握{(diào)用查詢時,MyBatis 沒有查詢數(shù)據(jù)庫而是直接從緩存中拿到了數(shù)據(jù)。
2.2 棄用一級緩存
2.2.1 select 配置關(guān)閉緩存
select 默認(rèn)會啟用一級緩存,我們也可通過配置來關(guān)閉掉 select 緩存。
如下,我們通過 flushCache 屬性來關(guān)閉 select 查詢的緩存。
<select id="selectUserById" flushCache="true" parameterType="java.lang.Integer"
resultType="com.imooc.mybatis.model.User">
SELECT * FROM imooc_user WHERE id = #{id}
</select>
再次運行程序,結(jié)果如下:
User{id=1, username='peter-gao', age=180, score=1000}
false
此時 user1 與 user2 不再指向同一內(nèi)存區(qū),緩存失效了。
2.2.2 調(diào)用 insert、update、delete 刷新緩存
一般情況下,我們都推薦開啟 select 的緩存,因為這會節(jié)省查詢時間。當(dāng)然在一個會話中,調(diào)用 insert、update、delete 語句時,會話中的緩存也會被刷新。
如下:
UserMapper userMapper = session.getMapper(UserMapper.class);
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("cache test");
user.setAge(10);
user.setScore(100);
userMapper.insertUser(user);
User user2 = userMapper.selectUserById(1);
System.out.println(user1 == user2);
session.commit();
session.close();
User{id=1, username='peter', age=18, score=100}
false
在第一個查詢調(diào)用前,我們先進(jìn)行了一次 insert 操作,此時會刷新緩存,user1 和 user2 又沒有指向同一處內(nèi)存。
3. 二級緩存
MyBatis 二級緩存默認(rèn)關(guān)閉,我們可以通過簡單的設(shè)置來開啟二級緩存。二級緩存的有效范圍為一個 SqlSessionFactory 生命周期,絕大多數(shù)情況下,應(yīng)用都會只有一個 SqlSessionFactory,因此我們可以把二級緩存理解為全局緩存。
3.1 全局可用
在 MyBatis 全局配置文件中,即 mybatis-config.xml 文件,二級緩存可由 settings 下的 cacheEnabled 屬性開啟。如下:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
當(dāng)打開 cacheEnabled 屬性后,二級緩存全局可用。
TIPS:注意,這里是可用,cacheEnabled 的默認(rèn)值其實也是 true,即全局可用,由于二級緩存需要對 mapper 配置后才真正生效,簡單來說就是雙層開關(guān)。當(dāng)將其設(shè)置為 false 后,則全局關(guān)閉,mapper 中即使配置了,二級緩存也會失效。
3.2 mapper 中開啟
3.2.1 xml 開啟
在二級緩存全局可用的情況下,mapper 才可通過 cache 配置開啟二級緩存。如,在 UserMapper.xml 文件中開啟二級緩存:
<cache/>
這種情況下,緩存的行為如下:
- mapper 下的所有 select 語句會被緩存;
- mapper 下的 update,insert,delete 語句會刷新緩存;
- 使用 LRU 算法來回收對象;
- 最大緩存 1024 個對象;
- 緩存可讀、可寫。
- 緩存不會根據(jù)時間來刷新。
cache 提供了諸多屬性來修改緩存行為,示例如下:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個例子下的緩存使用 FIFO 算法來回收對象,并每隔 60 秒刷新一次,最多緩存 512 個對象,且緩存只可讀。
cache 有 4 個屬性可配置,從而改變緩存的行為。
屬性 | 描述 |
---|---|
eviction | 回收策略,默認(rèn) LRU,可選擇的有 FIFO(先進(jìn)先出),SOFT(軟引用),WEAK(弱引用) |
flushInterval | 刷新時間 |
size | 最多緩存對象數(shù) |
readOnly | 是否只讀 |
3.2.2 注解開啟
如果你不使用 mapper.xml 文件,也可以使用注解來開啟。
如下:
package com.imooc.mybatis.mapper;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.cache.decorators.FifoCache;
@Mapper
@CacheNamespace(
eviction = FifoCache.class,
flushInterval = 60000,
size = 512,
readWrite = false
)
public interface BlogMapper {
}
注解 CacheNamespace 的配置與 xml 配置保持一致,唯一區(qū)別在于若使用注解,那么 eviction 屬性需直接給出緩存實現(xiàn)類。
3.3 緩存共享
3.3.1 xml 共享
有時候,我們想在不同的 mapper 中共享緩存,為了解決這類問題,MyBatis 提供了 cache-ref 配置。
使用也很簡單,如下:
<cache-ref namespace="com.imooc.mybatis.mapper.UserMapper"/>
mapper 由 namespace 來唯一標(biāo)識,因此只需在另一個 mapper 文件中添加上 cache-ref 配置,并加上相應(yīng)的 namespace 即可。
這樣當(dāng)前的 mapper 可以共享來自 UserMapper 的緩存。
3.3.2 注解共享
同樣的,我們也可以使用注解來共享緩存。
如下:
@CacheNamespaceRef(UserMapper.class)
public interface BlogMapper {
}
這里,BlogMapper 共享了 UserMapper 的緩存。
TIPS: 注意,CacheNamespaceRef 與 CacheNamespace 不能共存,既然選擇了共享就不能再獨立開辟緩存區(qū)了。
4. 小結(jié)
- MyBatis 的一級緩存默認(rèn)可用,有效范圍小,不會影響到其它會話,因此無特殊情況,不推薦丟棄一級緩存。
- MyBatis 二級緩存默認(rèn)使用程序內(nèi)存緩存,但這顯然不夠安全,一般情況下我們都推薦使用 Redis 等專業(yè)的緩存。