使用protostuff + redis做mybatis二级缓存

实操

首先使用jedis的方式来实现,后面使用spring-boot-data-redis

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
		<dependency>
			<groupId>io.protostuff</groupId>
			<artifactId>protostuff-core</artifactId>
			<version>1.5.9</version>
		</dependency>
        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.5.9</version>
        </dependency>

由于使用的spring-boot-starter-data-redis,它是对redis客户端的一层封装,默认使用的lettuce客户端,这里使用jedis,所以先exclusion掉它

/**
 * Created by zkk on 2019/3/14
 * 增加protostuff序列化方式,生成的码流比jdk序列化小,速度更快
 * 解决devtool热加载在jdk序列化下类型转换报错的情况
 **/
public class ProtostuffSerializer implements RedisSerializer<Object> {

    private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
    }

    private final Schema<ProtoWrapper> schema;

    private final ProtoWrapper wrapper;

    private final LinkedBuffer buffer;

    public ProtostuffSerializer() {
        this.wrapper = new ProtoWrapper();
        this.schema = RuntimeSchema.getSchema(ProtoWrapper.class);
        this.buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    }

    @Override
    public byte[] serialize(Object t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        wrapper.data = t;
        try {
            return ProtostuffIOUtil.toByteArray(wrapper, schema, buffer);
        } finally {
            buffer.clear();
        }
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (isEmpty(bytes)) {
            return null;
        }

        ProtoWrapper newMessage = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(bytes, newMessage, schema);
        return newMessage.data;
    }

    // Protostuff 无法直接序列化集合类对象,需要包装类包一下
    private static class ProtoWrapper {
        public Object data;
    }
}

然后就是序列化的实现类,它实现了RedisSerializer接口
主要的方法就是byte[] serialize(T t)和 deserialize(byte[] bytes)
在这里插入图片描述
可以看出该接口实现了各种序列化方式,这些也可以在RedisTemplate里面使用,可以set它的序列化方式,比如常用的stringRedisTemplate就是使用的StringRedisSerializer序列化方式

/**
 * Created by zkk on 2019/2/1
 * mybatis的redis二级缓存
 * 需要用到缓存的就在mapper.xml里面加上
 * <cache type="com.xxx.xxx.config.MybatisRedisCache"/>
 **/
public class MybatisRedisCache implements Cache {
    private static Logger LOGGER = LogManager.getLogger(MybatisRedisCache.class);
    private final String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private static JedisConnectionFactory jedisConnectionFactory;

