spring boot使用mybatis redis cache 缓存

背景:
      最近公司项目中需要引入缓存机制来减轻数据库负载,所以对一些缓存方案进行了研究,其中包括看了几篇讲mybatis的二级缓存的,写的都很不错,推荐美团的一篇:聊聊MyBatis缓存机制 对mybatis的缓存机制讲的很清楚了。博主在本文提供一种使用redis的hash结构来实现mybatis的二级缓存方案,初次尝试,如有疑问欢迎指正。

环境:
jdk 1.8
ide:Intellij 2017.1
spring-boot :2.0.4.RELEASE
redis:4.0.2
mysql:5.7.20
搭建项目&实现
通过idea新建spring-boot项目,傻瓜式的选择一些需要的starter即可,不再赘述:参考图片


数据源、redis、mybatis相关配置 (spring-boot的自动配置这里也很简单)直接加入相关的配置即可如下:
spring:
  redis:
    host: 127.0.0.1
    password:
    port: 6379
    jedis:
      pool:
        min-idle: 0
        max-active: 8
        max-idle: 8
        max-wait: 5000
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/song?characterEncoding=UTF-8&allowMultiQueries=true
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
mybatis:
  configuration:
    cache-enabled: true #开启全局二级缓存  mybatis通过这个会使用CachingExecutor
  mapper-locations: classpath:mapper/*.xml
 

正常mybatis使用配置(beans、mapper配置、mapper接口):
定义一个User bean
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Integer gender;
}
 

mapper接口
@Mapper
public interface UserMapper {
    User selectOne(Integer id);
    void insertOne(User user);
}
 

mapper配置
<?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="com.example.demo.mapper.UserMapper">
    <!--使用自定义的缓存
    <cache type="com.example.demo.cache.RedisCache"/>
    -->
    <resultMap id="userResultMap" type="com.example.demo.entities.User">
        <id property="id" column="id" jdbcType="INTEGER" javaType="java.lang.Integer"/>
        <result property="name" column="name" jdbcType="VARCHAR" javaType="java.lang.String"/>
        <result property="gender" column="gender" jdbcType="TINYINT" javaType="java.lang.Integer"/>
    </resultMap>
 
    <select id="selectOne" resultMap="userResultMap" parameterType="java.lang.Integer">
        SELECT id, name, gender
        FROM `USER`
        WHERE id = #{id}
    </select>
 
    <insert id="insertOne" parameterType="com.example.demo.entities.User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO `USER` (name, gender) VALUES (#{name}, #{gender})
    </insert>
 
</mapper>
最好先测试下不使用缓存时代码能用

RedisTemplate的配置:
@Configuration
public class RedisConfiguration {
 
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
 
    @Bean
    public RedisTemplate<String,Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
        return redisTemplate;
    }
 
    /**
     * 设置数据存入 redis 的序列化方式
     * @param template
     * @param factory
     */
    private void initDomainRedisTemplate(RedisTemplate<String, Object> template, RedisConnectionFactory factory) {
        // 定义 key 的序列化方式为 string
        // 需要注意这里Key使用了 StringRedisSerializer,那么Key只能是String类型的,不能为Long,Integer,否则会报错抛异常。
        StringRedisSerializer redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        // 定义 value 的序列化方式为 json
        @SuppressWarnings({"rawtypes", "unchecked"})
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
 
        //hash结构的key和value序列化方式
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setEnableTransactionSupport(true);
        template.setConnectionFactory(factory);
    }

}
 

自定义实现mybatis的cache接口  直接上代码吧
 
public class RedisCache implements Cache {
 
    private String id;
 
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 
    public RedisCache(String id) {
        this.id = id;
    }
 
    private RedisTemplate<Object, Object> getRedisTemplate(){
        return ApplicationContextHolder.getBean("redisTemplate");
    }
 
    @Override
    public String getId() {
        return id;
    }
 
    @Override
    public void putObject(Object key, Object value) {
        getRedisTemplate().boundHashOps(getId()).put(key, value);
    }
 
    @Override
    public Object getObject(Object key) {
        return getRedisTemplate().boundHashOps(getId()).get(key);
    }
 
