緩存的定義
緩存一般是orm框架會提供的功能,目的是提升查詢效率和減少數(shù)據(jù)庫的壓力华匾。Mybatis有1級緩存和2級緩存鳞尔,并且還有集成第三方的緩存工具比如說 redis。
一級緩存
一級緩存是默認開啟的不需要配置亚脆。同時因為它是sqlSession級別的也叫會話緩存。在同一個會話里面盲泛,多次執(zhí)行相同的 SQL 語句濒持,會直接從內(nèi)存取到緩存的結(jié)果,不會再發(fā)送 SQL 到數(shù)據(jù)庫寺滚。但是不同的會話里面柑营,即使執(zhí)行的 SQL 一模一樣(通過一個 Mapper 的同一個方法的相同參數(shù)調(diào)用),也不能使用到一級緩存村视。
命中緩存
@Test
public void selectListCacheOne() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.selectList();
UserMapper userMapper2 = sqlSession1.getMapper(UserMapper.class);
List<User> userList2 = userMapper2.selectList();
}
返回結(jié)果是:
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5ccddd20]
==> Preparing: select * from user
==> Parameters:
interface java.util.List
上面的代碼同一個sqlSession中執(zhí)行2次查詢 只打印出了一次sql語句官套。可見第2次查詢命中了緩存。
未命中緩存
@Test
public void selectListNoneCacheOne() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.selectList();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList2 = userMapper2.selectList();
}
返回結(jié)果
==> Preparing: select * from user
==> Parameters:
interface java.util.List
==> Preparing: select * from user
==> Parameters:
interface java.util.List
2個sqlsession執(zhí)行了2次查詢而sql語句打印了2次虏杰。由此可見不同不同的sqlsession查詢同一個sql不會命中緩存。
一級緩存的弊端
我們知道一級緩存是sqlsession級別的勒虾。那么我在方法1中用sqslsession查詢語句
同時打上斷點纺阔。在這個時候執(zhí)行另一個線程插入一條數(shù)據(jù)
@Test
public void mybatisMapperInsert() {
//4 獲取sqlSession SqlSession 提供了在數(shù)據(jù)庫執(zhí)行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執(zhí)行已映射的 SQL 語句
SqlSession sqlSession = sqlSessionFactory.openSession();
//5 獲取sql 通過代理模式 代理了一個映射器
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User userParam = new User();
userParam.setAge(1);
userParam.setSexState(SexEnum.MAN);
userParam.setSexStateOrigin(SexEnumOrigin.MAN);
userMapper.insert(userParam);
sqlSession.commit();
}
然后這個時候再次執(zhí)行方法1中的線程修然。
可以看到在同一個sqlsession中命中了緩存笛钝,第2次查詢結(jié)果是還是13個。但是數(shù)據(jù)庫中明明是14條數(shù)據(jù)愕宋。所以可見多線程的情況下 一級緩存會出現(xiàn)臟讀的情況玻靡。 那么怎么避免呢?用2級緩存
二級緩存
二級緩存是用來解決一級緩存不能跨會話共享的問題的中贝,范圍是 namespace 級別 的囤捻,可以被多個 SqlSession 共享(只要是同一個接口里面的相同方法,都可以共享)邻寿, 生命周期和應用同步蝎土。作為一個作用范圍更廣的緩存,它肯定是在 SqlSession 的外層绣否,否則不可能被多個 SqlSession 共享誊涯。而一級緩存是在 SqlSession 內(nèi)部的,也就是只有取不到二級緩存的情況下才到一個會話中去取一級緩存蒜撮。
開啟二級緩存
在想要開啟二級緩存的Mapper中添加表橋<cache/>
<mapper namespace="com.mybatis.demo.mybatis.mapper.UserMapper">
<cache eviction="LRU" flushInterval="600000" size="1024" readOnly="true"/>
?
屬性 | 含義 | 取值 | |
---|---|---|---|
type | 2級緩存的實現(xiàn)類 | 默認是需要實現(xiàn) Cache 接口暴构,默認是 PerpetualCache | |
size | 最多緩存對象數(shù)量 | 默認是1024 | |
eviction | 回收策略 | LRU(默認) FIFO SOFT WEAK | |
flushInterval | 定時自動清理緩存 | 自動刷新時間,單位 ms段磨,未配置時只有調(diào)用時刷新 | |
readOnly | 是否只讀 | false 默認 crud操作會更新緩存 true:只讀緩存 性能提高 | |
blocking | 是否使用可重入鎖實現(xiàn) 緩存的并發(fā)控制 | true取逾,會使用 BlockingCache 對 Cache 進行裝飾 默認 false | |
Mapper.xml 配置了<cache>之后,select()會被緩存薇溃。update()菌赖、delete()、insert() 會刷新緩存沐序。
如果某些查詢方法對數(shù)據(jù)的實時性要求很高琉用,不需要二級緩存,怎么辦? 我們可以在單個 Statement ID 上顯式關(guān)閉二級緩存(默認是 true):
<select id="selectList" resultMap="BaseResultMap" useCache="false">
select * from user
</select>
這樣子在命名空間com.mybatis.demo.mybatis.mapper.UserMapper就開啟了一個緩存
例子
@Test
public void selectListCacheTwoUpdate() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.selectList();
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList2= userMapper2.selectList();
sqlSession2.close();
}
返回結(jié)果
Cache Hit Ratio [com.mybatis.demo.mybatis.mapper.UserMapper]: 0.5
打印出這個信息代表了命中緩存策幼。
集成redis緩存
mybatis提供了集成redis的緩存 引入maven
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
mapper中添加緩存的type
<cache type="org.mybatis.caches.redis.RedisCache" flushInterval="60000" size="512" />
執(zhí)行的效果與上面的一樣
spring+redis+mybatis緩存
mybatis雖然提供了redis緩存但是沒有提供對spring的支持邑时。所以想要在spring緩存下使用需要自己實現(xiàn)對myabtis緩存的支持需要集成接口 org.apache.ibatis.cache.Cache
@Component
public class MybatisRedisCache implements Cache {
/**
* ID
*/
private String id;
/**
* 集成redisTemplate
*/
private static RedisTemplate redisTemplate;
public MybatisRedisCache() {
}
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
} else {
this.id = id;
}
}
@Override
public String getId() {
return this.id;
}
@Override
public int getSize() {
try {
Long size = redisTemplate.opsForHash().size(this.id.toString());
return size.intValue();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
@Override
public void putObject(final Object key, final Object value) {
try {
redisTemplate.opsForHash().put(this.id.toString(), key.toString(), value);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getObject(final Object key) {
try {
Object hashVal = redisTemplate.opsForHash().get(this.id.toString(), key.toString());
return hashVal;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Object removeObject(final Object key) {
try {
redisTemplate.opsForHash().delete(this.id.toString(), key.toString());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void clear() {
try {
redisTemplate.delete(this.id.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "MybatisRedisCache {" + this.id + "}";
}
public static void setRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.redisTemplate = redisTemplate;
}
}
private static RedisTemplate redisTemplate 為了集成spring redis,實現(xiàn)的緩存類中需要靜態(tài)注入redisTemplate特姐。通過靜態(tài)方法
public class RedisCacheTransfer {
@Autowired
public void serRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.setRedisTemplate(redisTemplate);
}
@Bean
public RedisCacheTransfer redisCacheTransfer(RedisTemplate redisTemplate) {
RedisCacheTransfer redisCacheTransfer = new RedisCacheTransfer();
redisCacheTransfer.serRedisTemplate(redisTemplate);
return redisCacheTransfer;
}
結(jié)果
cache 有多個實例晶丘,采用的裝飾器模式
2級緩存 要等session關(guān)閉才會放入到緩存中 這是為什么