Mybatis基础 -- 缓存

2021-01-25  本文已影响0人  Travis_Wu

一、Mybatis 缓存的概念

缓存就是内存中的数据,常常用来对数据库查询结果的保存,使用缓存,我们可以避免频繁地与数据库进行交互,进而提高响应速度。
mybatis 也提供了对缓存的支持,分为一级缓存和二级缓存

二、一级缓存的概念

1. 概念
一级缓存是 SqlSession 级别的缓存,在操作数据库时,需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的 SqlSession 之间的缓存数据区域(HashMap)是互补影响的。
2. 执行顺序
首先去一级缓存中查询,如果有直接返回,如果没有则查询数据库同时将查询出来的结果存到一级缓存中去
3. 数据结构
HashMap<key:statementId,params,boundSql,rowBounds ;value:查询出来的结果>
4. 缓存刷新的时机
(1)sqlSession 去执行 commit 操作(执行插⼊、更新、删除),则会清空 SqlSession 中的 ⼀级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
(2)手动刷新缓存:sqlSession.clearCache();

三、一级缓存的原理与源码分析

四、二级缓存的原理与源码分析

二级缓存的原理和一级缓存原理一样,第⼀次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是⼀级缓存是基于 sqlSession 的,而二级缓存是基于mapper文件的 namespace 的,也 就是说多个 sqlSession 可以共享⼀个mapper中的二级缓存区域,并且如果两个mapper的 namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。


五、二级缓存整合Redis

mybatis 自带的二级缓存是单服务器工作,无法实现分布式缓存。所以需要引入 redis 进行对 mybatis 二级缓存的升级。

  1. pom文件
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-redis</artifactId>
        <version>1.0.0-beta2</version>
    </dependency>
    
  2. 配置文件
    <?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.wujun.mapper.UserMapper"> 
       <cache type="org.mybatis.caches.redis.RedisCache" />
       <select id="findAll" resultType="com.lagou.pojo.User" useCache="true">
           select * from user
       </select>
    </mapper>
    
  3. redis.properties
    redis.host=localhost
    redis.port=6379
    redis.connectionTimeout=5000
    redis.password=
    redis.database=0
    
  4. 源码分析
    RedisCache 和普遍实现 Mybatis 的缓存方案大同小异,无非是实现 Cache 接口,并使用 jedis 操作缓存,不过该项目在设计细节上有⼀些区别
    public final class RedisCache implements Cache {
        public RedisCache(final String id) {
            if (id == null) {
                throw new IllegalArgumentException("Cache instances require anID");
            }
            this.id = id;
            RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
            pool = new JedisPool(redisConfig,
                                 redisConfig.getHost(), 
                                 redisConfig.getPort(),
                                 redisConfig.getConnectionTimeout(),
                                 redisConfig.getSoTimeout(), 
                                 redisConfig.getPassword(),
                                 redisConfig.getDatabase(), 
                                 redisConfig.getClientName()
                   );
        }
    }
    
    RedisCache 在 mybatis 启动的时候,由 myBatis 的 CacheBuilder 创建,创建的方式很简单,就是调用 RedisCache 的带有 String 参数的构造方法,即RedisCache(String id);而在 RedisCache 的构造方法中,调用了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool。
    RedisConfig 类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看⼀下 RedisConfig 的
    属性:
    public class RedisConfig extends JedisPoolConfig {
        private String host = Protocol.DEFAULT_HOST;
        private int port = Protocol.DEFAULT_PORT;
        private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
        private int soTimeout = Protocol.DEFAULT_TIMEOUT;
        private String password;
        private int database = Protocol.DEFAULT_DATABASE;
        private String clientName;
    }
    
    RedisConfig 对象是由 RedisConfigurationBuilder 创建的,核心的方法就是 parseConfiguration 方法,该方法从 classpath 中读取⼀个 redis.properties 文件,
    并将该配置文件中的内容设置到 RedisConfig 对象中,并返回
    public RedisConfig parseConfiguration(ClassLoader classLoader) {
        Properties config = new Properties();
        InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);
        if (input != null) {
            try {
                config.load(input);
            } catch (IOException e) {
                throw new RuntimeException("An error occurred while reading classpath property '" + redisPropertiesFilename + "', see nested exceptions", e);
            } finally {
                try {
                    input.close();
                } catch (IOException e) {
                    // close quietly
                }
            }
        }
        RedisConfig jedisConfig = new RedisConfig();
        setConfigProperties(config, jedisConfig);
        return jedisConfig; 
    }
    
    接下来,就是 RedisCache 使用 RedisConfig 类创建完成 jedisPool,在 RedisCache 中实现了⼀个简单的模板方法,用来操作Redis
    private Object execute(RedisCallback callback) {
        Jedis jedis = pool.getResource();
        try {
            return callback.doWithRedis(jedis);
        } finally {
            jedis.close();
        } 
    }
    
    模板接口为 RedisCallback,这个接口中就只需要实现了⼀个 doWithRedis 方法而已
    public interface RedisCallback {
         Object doWithRedis(Jedis jedis);
    }
    
    接下来看看 Cache 中最重要的两个方法:putObject 和 getObject,通过这两个方法来查看 mybatis-redis 储存数据的格式
    @Override
    public void putObject(final Object key, final Object value) {
         execute(new RedisCallback() {
             @Override
             public Object doWithRedis(Jedis jedis) {
                 jedis.hset(id.toString().getBytes(), 
                            key.toString().getBytes(), 
                            SerializeUtil.serialize(value));
                 return null;
             }
         });
    }
    
    @Override
    public Object getObject(final Object key) {
        return execute(new RedisCallback() {
            @Override
            public Object doWithRedis(Jedis jedis) {
                return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));
            }
        });
    }
    
    可以很清楚的看到,mybatis-redis 在存储数据的时候,是使用的 hash 结构,把 cache 的 id 作为这个 hash 的 key (cache的 id 在 mybatis 中就是 mapper 的namespace)。这个 mapper 中的查询缓存数据作为 hash
    的 field,需要缓存的内容直接使用 SerializeUtil 存储,SerializeUtil 和其他的序列化类差不多,负责对象的序列化和反序列化
上一篇 下一篇

猜你喜欢

热点阅读