mybatis缓存 redis实现

934 阅读5分钟

最近项目需要针对mybatis查询加入缓存,使用redis,于是上网查找mybatis缓存 redis实现的相关文章,有很多关于mybatis redis缓存的介绍以及mybatis Cache接口的redis实现代码,但内容都是一致,看得出都转载某一人的手笔,虽然提供的代码逻辑是正确的,但是在项目应用中都存在问题。请小心使用! 为什么网上流传的mybatis redis实现代码有问题,只要你调试过、测试过,你会发现有这么个问题存在:当页面对数据表存在CUD操作时,以往所有的查询缓存都会clear掉,当再次查询时,重新缓存;只有任意一个CUD操作,整个mybatis缓存都会清空,这样会带来相反的结果,不能利用缓存提供效率,反而降低了系统性能。我们的目标就是,针对Mybatis的Mapper.xml文件,当数据变动后,只清除变动语句存在mapper.xml里的查询缓存,而不是清除所有mapper.xml查询语句缓存。 想了解这块的人,可以对比本文实现代码,和网上其他文章的实现代码,虽然缓存都是可用,但是对系统性能却存在极大差别。 下面分享mybatis缓存 redis实现的步骤以及关键代码:

1,开启mybatis二级缓存,true ,在sqlSessionFactory配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 配置sqlSessionFactory的参数 -->
        <property name="configurationProperties">  
            <props>  
                <prop key="cacheEnabled">true</prop>   
                <!-- 查询时,关闭关联对象即时加载以提高性能  -->
                <prop key="lazyLoadingEnabled">false</prop>  
                 <!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指定),不会加载关联表的所有字段,以提高性能 --> 
                <prop key="aggressiveLazyLoading">true</prop>  
                 <!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果  -->  
                <prop key="multipleResultSetsEnabled">true</prop>  
                 <!-- 允许使用列标签代替列名 -->   
                <prop key="useColumnLabel">true</prop>  
                <!-- 允许使用自定义的主键值(比如由程序生成的UUID 32位编码作为键值),数据表的PK生成策略将被覆盖 -->   
                <prop key="useGeneratedKeys">true</prop>  
                <!-- 给予被嵌套的resultMap以字段-属性的映射支持     -->
                <prop key="autoMappingBehavior">FULL</prop>  
                <!-- 对于批量更新操作缓存SQL以提高性能      -->
                <prop key="defaultExecutorType">BATCH</prop>  
                <!-- 数据库超过25000秒仍未响应则超时     -->
                <prop key="defaultStatementTimeout">25000</prop>  
            </props>
        </property>  
   </bean>

2,在需要添加缓存的mybatis mapper.xml文件中指定缓存Cache实现类,广泛采用LRU算法:

   <mapper namespace="com......Mapper">
    <cache eviction="LRU" type="com.xfx.service.cache.redis.MybatisRedisCache" />
   </mapper>

