mybatis整合redis二级缓存

mybatis默认开启了二级缓存功能,在mybatis主配置文件中,将cacheEnabled设置成false,则会关闭二级缓存功能

  <settings>
    <!--二级缓存默认开启,false关闭-->
    <setting name="cacheEnabled" value="false" />
    <!--mybatis日志打印到控制台-->
    <setting name="logImpl" value="STDOUT_LOGGING" />
  </settings>

mybatis框架虽然默认开启了二级缓存功能,但是并没有默认实现,也就是下面这句代码返回null, 然后走一级缓存

下面是配置Redis作为mybatis的二级缓存,代码如下:

(1)mybatis主配置文件mybatis-config.xml中添加如下配置

  <settings>
    <!--二级缓存默认开启,false关闭-->
    <setting name="cacheEnabled" value="true" />
    <!--mybatis日志打印到控制台,以便于观察-->
    <setting name="logImpl" value="STDOUT_LOGGING" />
  </settings>

(2)FemaleMapper.xml 配置如下

<mapper namespace="qinfeng.zheng.FemaleMapper">
    <select id="getFemaleById" resultType="qinfeng.zheng.entity.Female" parameterType="int">
      SELECT id,name FROM tb_female where id = #{id};
    </select>
    <cache eviction="LRU" type="qinfeng.zheng.RedisCache"/>
</mapper>

LRU是mybatis默认的过期策略

(3)qinfengzhengRedisCache.java

import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;

/**
 * 使用redis的Hash结构
 */
public class RedisCache implements Cache {

    private String id;
    private JedisPool jedisPool;
    private static int EXPIRE_TIME = 1000 * 1000;

    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPool = new JedisPool(jedisPoolConfig, "192.168.79.221", 6379, 3000, "123456");
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        execute(jedis -> {
            try {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                ObjectOutputStream outputStream = new ObjectOutputStream(stream);
                outputStream.writeObject(value);
                byte[] idBytes = id.getBytes();
                jedis.hset(idBytes, key.toString().getBytes(), stream.toByteArray());
                // 设置一个过期时间。。
                if (jedis.ttl(idBytes) == -1) {
                    jedis.expire(idBytes, EXPIRE_TIME);
                }
            } catch (Exception e) {
                throw new RuntimeException("put object to redis error!", e);
            }
            return null;
        });
    }

    @Override
    public Object getObject(Object key) {
        return execute(jedis -> {
            try {
                byte[] bytes = jedis.hget(id.getBytes(), key.toString().getBytes());
                if (bytes == null) {
                    return null;
                }
                ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
                ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                return objectInputStream.readObject();
            } catch (Exception e) {
                throw new RuntimeException("get data from redis error!", e);
            }
        });
    }

    @Override
    public Object removeObject(Object key) {
        return execute(jedis -> jedis.hdel(id.getBytes(), key.toString().getBytes()));
    }

    @Override
    public void clear() {
        execute(jedis -> jedis.del(id.getBytes()));  //因为id是String类型,所以直接jedis.del(id)也行
    }

    @Override
    public int getSize() {
        return (Integer) execute(jedis -> {
            Map<byte[], byte[]> map = jedis.hgetAll(id.getBytes());
            return map.size();
        });
    }

    private Object execute(RedisCallBack redisCallBack) {
        try (Jedis jedis = jedisPool.getResource()) {
            return redisCallBack.callBack(jedis);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
public interface RedisCallBack {
    Object callBack(Jedis jedis);
}

使用redis作为mybatis的二级缓存框架,最主要就是实现mybatis提供的spi接口--Cache即可,然后实现putObject,getObject这两个主要的方法即可。

除此之外,记得pom.xml文件中添加jedis依赖

      <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

(4)修改测试代码

public class V1Test {
    public static void main(String[] args) {
        try (InputStream is = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            FemaleMapper femaleMapper = sqlSession.getMapper(FemaleMapper.class);
            Female female = femaleMapper.getFemaleById(1);
            System.out.println(female);
            System.out.println("-----------分隔符----------------------------------");
            SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
            FemaleMapper femaleMapper2 = sqlSession2.getMapper(FemaleMapper.class);
            Female female2 = femaleMapper2.getFemaleById(1);
            System.out.println(female2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第一次运行:

Opening JDBC Connection
Created connection 331510866.
==>  Preparing: SELECT id,name FROM tb_female where id = ?; 
==> Parameters: 1(Integer)
<==    Columns: id, name
<==        Row: 1, soga
<==      Total: 1
Female(id=1, name=soga)
-----------分隔符----------------------------------
Opening JDBC Connection
Created connection 1316061703.
==>  Preparing: SELECT id,name FROM tb_female where id = ?; 
==> Parameters: 1(Integer)
<==    Columns: id, name
<==        Row: 1, soga
<==      Total: 1
Female(id=1, name=soga)

哈哈,貌似还是查库两次,redis缓存好像没有起到作用。

事实上,mybatis框架为二级缓存提供了一个缓冲Map, 需要在调用commit方法之后,才会将缓冲区Map中的数据写入到redis中。

我们修改一下测试代码,然后再运行观察结果

public class V1Test {
    public static void main(String[] args) {
        try (InputStream is = Resources.getResourceAsStream("mybatis-config.xml")) {
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            FemaleMapper femaleMapper = sqlSession.getMapper(FemaleMapper.class);
            Female female = femaleMapper.getFemaleById(1);
            System.out.println(female);
            sqlSession.commit();
            System.out.println("-----------分隔符----------------------------------");
            SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
            FemaleMapper femaleMapper2 = sqlSession2.getMapper(FemaleMapper.class);
            Female female2 = femaleMapper2.getFemaleById(1);
            System.out.println(female2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

哎哎哎,报了个错,记得Female 需要序列化

Opening JDBC Connection
Created connection 490150701.
==>  Preparing: SELECT id,name FROM tb_female where id = ?; 
==> Parameters: 1(Integer)
<==    Columns: id, name
<==        Row: 1, soga
<==      Total: 1
Female(id=1, name=soga)
-----------分隔符----------------------------------
Cache Hit Ratio [qinfeng.zheng.FemaleMapper]: 0.5
Female(id=1, name=soga)

请看,这次只查了一个库,第2冲直接缓存命中、

我们再运行一次刚才的代码

Cache Hit Ratio [qinfeng.zheng.FemaleMapper]: 1.0
Female(id=1, name=soga)
-----------分隔符----------------------------------
Cache Hit Ratio [qinfeng.zheng.FemaleMapper]: 1.0
Female(id=1, name=soga)

两次都缓存命中!!

再看一下redis:

好了, mybatis整合redis为二级缓存到此完毕,下章分析一下它的源码实现!

原文地址:https://www.cnblogs.com/z-qinfeng/p/11920606.html