    @Override
    public Object removeObject(Object key) {
        return getRedisTemplate().boundHashOps(getId()).delete(key);
    }
 
    @Override
    public void clear() {
        getRedisTemplate().delete(getId());
    }
 
    @Override
    public int getSize() {
        Long size = getRedisTemplate().boundHashOps(getId()).size();
        return size == null ? 0 : size.intValue();
    }
 
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}
      这里注意RedisTemplate不能通过spring的resource注入,因为cache对象是和id绑定的,可以debug看下这个id其实是mapper中的namespace、这也是为什么自定义cache中必须有一个有参构造器;这个实现中也是根据这个id来为每个namespace对应一个redis的hash结构,在clear时只删掉本namespace的缓存即可

mapper中使用自定义cache,只需要在mapper中加上<cache>标签,具体其中的属性可以自己Google
<cache type="com.example.demo.cache.RedisCache"/>
 

所有代码见:https://github.com/swg-liuge/spring-boot-mybatis-cache2-redis


原文链接:

https://blog.csdn.net/weiguang111/article/details/81626679

#############################

SpringBoot使用Redis做缓存

      随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。
在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。

缓存工具简单比较
实现缓存有多种方式:EhCache、MemCached、Redis等

EhCache:纯Java进程内缓存框架,配置简单、结构清晰、功能强大。开始接触javaweb的时候,不管是使用hibernate还是mybatis,这个应该是最常见的缓存的工具;经常会用作二级缓存。百度百科上的特点:
MemCached:一个自由开源的,高性能的分布式对象缓存;使用key-value存储系统。特点:
协议简单
基于libevent的事件处理


内置内存存储方式
memcached不互相通信的分布式
Redis:NoSql数据库,不仅仅支持k/v存储,同时还提供list、set、hash的数据结构的存储。具体学习材料可以进官网,传送门:http://www.redis.cn/

Redis缓存介绍

本文的项目主要是SpringBoot+mybatis+redis+mysql。使用redis做缓存可以有几种实现方式:
直接将redis作为mybatis的二级缓存;
通过注解方式给项目添加缓存;
手动调用jedis。这种方式不推荐。。。
环境的搭建
在SpringBoot中已经有对于缓存的支持,只需要做简单的配置添加即可。
pom.xml文件

 <!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--缓存-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
添加redis的配置,这列使用的是yml的配置

# redis配置
  redis:
    #数据库索引,默认为0
    database: 0
    #服务器地址
    host: localhost
    #端口
    port: 6379
    #密码(默认为空)
    password:
    pool:
      #连接池最大连接数(使用负值表示没有限制)
      max-active: 8
      #连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1
      # 连接池中的最大空闲连接
      max-idle: 8
      # 连接池中的最小空闲连接
      min-idle: 0
    # 连接超时时间(毫秒)
    timeout: 5000
使用
使用注解配置缓存
这种方式比较简单,在相应的service中添加Cache注解就可以,项目代码,mybatis的配置文件以及mapper文件省略,
test实体类:这里使用了lombok的@Data注解


@Data
public class Test implements Serializable{
    private String id;
    private String name;
    private String remake;
}
service类:这里的注解使用就是在service类中使用


@Service
@CacheConfig(cacheNames = "test")
public class TestService {
    @Autowired
    private TestMapper testMapper;
    @Cacheable(key = "#p0")
    public Test get(String id) {
        return testMapper.get(id);
    }
    @CachePut(key = "#p0.id")
    public Test insert(Test test) {
        testMapper.insert(test);
        return test;
    }
    @CachePut(key = "#p0.id")
    public Test update(Test test) {
        testMapper.update(test);
        return test;
    }
}
测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestServiceTest {
    private static final Logger logger = LoggerFactory.getLogger(TestServiceTest.class);
    @Autowired
    private TestService testService;
    @Test
    public void get() {
        com.wqh.blog.domain.Test test = testService.get("d8e875c8-9425-485b-9665-f5dda1e788bf");
        logger.info("======"+test+"=============");
    }
    @Test
    public void insert() {
        com.wqh.blog.domain.Test test = new com.wqh.blog.domain.Test();
        test.setId(UUID.randomUUID().toString());
        test.setName("redis");
        test.setRemake("hhhhhhhhhhhhhhhhhhhhhhh");
        testService.insert(test);
    }
    @Test
    public void update() {
    }
}
首先运行,插入方法,然后使用RedisClient查看,可以发现已经有数据。然后调用get方法,控制台并没有打印sql语句,配置成功