3,mybatis redis缓存重点,Cache实现类(通过jedisPool获取jedis的方式你可以修改):

   MybatisRedisCache.java
    import java.io.BufferedReader;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.UnsupportedEncodingException;
    import java.util.Properties;
    import java.util.Set;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
     
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.ibatis.cache.Cache;
    import org.apache.log4j.Logger;
     
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
     
    /*
     * 使用第三方缓存服务器,处理二级缓存
     * <a href="http://www.zyiqibook.com">在一起 学习交流分享网 功能源码分享</a>
     * @author xfxpeter@gmail.com
     */
    public class MybatisRedisCache implements Cache {
         
        private static final Logger logger = Logger.getLogger(MybatisRedisCache.class);
         
        /** The ReadWriteLock. */
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
     
        private JedisPool jedisPool;
        private static final int DB_INDEX = 1;
        private final String COMMON_CACHE_KEY = "COM:";
        private static final String UTF_8 = "utf-8";
      
        /**
         * 按照一定规则标识key
         */
        private String getKey(Object key) {
            StringBuilder accum = new StringBuilder();
            accum.append(COMMON_CACHE_KEY);
            accum.append(this.id).append(":");
            accum.append(DigestUtils.md5Hex(String.valueOf(key)));
            return accum.toString();
        }
      
        /**
         * redis key规则前缀
         */
        private String getKeys() {
            return COMMON_CACHE_KEY + this.id + ":*";
        }
     
        private String id;
     
        private Properties properties;
     
        {
            properties = getProp();
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(Integer.valueOf(properties
                    .getProperty("redis.pool.maxIdle")));
            jedisPool = new JedisPool(config, properties.getProperty("redis.host"),
                    Integer.valueOf(properties.getProperty("redis.port")),
                    Integer.valueOf(properties.getProperty("redis.timeout")),
                    properties.getProperty("redis.password"));
        }
         
        /**
         * 加载项目redis连接属性文件
         */
        private Properties getProp(){
            if(properties == null || properties.isEmpty()){
                String propName = "config.properties";
                properties = new Properties();
                InputStream is = null;
                BufferedReader bf = null;
                try {
                    is= this.getClass().getResourceAsStream("/META-INF/config.properties");//将地址加在到文件输入流中
                    bf = new BufferedReader(new InputStreamReader(is,"UTF-8"));//转为字符流,设置编码为UTF-8防止出现乱码
                    properties.load(bf);//properties对象加载文件输入流
                } catch (UnsupportedEncodingException e) {
                    logger.error(propName + "编码格式转换失败,不支持指定编码。" +  e);
                } catch (FileNotFoundException e) {
                    logger.error(propName + "属性文件common.properties不存在。" +  e);
                } catch (IOException e) {
                    logger.error(propName + "属性文件common.properties读取失败。" +  e);
                } catch (Exception e) {
                    logger.error(propName + "属性文件common.properties读取失败。" +  e);
                } finally {
                    try {//文件流关闭
                        if(bf != null){
                            bf.close();
                        }
                        if(is != null ){
                            is.close();
                        }
                    } catch (IOException e) {
                        logger.error("关闭文件流失败。" +  e);
                    }
                }
            }
            return properties;
        }
     
        public MybatisRedisCache() {
        }
     
        public MybatisRedisCache(final String id) {
            if (id == null) {
                throw new IllegalArgumentException("必须传入ID");
            }
            logger.debug("MybatisRedisCache:id=" + id);
            this.id = id;
        }
     
        @Override
        public String getId() {
            return this.id;
        }
     
        @Override
        public int getSize() {
            Jedis jedis = null;
            int result = 0;
            boolean borrowOrOprSuccess = true;
            try {
                jedis = jedisPool.getResource();
                jedis.select(DB_INDEX);
                Set<byte[]> keys = jedis.keys(getKeys().getBytes(UTF_8));
                if (null != keys && !keys.isEmpty()) {
                    result = keys.size();
                }
                logger.debug(this.id+"---->>>>总缓存数:" + result);
            } catch (Exception e) {
                borrowOrOprSuccess = false;
                if (jedis != null)
                    jedisPool.returnBrokenResource(jedis);
            } finally {
                if (borrowOrOprSuccess)
                    jedisPool.returnResource(jedis);
            }
            return result;
     
        }
     
        @Override
        public void putObject(Object key, Object value) {
            Jedis jedis = null;
            boolean borrowOrOprSuccess = true;
            try {
                jedis = jedisPool.getResource();
                jedis.select(DB_INDEX);
                 
                byte[] keys = getKey(key).getBytes(UTF_8);
                jedis.set(keys, SerializeUtil.serialize(value));
                logger.debug("添加缓存--------"+this.id);
                //getSize();
            } catch (Exception e) {
                borrowOrOprSuccess = false;
                if (jedis != null)
                    jedisPool.returnBrokenResource(jedis);
            } finally {
                if (borrowOrOprSuccess)
                    jedisPool.returnResource(jedis);
            }
     
        }
     
        @Override
        public Object getObject(Object key) {
            Jedis jedis = null;
            Object value = null;
            boolean borrowOrOprSuccess = true;
            try {
                jedis = jedisPool.getResource();
                jedis.select(DB_INDEX);
                value = SerializeUtil.unserialize(jedis.get(getKey(key).getBytes(UTF_8)));
                logger.debug("从缓存中获取-----"+this.id);
                //getSize();
            } catch (Exception e) {
                borrowOrOprSuccess = false;
                if (jedis != null)
                    jedisPool.returnBrokenResource(jedis);
            } finally {
                if (borrowOrOprSuccess)
                    jedisPool.returnResource(jedis);
            }
            return value;
        }
     
        @Override
        public Object removeObject(Object key) {
            Jedis jedis = null;
            Object value = null;
            boolean borrowOrOprSuccess = true;
            try {
                jedis = jedisPool.getResource();
                jedis.select(DB_INDEX);
                value = jedis.del(getKey(key).getBytes(UTF_8));
                logger.debug("LRU算法从缓存中移除-----"+this.id);
                //getSize();
            } catch (Exception e) {
                borrowOrOprSuccess = false;
                if (jedis != null)
                    jedisPool.returnBrokenResource(jedis);
            } finally {
                if (borrowOrOprSuccess)
                    jedisPool.returnResource(jedis);
            }
            return value;
        }
     
        @Override
        public void clear() {
            Jedis jedis = null;
            boolean borrowOrOprSuccess = true;
            try {
                jedis = jedisPool.getResource();
                jedis.select(DB_INDEX);
                Set<byte[]> keys = jedis.keys(getKeys().getBytes(UTF_8));
                logger.debug("出现CUD操作,清空对应Mapper缓存======>"+keys.size());
                for (byte[] key : keys) {
                    jedis.del(key);
                }
                //下面是网上流传的方法,极大的降低系统性能,没起到加入缓存应有的作用,这是不可取的。
                //jedis.flushDB();
                //jedis.flushAll();
            } catch (Exception e) {
                borrowOrOprSuccess = false;
                if (jedis != null)
                    jedisPool.returnBrokenResource(jedis);
            } finally {
                if (borrowOrOprSuccess)
                    jedisPool.returnResource(jedis);
            }
        }
     
        @Override
        public ReadWriteLock getReadWriteLock() {
            return readWriteLock;
        }
         
    }

SerializeUtil.java

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
     
    /**
     * 序列化与反序列化
     * <a href="http://www.zyiqibook.com">在一起 学习交流分享网 功能源码分享</a>
     * @author xfxpeter@gmail.com
     */
    public class SerializeUtil {
        public static byte[] serialize(Object object) {  
            if (null == object) return null;
            ObjectOutputStream oos = null;  
            ByteArrayOutputStream baos = null;  
            try {  
                //序列化  
                baos = new ByteArrayOutputStream();  
                oos = new ObjectOutputStream(baos);  
                oos.writeObject(object);  
                byte[] bytes = baos.toByteArray();  
                return bytes;  
            } catch (Exception e) {  
                    e.printStackTrace();  
            }  
            return null;  
         }  
                
        public static Object unserialize(byte[] bytes) {  
              if(null == bytes) return null;
            ByteArrayInputStream bais = null;  
            try {  
                //反序列化  
                bais = new ByteArrayInputStream(bytes);  
                ObjectInputStream ois = new ObjectInputStream(bais);  
                return ois.readObject();  
            } catch (Exception e) {  
                  e.printStackTrace();
               }  
            return null;  
         }
    }

redis缓存使用好了,对项目性能有极大改变,特别是大型web应用项目,这是大型项目性能优化的重点内容。