使用Redis做MyBatis的二级缓存

使用Redis做MyBatis的二级缓存

Mybatis的缓存

通大多数ORM层框架一样,Mybatis自然也提供了对一级缓存和二级缓存的支持。一下是一级缓存和二级缓存的作用于和定义。
      1、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。 
      2、一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
      二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
      一般的我们将Mybatis和Spring整合时,mybatis-spring包会自动分装sqlSession,而Spring通过动态代理sqlSessionProxy使用一个模板方法封装了select()等操作,每一次select()查询都会自动先执行openSession(),执行完close()以后调用close()方法,相当于生成了一个新的session实例,所以我们无需手动的去关闭这个session(),当然也无法使用mybatis的一级缓存,也就是说mybatis的一级缓存在spring中是没有作用的。
      因此我们一般在项目中实现Mybatis的二级缓存,虽然Mybatis自带二级缓存功能,但是如果实在集群环境下,使用自带的二级缓存只是针对单个的节点,所以我们采用分布式的二级缓存功能。一般的缓存NoSql数据库如redis,Mancache等,或者EhCache都可以实现,从而更好地服务tomcat集群中ORM的查询。

使用Redis做MyBatis的二级缓存
1. 介绍
  使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验。
  使用redis做mybatis的二级缓存可是内存可控<如将单独的服务器部署出来用于二级缓存>,管理方便。
 
2. 使用思路
  2.1 配置redis.xml 设置redis服务连接各参数
  2.1 在配置文件中使用 <setting> 标签,设置开启二级缓存;
  2.2 在mapper.xml 中使用<cache type="at.yuxin.com.util.MybatisRedisCache " /> 将cache映射到指定的MybatisRedisCache类中;
  2.3 映射类RedisCacheClass 实现 MyBatis包中的Cache类,并重写其中各方法;
    在重写各方法体中,使用redisFactory和redis服务建立连接,将缓存的数据加载到指定的redis内存中(putObject方法)或将redis服务中的数据从缓存中读取出来(getObject方法);
    在redis服务中写入和加载数据时需要借用spring-data-redis.jar中JdkSerializationRedisSerializer.class中的序列化(serialize)和反序列化方法(deserialize),此为包中封装的redis默认的序列化方法;
  2.4 映射类中的各方法重写完成后即可实现mybatis数据二级缓存到redis服务中;
 
3. 代码实践
  3.1 配置spring-redis.xml
< ?xml version="1.0" encoding="UTF-8"?>
   
    <!-- 启动缓存注解,这个必须的,否则注解不生效,另外,该注解一定要声明在spring主配置文件中才会生效  -->
   <!--  <cache:annotation-driven cache-manager="cacheManager"/> -->
   
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}" />
        <property name="maxTotal" value="${redis.maxTotal}" />
        <property name="blockWhenExhausted" value="true" />
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
        <property name="testOnBorrow" value="true" /> 
    </bean>
  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.hostName}" />
        <!-- <property name="hostName" value="192.168.1.105" />--> 
        <property name="port" value="${redis.port}"/>
        <property name="poolConfig" ref="jedisPoolConfig" />
        <property name="usePool" value="true"/>
    </bean>
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">  
        <property name="connectionFactory"   ref="jedisConnectionFactory" />  
        <property name="keySerializer">  
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />  
        </property>     
        <property name="valueSerializer">  
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />  
        </property>  
        <property name="hashKeySerializer">    
           <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>    
        </property>  
        <property name="hashValueSerializer">  
           <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>    
        </property>
        <!-- 开启事务支持 -->
        <property name="enableTransactionSupport" value="true"/>
      </bean>
       <property name="redisTemplate" ref="redisTemplate"/>
      </bean>
</beans>

 
  3.2 mybatisCfg.xml 配置开启二级缓存
<<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
       <!-- 开启二级缓存 -->
       <setting name="cacheEnabled" value="true" />
       <!--开启延时加载,如果有关联关系,则默认不会获取数据 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
           在association中指定fetchType="eager(立即)" 或者 lazy(延迟) 默认:false -->
       <setting name="lazyLoadingEnabled" value="false" />
       <!--true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载; false,每种属性将会按需加载。 默认为:true -->
       <setting name="aggressiveLazyLoading" value="true" />
    </settings>