CACHE注解
这里直接引用程序员DD大神博客的内容:

@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = “test”):配置了该数据访问对象中返回的内容将存储于名为test的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
@Cacheable:配置了get函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:
value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。
unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。
@CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析
@CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:
allEntries:非必需,默认为false。当为true时,会移除所有数据
beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。
使用redis做mybatis的二级缓存
在mybatis中有一级缓存、二级缓存

  1. 一级缓存:该缓存是基于SqlSession的,mybatis默认开启一级缓存。
  2. 二级缓存:该缓存是Mapper级别的,默认没有开启二级缓存,需要在配置文件中开启:

<!-- 开启二级缓存,默认是false -->
<setting name="cacheEnabled" value="true"/>
依赖跟前面一样,使用redis做二级缓存,主要是要实现org.apache.ibatis.cache包下的Cache接口

public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id;
    private RedisTemplate redisTemplate;
    /**
     *    redis过期时间
     */
    private static final long EXPIRE_TIME_IN_MINUTES = 300;
    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }
    @Override
    public String getId() {
        return id;
    }
    /**
     * Put query result to redis
     * TimeUnit.HOURS 设置时间的类型:时、分、秒、毫秒
     * @param key
     * @param value
     */
    @Override
    public void putObject(Object key, Object value) {
        RedisTemplate redisTemplate = getRedisTemplate();
        ValueOperations opsForValue = redisTemplate.opsForValue();
        opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.HOURS);
        logger.debug("Put query result to redis");
    }
    /**
     * Get cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        ValueOperations opsForValue = redisTemplate.opsForValue();
        logger.debug("Get cached query result from redis");
        Object o = opsForValue.get(key);
        return o;
    }
    /**
     * Remove cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object removeObject(Object key) {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.delete(key);
        logger.debug("Remove cached query result from redis");
        return null;
    }
    /**
     * Clears this cache instance
     */
    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.execute((RedisCallback) connection -> {
            connection.flushDb();
            return null;
        });
        logger.debug("Clear all the cached query result from redis");
    }
    /**
     * This method is not used
     *
     * @return
     */
    @Override
    public int getSize() {
        return 0;
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = SpringContextHolder.getBean("cacheRedisTemplate");
        }
        return redisTemplate;
    }
}
自定义一个序列化接口

public class RedisObjectSerializer implements RedisSerializer<Object> {
    private Converter<Object, byte[]> serializer = new SerializingConverter();
    private Converter<byte[], Object> deserializer = new DeserializingConverter();
    static final byte[] EMPTY_ARRAY = new byte[0];
    @Override
    public byte[] serialize(Object object) throws SerializationException {
        if (object == null) {
            return EMPTY_ARRAY;
        }
        try {
            return serializer.convert(object);
        } catch (Exception ex) {
            return EMPTY_ARRAY;
        }
    }
    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (isEmpty(bytes)) {
            return null;
        }
        try {
            return deserializer.convert(bytes);
        } catch (Exception ex) {
            throw new SerializationException("Cannot deserialize", ex);
        }
    }
    private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
    }
}
redis的配置类


