缓存是在计算机内存上保存的数据,在读取的时候无需再从磁盘读入,因此具备快速读取和使用的特点,如果缓存命中率高,那么可以极大地提高系统的性能。如果缓存命中率很低,那么缓存就不存在使用的意义,所以使用缓存的关键在于存储内容访问的命中率
在默认情况下,MyBatis 值开启一级缓存,一级缓存只想对于同一个 SqlSession 而言。因此,在参数和 SQL 相同的情况下,使用同一个 SqlSession 对象调用同一个 Mapper 的方法,只会执行一次 SQL,因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession都只会取出当前缓存的数据,而不会再次发送 SQL 到数据库。但是若使用不同的 SqlSession 对象,由于不同的 SqlSession 都是相互隔离的,所以用相同的 Mapper、参数和方法,还是会发送 SQL 到数据库中去执行
二级缓存使得缓存在 SqlSession 层面上能够提供给各个 SqlSession 对象共享。默认二级缓存是不开启的,要实现二级缓存需要 POJO 是可序列化的,就是实现 Serializable 接口,此时只需要在 mapper 映射 xml文件配置添加 节点 就可以开启缓存,例如如下代码 :
<
mapper
namespace
="main.mapper.StudentMapper"
>
<
cache
eviction
="LRU"
flushInterval
="60000"
size
="1024"
readOnly
="true"
/>
mapper
>
eviction : 缓存的淘汰算法,缺省值是LRU,可选值有"LRU"(Least Recently Used最近最少使用)
"FIFO"(先进先出)
"SOFT"(软引用,移除基于垃圾回收器状态和软件引用规则对象)
"WEAK"(弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象)
flashInterval : 指缓存过期时间,单位为毫秒,60000即为60秒,缺省值为空,即只要容量足够,永不过期
size : 指缓存多少个对象,默认值为1024
readOnly : 是否只读,如果为true,则所有相同的sql语句返回的是同一个对象(有助于提高性能,但并发操作同一条数据时,可能不安全)缓存数据只能读不能修改,如果设置为false,则相同的sql,后面访问的是cache的clone副本,默认为 false
在每条单独的sql语句上,还可以有局部设置,比如 :
<
select
id
="findByName"
parameterType
="string"
resultMap
="BaseResultMap"
useCache
="true"
flushCache
="true"
>
SELECT
<
include
refid
="Base_Column_List"
/>
FROM cn_user
WHERE cn_user_name=#{name}
LIMIT 0,1
select
>
useCache="false"表示该select语句不使用缓存(即使xml最开头的全局cache启用)默认情况下,如果全局开启了缓存,insert/update/delete成功后,会自动刷新相关的缓存项
flushCache表示插入数据后是否刷新缓存
注 : 在 MyBatis与 Hibernate混用时,由于 Mybatis与 Hibernate的缓存是无关的,如果用 Mybatis做select查询,用 Hibernate做insert/update/delete,Hibernate对数据的修改,并不会刷新mybatis的缓存
自定义缓存
系统缓存是 MyBatis 应用机器上的本地缓存,但是在大型服务器上,会使用不同的缓存服务器,此时可以定制缓存,如 Redis缓存
MyBatis 使用 Redis 二级缓存案例
定义 iBatis 缓存类
import
com.chenshun.studyapp.controller.user.LoginController;
import
org.apache.ibatis.cache.Cache;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
import
org.springframework.data.redis.cache.RedisCache;
import
redis.clients.jedis.Jedis;
import
redis.clients.jedis.JedisPool;
import
redis.clients.jedis.JedisPoolConfig;
import
java.util.concurrent.locks.ReadWriteLock;
import
java.util.concurrent.locks.ReentrantReadWriteLock;
public class
MyCache
implements
Cache {
private static
Logger
logger
= LoggerFactory.
getLogger
(MyCache.
class
);
private
Jedis
redisClient
=
createClient
();
private final
ReadWriteLock
readWriteLock
=
new
ReentrantReadWriteLock();
private
String
id
;
private
String
host
;
public
MyCache(
final
String id) {
if
(id ==
null
) {
throw new
IllegalArgumentException(
"Cache instances require an ID"
);
}
logger
.debug(
">>>>>>>>>>>>>>>>>>>>>>>>MybatisRedisCache:id="
+ id);
this
.
id
= id;
}
@Override
public
String getId() {
// 获取缓存编号
return
id
;
}
@Override
public void
putObject(Object key, Object value) {
// 保存key值缓存对象
logger
.debug(
">>>>>>>>>>>>>>>>>>>>>>>>putObject:"
+ key +
"="
+ value);
redisClient
.set(SerializeUtil.
serialize
(key.toString()), SerializeUtil.
serialize
(value));
}
@Override
public
Object getObject(Object key) {
// 获取key值缓存对象
Object value = SerializeUtil.
unserialize
(
redisClient
.get(SerializeUtil.
serialize
(key.toString())));
logger
.debug(
">>>>>>>>>>>>>>>>>>>>>>>>getObject:"
+ key +
"="
+ value);
return
value;
}
@Override
public
Object removeObject(Object key) {
// 删除key值缓存对象
return
redisClient
.expire(SerializeUtil.
serialize
(key.toString()),
0
);
}
@Override
public void
clear() {
// 清空缓存
redisClient
.flushDB();
}
@Override
public synchronized int
getSize() {
// 获取缓存对象大小
return
Integer.
valueOf
(
redisClient
.dbSize().toString());
}
@Override
public
ReadWriteLock getReadWriteLock() {
// 获取缓存的读写锁
return
readWriteLock
;
}
public void
setHost(String host) {
this
.
host
= host;
}
protected static
Jedis createClient() {
try
{
JedisPool pool =
new
JedisPool(
new
JedisPoolConfig(),
"localhost"
);
Jedis jedis = pool.getResource();
// jedis.auth("jintoufs");
return
jedis;
}
catch
(Exception e) {
e.printStackTrace();
}
throw new
RuntimeException(
"初始化连接池错误"
);
}
}
定义 LoggingCache 继承类
import
org.apache.ibatis.cache.decorators.LoggingCache;
public class
LoggingRedisCache
extends
LoggingCache
{
public
LoggingRedisCache(String id) {
super
(
new
MyCache(id));
}
}
在 mapper 的 xml 文件中设置 cache 标签
xml version
="1.0"
encoding
="UTF-8"
?>
mapper
PUBLIC
"-//mybatis.org//DTD Mapper 3.0//EN"
" http://mybatis.org/dtd/mybatis-3-mapper.dtd
"
>
<
mapper
namespace
="com.chenshun.studyapp.dao.UserMapper"
>
<
cache
type
="com.chenshun.studyapp.cache.LoggingRedisCache"
>
<
property
name
="host"
value
="localhost"
/>
cache
>
<
resultMap
id
="BaseResultMap"
type
="com.chenshun.studyapp.domain.User"
>
<
id
column
="cn_user_id"
property
="cnUserId"
jdbcType
="VARCHAR"
/>
<
result
column
="cn_user_name"
property
="cnUserName"
jdbcType
="VARCHAR"
/>
<
result
column
="cn_user_password"
property
="cnUserPassword"
jdbcType
="VARCHAR"
/>
<
result
column
="cn_user_token"
property
="cnUserToken"
jdbcType
="VARCHAR"
/>
<
result
column
="cn_user_desc"
property
="cnUserDesc"
jdbcType
="LONGVARCHAR"
/>
resultMap
>
<
sql
id
="Base_Column_List"
>
cn_user_id, cn_user_name, cn_user_password, cn_user_token, cn_user_desc
sql
>
<
select
id
="findByName"
parameterType
="string"
resultMap
="BaseResultMap"
useCache
="true"
>
SELECT
<
include
refid
="Base_Column_List"
/>
FROM cn_user
WHERE cn_user_name=#{name}
LIMIT 0,1
select
>
mapper
>
使用 Junit 执行测试
@RunWith
(SpringJUnit4ClassRunner.
class
)
// 基于 JUnit4 的Spring测试框架
@ContextConfiguration
(locations = {
"/spring/applicationContext.xml"
,
"/spring/spring-mvc.xml"
})
// 启动 Spring 容器,由 Spring 提供
public class
CacheTest {
@Resource
private
UserMapper
userMapper
;
@Test
public void
findByNameTest() {
System.
out
.println(
"++++++++++++++++++++++++ 第一次查询数据 +++++++++++++++++++++++++++++"
);
userMapper
.findByName(
"demo"
);
System.
out
.println(
"++++++++++++++++++++++++ 第二次查询数据 +++++++++++++++++++++++++++++"
);
userMapper
.findByName(
"demo"
);
System.
out
.println(
"++++++++++++++++++++++++ 第三次查询数据 +++++++++++++++++++++++++++++"
);
userMapper
.findByName(
"demo"
);
System.
out
.println(
"++++++++++++++++++++++++ 第四次查询数据 +++++++++++++++++++++++++++++"
);
userMapper
.findByName(
"demo"
);
}
}
在控制台看到最终的执行结果,只会在第一次执行的时候调用 SQL,之后将不会调用