</configuration>

 
   3.3 在mapper.xml中映射缓存类MybatisRedisCacheClass
<?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" >
  <resultMap id="BaseResultMap" type="at.yuxin.com.dao.model.Company" >
    <id column="company_id" property="companyId" jdbcType="INTEGER" />
    <result column="company_name" property="companyName" jdbcType="VARCHAR" />
    <result column="company_addr" property="companyAddr" jdbcType="VARCHAR" />
    <result column="company_legal_person" property="companyLegalPerson" jdbcType="VARCHAR" />
  </resultMap>
  <!-- 
    flushInterval(清空缓存的时间间隔): 单位毫秒,可以被设置为任意的正整数。 
        默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。 
    size(引用数目): 可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。 
    readOnly(只读):属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。 
        因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。 
    eviction(回收策略): 默认的是 LRU: 
        1.LRU – 最近最少使用的:移除最长时间不被使用的对象。 
        2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 
        3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 
        4.WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 
    blocking(是否使用阻塞缓存): 默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思, 
        使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁, 
        这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。 
    type(缓存类):可指定使用的缓存类,mybatis默认使用HashMap进行缓存 
    -->
  <cache eviction="LRU"  type="at.yuxin.com.util.MybatisRedisCache" flushInterval="5000"/>
  <sql id="Base_Column_List" >
    company_id, company_name, company_addr, company_legal_person
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" useCache="true">
    select
    <include refid="Base_Column_List" />
    from company where company_id = #{companyId,jdbcType=INTEGER}
  </select>
</mapper>

 
  3.4 实现Mybatis中的Cache接口
    Cache.class源码:
/*
* Copyright 2009-2012 the original author or authors.
* http://www.apache.org/licenses/LICENSE-2.0 */ package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {

String getId();

int getSize();

void putObject(Object key, Object value);

Object getObject(Object key);

Object removeObject(Object key);

void clear();

ReadWriteLock getReadWriteLock();

}
 
    MybatisRedisCache.java
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.log4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
public class MybatisRedisCache implements Cache{
     private static final Logger LOG = Logger.getLogger(MybatisRedisCache.class);  
    
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); 
         
        private static RedisTemplate<Serializable, Serializable> redisTemplate;
        private String id
         
        private JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer(); 
         
        public MybatisRedisCache(final String id){ 
            if(id == null){ 
                throw new IllegalArgumentException("Cache instances require an ID"); 
            } 
            LOG.info("Redis Cache id " + id); 
            this.id = id
        } 
         
        @Override 
        public String getId() { 
            return this.id
        } 
     
        @Override 
        public void putObject(Object key, Object value) { 
            if(value != null){ 
                redisTemplate.opsForValue().set(key.toString(), jdkSerializer.serialize(value), 2, TimeUnit.DAYS); 
            } 
        } 
     
        @Override 
        public Object getObject(Object key) { 
            try
                if(key != null){ 
                    Object obj = redisTemplate.opsForValue().get(key.toString()); 
                    return jdkSerializer.deserialize((byte[])obj);  
                } 
            } catch (Exception e) { 
                LOG.error("redis "); 
            } 
            return null
        } 
     
        @Override 
        public Object removeObject(Object key) { 
            try
                if(key != null){ 
                    redisTemplate.expire(key.toString(), 1, TimeUnit.SECONDS); 
                } 
            } catch (Exception e) { 
            } 
            return null
        } 
     
        @Override 
        public void clear() { 
            //jedis nonsupport 
        } 
        @Override 
        public int getSize() { 
            Long size = redisTemplate.execute(new RedisCallback<Long>(){ 
               @Override
               public Long doInRedis(RedisConnection connection) throws DataAccessException {
                  return connection.dbSize(); 
               } 
            }); 
            return size.intValue(); 
        } 
     
        @Override 
        public ReadWriteLock getReadWriteLock() { 
            return this.readWriteLock
        }
       
       public static void setRedisTemplate(RedisTemplate redisTemplate) {
           MybatisRedisCache.redisTemplate = redisTemplate;
       }     
}

 
  4. 总结
    通过重写Cache类中的方法,将mybatis中默认的缓存空间映射到redis空间中。

猜你喜欢

转载自blog.csdn.net/qq_20949511/article/details/80735545