Level 2 Cache Integration with Redis

Currently, the level 2 cache we implement is per service. At this time, the cache is on a single server. If we want to implement distributed caching, we need to consider using Redis as our caching service.

In MyBatis, an interface for Cache is provided. MyBatis also provides a Redis implementation class for the Cache interface, which exists in the myBatis-redis package.

pom.xml

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
</dependency>

Write Code

UserCacheMapper.xml

<?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="icu.wzk.mapper.UserCacheMapper">

    <!--Enable level 2 cache-->
    <cache type="org.mybatis.caches.redis.RedisCache"></cache>

    <resultMap id="userMap" type="icu.wzk.model.WzkUser">
        <result column="id" property="id"></result>
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="birthday" property="birthday"></result>
        <collection property="orderList" ofType="icu.wzk.model.WzkOrder">
            <result column="oid" property="id"></result>
            <result column="ordertime" property="ordertime"></result>
            <result column="total" property="total"></result>
        </collection>
    </resultMap>

    <select id="findAll" resultMap="userMap" useCache="true">
        select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
    </select>
</mapper>

UserCacheMapper.java

public interface UserCacheMapper {
    List<WzkUser> findAll();
}

redis.properties

redis.host=10.10.52.11
redis.port=31679
redis.connectionTimeout=5000
redis.password=
redis.database=0

Test Code

public class WzkicuCache05 {

    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        UserCacheMapper userCacheMapper1 = sqlSession1.getMapper(UserCacheMapper.class);
        UserCacheMapper userCacheMapper2 = sqlSession2.getMapper(UserCacheMapper.class);
        List<WzkUser> wzkUserList = userCacheMapper1.findAll();
        for (WzkUser wu : wzkUserList) {
            System.out.println(wu);
        }
        sqlSession1.close();

        List<WzkUser> wzkUserList2 = userCacheMapper2.findAll();
        for (WzkUser wu : wzkUserList2) {
            System.out.println(wu);
        }
        sqlSession2.close();
    }
}

Running Results

After running, you can see data is output normally, and after running the code multiple times, no requests are sent because the data is cached on Redis.

Source Code Analysis

RedisCache is basically similar to other caching solutions. It implements the Cache interface and uses Jedis to operate the cache.

public final class RedisCache implements Cache {

    private final String id;
    private final JedisPool pool;

    public RedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        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 is created by MyBatis’s CacheBuilder at startup. The creation method is simple: call the constructor of RedisCache with String parameter, i.e., RedisCache(String id). In the RedisCache constructor, RedisConfigurationBuilder is called to create a RedisConfig object, and RedisConfig is used to create JedisPool.

RedisConfig inherits from JedisPoolConfig and provides multiple parameters:

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;

The RedisConfig object is created by RedisConfigurationBuilder, which reads the redis.properties configuration file from the classpath and sets the content of that configuration file into the RedisConfig object.

Template Method

In RedisCache, there is a template method:

private Object execute(RedisCallback callback) {
    Jedis jedis = pool.getResource();
    try {
        return callback.doWithRedis(jedis);
    } finally {
        jedis.close();
    }
}

The template interface is RedisCallback, which only implements one method, doWithRedis:

package org.mybatis.caches.redis;

import redis.clients.jedis.Jedis;

public interface RedisCallback {
    Object doWithRedis(Jedis jedis);
}

Summary

RedisCache is created by MyBatis’s CacheBuilder at startup. The creation method is calling the constructor of RedisCache with String parameter, i.e., RedisCache(String id). In the RedisCache constructor, RedisConfigurationBuilder is called to create a RedisConfig object, and RedisConfig is used to create JedisPool.