MyBatis 缓存 详解

缓存是在计算机内存上保存的数据,在读取的时候无需再从磁盘读入,因此具备快速读取和使用的特点,如果缓存命中率高,那么可以极大地提高系统的性能。如果缓存命中率很低,那么缓存就不存在使用的意义,所以使用缓存的关键在于存储内容访问的命中率

在默认情况下,MyBatis 值开启一级缓存,一级缓存只想对于同一个  SqlSession 而言。因此,在参数和 SQL 相同的情况下,使用同一个 SqlSession 对象调用同一个 Mapper 的方法,只会执行一次 SQL,因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession都只会取出当前缓存的数据,而不会再次发送 SQL 到数据库。但是若使用不同的 SqlSession 对象,由于不同的 SqlSession 都是相互隔离的,所以用相同的 Mapper、参数和方法,还是会发送 SQL 到数据库中去执行

二级缓存使得缓存在 SqlSession 层面上能够提供给各个 SqlSession 对象共享。默认二级缓存是不开启的,要实现二级缓存需要 POJO 是可序列化的,就是实现 Serializable 接口,此时只需要在 mapper 映射 xml文件配置添加 <cache>节点 就可以开启缓存,例如如下代码 :
< 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" ?>
<!DOCTYPE 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,之后将不会调用


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值