@Configuration
public class RedisConfig {
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(){
        return new JedisConnectionFactory();
    }
    @Bean
    public RedisTemplate<String,Object> cacheRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 设置值(value)的序列化采用自定义的RedisObjectSerializer
        redisTemplate.setValueSerializer(new RedisObjectSerializer());
        // 设置键(key)的序列化采用jackson2JsonRedisSerializer
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
前面已经开启了二级缓存,现在需要修改Mapper.xml文件


<!--表示开启基于redis的二级缓存-->
  <cache type="com.wqh.blog.config.cache.RedisCache">
      <property name="eviction" value="LRU" />
      <property name="flushInterval" value="6000000" />
      <property name="size" value="1024" />
      <property name="readOnly" value="false" />
  </cache>
另外在插入、修改和删除时需要添加flushCache="true"。
缓存添加成功的话,使用RedisClient可以查看:


手动将数据添加到redis
在上面的RedisConfig类中添加方法,比如我这里要将Test对象保存到redis:

@Bean
  public RedisTemplate<String,Test> testRedisTemplate(RedisConnectionFactory redisConnectionFactory){
      RedisTemplate<String,Test> template = new RedisTemplate<String,Test>();
      template.setConnectionFactory(redisConnectionFactory);
      //直接使用Jedis提供的StringRedisSerializer
      template.setKeySerializer(new StringRedisSerializer());
      Jackson2JsonRedisSerializer<Test> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Test>(Test.class);
      ObjectMapper om = new ObjectMapper();
      om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
      om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
      jackson2JsonRedisSerializer.setObjectMapper(om);
      //使用jackson2JsonRedisSerializer序列化value
      template.setValueSerializer(jackson2JsonRedisSerializer);
      return template;
  }
测试类


@Autowired
private RedisTemplate<String, com.wqh.blog.domain.Test> testRedisTemplate;
@Test
public void testRedis() throws Exception{
    com.wqh.blog.domain.Test test = new com.wqh.blog.domain.Test();
    test.setId(UUID.randomUUID().toString());
    test.setName("redis");
    test.setRemake("hhhhhhhhhhhhhhhhhhhhhhh");
    testRedisTemplate.opsForValue().set(test.getId(),test);
    Assert.assertEquals("redis",testRedisTemplate.opsForValue().get(test.getId()).getName());
}
问题
前面使用注解方式添加缓存的功能,如果使用RedisClient查看数据的话,会发现数出现乱码现象,这主要是序列化问题。解决办法只要自定义一个redisTemplate的bean


@Bean
public RedisTemplate<Object, Object> redisTemplate() {
    RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(jedisConnectionFactory());
    //key序列化方式,但是如果方法上有Long等非String类型的话,会报类型转换错误
    //Long类型不可以会出现异常信息;
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(redisSerializer);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}
转自:

http://www.wanqhblog.top/2017/12/26/SpringBoot%E4%BD%BF%E7%94%A8Redis%E5%81%9A%E7%BC%93%E5%AD%98/

####################################

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

 

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的方式你可以修改):

    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;
        }
         
    }
 

我的疑问:只能这样解析key吗,不能自定义生成key的规则吗?

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应用项目,这是大型项目性能优化的重点内容。

 

#############################################################

spring boot 2.1.3 

为了减少数据库访问频率,提高查询速度,今天讲一下spring boot mybatis 怎么集成 redis缓存。

第一步:添加redis相关jar包依赖

这里我只贴出集成redis缓存所需要的jar包,至于链接数据库,和基本的项目启动请参考
Spring boot 2 mybatis配置

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

至于jar包spring-boot-starter-cache至少在版本2.1.3可以不用引用,2.1.3版本jar包spring-boot-starter-data-redis已经自带cache所需要的jar包,不用重复引用。

第二步:添加redis链接数据库配置文件

spring:
  # 数据库连接
  #datasource:
  #  password: 111111
  #  url: jdbc:mysql://120.77.241.43:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8
  #  username: root

  # redis
  redis:
    host: 127.0.0.1
    port: 6300
    password: 123
    # Redis数据库索引(默认为0)
    database: 0
    #连接超时时间(毫秒)
    timeout: 10000

    jedis: #一些常规配置
      pool:
        # 连接池中的最大空闲连接
        max-idle: 60
        # 连接池中的最小空闲连接
        min-idle: 30
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 60000
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 200

第三步:添加RedisConfig配置类

@Configuration