    /**
     * 这个地方需要静态注入,这里通过中间类 MybatisRedisCacheTransfer 实现的
     *
     * @param jedisConnectionFactory
     */
    public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        MybatisRedisCache.jedisConnectionFactory = jedisConnectionFactory;
    }

    public MybatisRedisCache(final String id) {
        if (null == id || "".equals(id)) {
            throw new IllegalArgumentException("mybatis redis cache need an id.");
        }
        this.id = id;
        LOGGER.debug("mybatis redis cache id: {}", id);
    }


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

    /**
     * 存值
     *
     * @param key
     * @param value
     */
    @Override
    public void putObject(Object key, Object value) {
        if (null == key) {
            return;
        }
        LOGGER.debug("mybatis redis cache put. K={} value={}", key, value);
        RedisConnection redisConnection = null;
        try {
            redisConnection = jedisConnectionFactory.getConnection();
            RedisSerializer serializer = new ProtostuffSerializer();

            redisConnection.setEx(serializer.serialize(key), 500, serializer.serialize(value));

            // 将key保存到redis.list中
            redisConnection.lPush(serializer.serialize(id), serializer.serialize(key));
            redisConnection.expire(serializer.serialize(id), 500);
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache put exception. K=" + key + " V=" + value + "", e);
        } finally {
            if (null != redisConnection) {
                redisConnection.close();
            }
        }
    }

    /**
     * 取值
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        if (null == key) {
            return null;
        }
        LOGGER.debug("mybatis redis cache get. K={}", key);
        RedisConnection redisConnection = null;
        Object result = null;
        try {
            redisConnection = jedisConnectionFactory.getConnection();
            RedisSerializer serializer = new ProtostuffSerializer();
            result = serializer.deserialize(redisConnection.get(serializer.serialize(key)));
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache get exception. K=" + key + " V=" + result + "", e);
        } finally {
            if (null != redisConnection) {
                redisConnection.close();
            }
        }
        return result;
    }

    /**
     * 删值
     *
     * @param key
     * @return
     */
    @Override
    public Object removeObject(Object key) {
        if (null == key) {
            return null;
        }
        LOGGER.debug("mybatis redis cache remove. K={}", key);
        RedisConnection redisConnection = null;
        Object result = null;
        try {
            redisConnection = jedisConnectionFactory.getConnection();
            RedisSerializer serializer = new ProtostuffSerializer();
            // 讲key设置为立即过期
            result = redisConnection.expireAt(serializer.serialize(key), 0);

            // 将key从redis.list中删除
            redisConnection.lRem(serializer.serialize(id), 0, serializer.serialize(key));
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache remove exception. " + key + " V=" + result + "", e);
        } finally {
            if (null != redisConnection) {
                redisConnection.close();
            }
        }
        return result;
    }

    /**
     * 清空缓存
     * flushCache="true" 的时候会调用这个地方
     */
    @Override
    public void clear() {
        LOGGER.debug("mybatis redis cache clear. ");
        RedisConnection redisConnection = null;
        try {
            redisConnection = jedisConnectionFactory.getConnection();
            RedisSerializer serializer = new ProtostuffSerializer();
            /**
             * 千万不要直接 redisConnection.flushDb(),会把整个redis的东西都清除掉,我不相信你的redis里没有其他东西
             * 获取redis.list中的保存的key值,遍历删除
             */
            Long length = redisConnection.lLen(serializer.serialize(id));
            if (0 == length) {
                return;
            }
            List<byte[]> keyList = redisConnection.lRange(serializer.serialize(id), 0, length - 1);
            for (byte[] key : keyList) {
                redisConnection.expireAt(key, 0);
            }
            redisConnection.expireAt(serializer.serialize(id), 0);
            keyList.clear();
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache clear exception. ", e);
        } finally {
            if (null != redisConnection) {
                redisConnection.close();
            }
        }
    }

    @Override
    public int getSize() {
        int result = 0;
        try {
            RedisConnection redisConnection = jedisConnectionFactory.getConnection();
            result = Math.toIntExact(redisConnection.dbSize());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
}

这些是在网上找的demo,修改了些参数,现在能很好的运行的代码

需要注意的是在spring-boot-devtools热加载工具开启的情况下经常会发生java.lang.ClassCastException:异常,就是两个相同的类不能cast,关闭devtool就可以了。

然后在mybatis-config.xml配置上加上一句

<setting name="cacheEnabled" value="true"/>

最后在需要的mapper.xml加上

<mapper namespace="com.xxx.mapper.xxx">
 <cache type="com.config.MybatisRedisCache"/>
 </mapper>

来开启二级缓存

使用spring-boot-starter-data-redis的实现

因为他们说尽量都用spring-boot集成了的东西,这样有利于平滑升级,所以这里把上面的换成spring-boot-starter-data-redis实现一遍

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

redis客户端用spring封装好的就可以了,它底层是用的lettuce客户端

import org.apache.ibatis.cache.Cache;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created by zkk on 2019/2/1
 * mybatis的redis二级缓存
 * 需要用到缓存的就在mapper.xml里面加上
 * <cache type="com.xxx.xxx.MybatisRedisCache"/>
 **/
public class MybatisRedisCache implements Cache {
    private static Logger LOGGER = LogManager.getLogger(MybatisRedisCache.class);
    private final String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private static RedisTemplate<Object, Object> redisTemplate;

    /**
     * 这里需要外部去set redisTemplate来注入
     * @param redisTemplate
     */
    public static void setRedisTemplate(RedisTemplate redisTemplate) {
        MybatisRedisCache.redisTemplate = redisTemplate;
    }

    public MybatisRedisCache(final String id) {
        if (null == id || "".equals(id)) {
            throw new IllegalArgumentException("mybatis redis cache need an id.");
        }
        this.id = id;
        LOGGER.debug("mybatis redis cache id: {}", id);
    }


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


    /**
     * 向redis中存值,设置过期时间500s
     * @param key
     * @param value
     */
    @Override
    public void putObject(Object key, Object value) {
        if (null == key) {
            return;
        }
        LOGGER.debug("mybatis redis cache put. K={} value={}", key, value);
        try {
            redisTemplate.opsForValue().set(key, value, 500, TimeUnit.SECONDS);
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache put exception. K=" + key + " V=" + value + "", e);
        }
    }


    /**
     * 从redis中取值
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        if (null == key) {
            return null;
        }
        LOGGER.debug("mybatis redis cache get. K={}", key);
        Object result = null;
        try {
            result = redisTemplate.opsForValue().get(key);
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache get exception. K=" + key + " V=" + result + "", e);
        }
        return result;
    }


    @Override
    public Object removeObject(Object key) {
        if (null == key) {
            return null;
        }
        LOGGER.debug("mybatis redis cache remove. K={}", key);
//        RedisConnection redisConnection = null;
        Object result = null;
        try {
            result = redisTemplate.delete(key);
        } catch (Exception e) {
            LOGGER.error("mybatis redis cache remove exception. " + key + " V=" + result + "", e);
        }
        return result;
    }

    @Override
    public void clear() {
        // TODO
    }

    @Override
    public int getSize() {
        int result = 0;
        try {
            result = Math.toIntExact(redisTemplate.execute(RedisServerCommands::dbSize));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
}

这里使用redisTemplate.opsForValue()就可以了,关于这个方法是spring-boot对redis常用操作的一层封装,底层是通过RedisCallback接口获取到了RedisConnection来实现的,所以当使用一些不常用的操作就可以使用这个
在这里插入图片描述

redisTemplate.execute((RedisCallback<Object>) connection -> {
                connection.set(new byte[]{1},new byte[]{1},
                        Expiration.from(5, TimeUnit.SECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
                return null;
            });

这里就是redis的setnx、setpx结合方法

继续回到主题,实现了Cache之后,跟前面的类似,需要指定序列化方式

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * Created by zkk on 2019/3/27
 **/
@Configuration
public class RedisTemplateConfig {

    @Autowired
    @SuppressWarnings("unchecked")
    public void setRedisTemplate(RedisTemplate redisTemplate) {

        // 使用ProtostuffSerializer 替换默认序列化
        RedisSerializer serializer = new ProtostuffSerializer();

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setDefaultSerializer(serializer);
        redisTemplate.setHashKeySerializer(serializer);
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.setStringSerializer(serializer);
        redisTemplate.afterPropertiesSet();

        MybatisRedisCache.setRedisTemplate(redisTemplate);
    }
}

指定的之前的protostuff序列化方式,使用setter注入,往MybatisRedisCache中注入redisTemplate,其实当然也可以配置一个新的bean来实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值