@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean(name = "cacheKeyGenerator")
    @Primary//@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> CacheHashCode.of(params);
    }

    /**
     * spring boot 缓存默认配置
     * @param factory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory)
                //默认缓存时间
                .cacheDefaults(getRedisCacheConfigurationWithTtl(60))
                .transactionAware()
                //自定义缓存时间
                .withInitialCacheConfigurations(getRedisCacheConfigurationMap())
                .build();
    }

    /**
     * 自定义缓存时间
     * @return
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        redisCacheConfigurationMap.put("test", this.getRedisCacheConfigurationWithTtl(3000));
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(seconds))//定义默认的cache time-to-live.(缓存存储有效时间)
                .disableCachingNullValues()//静止缓存为空
                //此处定义了cache key的前缀, 避免公司不同项目之间的key名称冲突.
                .computePrefixWith(cacheName -> "api".concat(":").concat(cacheName).concat(":"))
                //定义key和value的序列化协议, 同时的hash key和hash value也被定义.
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(createJackson2JsonRedisSerializer()))
                //自定义key的生成策略, 将方法参数转换为hashcode, 作为redis key. 需要做两个事情, 一个是添加一个自定义的ConversionService, 另一个是需要自定义一个KeyGenerator.
                .withConversionService(new CacheKeyConversionService());
    }

    /**
     * 创建redisTemplate工具
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        template.setKeySerializer(new StringRedisSerializer());//设置key序列化类,否则key前面会多了一些乱码
        template.setValueSerializer(createJackson2JsonRedisSerializer());//设置value序列化
        template.setHashKeySerializer(createJackson2JsonRedisSerializer());//设置 hash key 序列化
        template.setHashValueSerializer(createJackson2JsonRedisSerializer());//设置 hash value 序列化
        template.setEnableTransactionSupport(true);//设置redis支持数据库的事务
        template.afterPropertiesSet();//初始化设置并且生效
        return template;
    }

    /**
     * 创建redis序列化
     * @return
     */
    private RedisSerializer<Object> createJackson2JsonRedisSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }

}
public class CacheKeyConversionService implements ConversionService {
    @Override
    public boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType) {
        return true;
    }

    @Override
    public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
        return true;
    }

    @Nullable
    @Override
    public <T> T convert(@Nullable Object source, Class<T> targetType) {
        return (T) convert(source);
    }

    @Nullable
    @Override
    public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
        return convert(source);
    }

    private Object convert(Object source) {
        if (source instanceof CacheHashCode) {
            return ((CacheHashCode) source).hashString();
        }
        return CacheHashCode.of(source).hashString();
    }
}

第四步:在需要存入redis缓存的方法上添加注解

image.png

注解介绍

  • @Cacheable
    获取缓存 如果有缓存 直接返回
属性类型功能
valueString[]缓存的名称 和cacheNames功能一样
cacheNamesString[]缓存的名称和value功能一样
keyString缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
conditionString根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
unlessString和condition相反
syncboolean是否开启同步功能、默认不开启
  • @CachePut
    执行并且更新缓存相关 不管如何 肯定会执行方法 然后返回 这样可以更新缓存的内容
属性类型功能
valueString[]缓存的名称 和cacheNames功能一样
cacheNamesString[]缓存的名称和value功能一样
keyString缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
conditionString根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
unlessString和condition相反
  • @CacheEvict
    删除缓存相关
属性类型功能
valueString[]缓存的名称 和cacheNames功能一样
cacheNamesString[]缓存的名称和value功能一样
keyString缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
conditionString根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
allEntriesboolean是否删除所有键的缓存 默认不删除
beforeInvocationboolean是否在调用此方法前 删除缓存
  • @CacheConfig
    在类级别统一的配置缓存公共配置
属性类型功能
cacheNamesString[]缓存的名称和value功能一样
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
  • @EnableCaching
    开启缓存以及缓存的全局配置
属性类型功能
proxyTargetClassboolean是否要基于cglib生成代理去实现缓存
modeAdviceMode配置那种模式去实现缓存、默认是AdviceMode.PROXY 可以切换为 AdviceMode#ASPECTJ
orderint设置缓存管理器执行的顺序
  • @Caching
    对多个缓存组的配置
属性类型功能
cacheableCacheable配置获取缓存相关的配置
putCachePut配置如何更新缓存的相关配置
evictCacheEvict配置如何删除缓存的相关配置

注解介绍引用: https://www.jianshu.com/p/fd950f65aec7

第五步:在启动类配置开启缓存

image.png

第六步:运行查看效果

controller

image.png
查看redis客户端存储的数据

image.png
image.png

 

转自:

链接:https://juejin.im/post/5adc6c0c518825673123cb5c

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值