Spring Redis 整合

spring cache不是具体的缓存解决方案,而是对缓存操作的抽象。可以这么理解:spring cache是对缓存增删改查这些操作的抽象,按照spring cache提供的api可以很方便地操作缓存。

本文使用spring + mybatis + redis。不建议使用mybatis的二级缓存。理由如下:

        1、单表更新可能导致复杂的多表查询读取到脏数据。

        2、缓存的key可读性极差,出现bug时不好调试。如下图:

                

 

使用spring提供的@Cacheable、@CachePut、@CacheEvict可以很方便的操作缓存

@Cacheable 用在查询方法上。当标记在方法上时表示该方法支持缓存,Spring会将此方法的返回值缓存起来。下一次执行时,能在缓存中读取到数据就直接返回缓存数据,不再执行方法体里面的代码。

@CachePut 用在增加、更新方法上。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CacheEvict 用在删除方法上。删除缓存。

介绍这些注解中的两个元素

value:缓存的名称,必须指定。在redis中是一个有序集合zset

key: 缓存key,不填写则按照keyGenerator()来生成,建议手动填写key,不用keyGenerator()生成的key。如果指定要按照 SpEL 表达式编写。

写这篇博文的唯一目的是教大家使用spel编写合理、可读性强的缓存key。spel表达式:@表示查找IOC容器中的bean,#引用参数

项目代码地址:https://pan.baidu.com/s/1jwzHLMR1MKcexySTARuYsw

开始上代码

新建spring boot项目,pom.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>cache</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>cache</name>
  <description>Demo project for Spring Boot</description>

  <!-- 定义公共资源版本 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.6.RELEASE</version>
    <relativePath />
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <!--测试-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <!--database-->
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.44</version>
    </dependency>

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

  </dependencies>

  <build>
    <resources>
      <resource>
        <!--解决resource中的资源文件不打包-->
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
        </includes>
        <filtering>false</filtering>
      </resource>
      <resource>
        <!--解决mapper.xml不打包-->
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>

    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!--该配置必须-->
          <fork>true</fork>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

application.yml 配置如下

server:
   port: 8080
logging:
  level:
    root: info, WARN, DEBUG
    com:
      example:
        cache: DEBUG  #打印sql

spring:
  # 数据库配置
  datasource:
    url: jdbc:mysql://localhost:3306/cpq
    username: root
    password: cpq..123
    driver-class-name: com.mysql.jdbc.Driver

  redis:
    # redis数据库索引(默认为0)
    database: 0
    # redis服务器地址(默认为localhost)
    host: localhost
    # redis端口(默认为6379)
    port: 6379
    # redis访问密码(默认为空)
    password:
    # redis连接超时时间(单位为毫秒)
    timeout: 10000
    # redis连接池配置
    pool:
      # 最大可用连接数(默认为8,负数表示无限)
      max-active: 8
      # 最大空闲连接数(默认为8,负数表示无限)
      max-idle: 8
      # 最小空闲连接数(默认为0,该值只有为正数才有作用)
      min-idle: 0
      # 从连接池中获取连接最大等待时间(默认为-1,单位为毫秒,负数表示无限)
      max-wait: -1

启动类配置如下:

package com.example.cache;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("com.example.cache.*.mapper")  //扫描mapper
@EnableAutoConfiguration
@SpringBootApplication
public class CacheApplication {

  public static void main(String[] args) {
    SpringApplication.run(CacheApplication.class, args);
  }
}

缓存配置类如下

package com.example.cache.redis;


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
@EnableCaching  //开启缓存
public class RedisConfig extends CachingConfigurerSupport {

    //不建议使用,默认key生成策略为类全名+方法名+参数
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            //StringBuffer是线程安全的
            StringBuffer sb = new StringBuffer();
            sb.append(target.getClass().getName());
            sb.append(":");
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(":");
                sb.append(obj.toString());
            }
            return sb.toString().replaceAll("\\.", ":");
        };
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        //设置缓存过期时间,单位是秒
        cacheManager.setDefaultExpiration(60*60*24*30);
        return cacheManager;
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);

        //key使用StringRedisSerializer
        StringRedisSerializer strSerializer = new StringRedisSerializer();
        template.setKeySerializer(strSerializer);
        template.setHashKeySerializer(strSerializer);

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

        //value使用Jackson2JsonRedisSerializer
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        return template;
    }

}

新建一个存储redis key的常量类

package com.example.cache.redis;

import org.springframework.stereotype.Component;

//定义redis key的前缀常量类,使用冒号分割
@Component  //此类必须注入到ioc中才能被spel表达式通过@找到
public class RedisConst {

    public static final String SPRING_CACHE = "SPRING:CACHE:";

}

mode和mapper使用generator生成的,model的sql

CREATE DATABASE /*!32312 IF NOT EXISTS*/`cpq` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `cpq`;

DROP TABLE IF EXISTS `spring_cache`;

CREATE TABLE `spring_cache` (
  `id` varchar(36) COLLATE utf8_unicode_ci NOT NULL,
  `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  `num` int(11) NOT NULL,
  `date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

 

SpringCacheService.java类如下

 

package com.example.cache.springcache.service;

import com.example.cache.redis.RedisConst;
import com.example.cache.springcache.mapper.SpringCacheMapper;
import com.example.cache.springcache.model.SpringCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class SpringCacheService {

    @Autowired
    SpringCacheMapper springCacheMapper;

    /**
     * 插入数据
     * key: @表示查找IOC容器中的bean,#引用参数
     */
    @CachePut(value = RedisConst.SPRING_CACHE, key = "@redisConst.SPRING_CACHE + #record.getId()")
    //@CachePut(value = RedisConst.SPRING_CACHE)  //不建议省略key
    public SpringCache insertSelective(SpringCache record){
        springCacheMapper.insertSelective(record);
        return record;
    }

    //更新
    @CachePut(value = RedisConst.SPRING_CACHE, key = "@redisConst.SPRING_CACHE + #record.getId()")
    public SpringCache updateByPrimaryKey(SpringCache record){
        springCacheMapper.updateByPrimaryKey(record);
        return record;
    }


    //查找
    @Cacheable(value = RedisConst.SPRING_CACHE, key = "@redisConst.SPRING_CACHE + #id" )
    public SpringCache selectByPrimaryKey(String id){
        System.out.println("******************没找到缓存,执行本方法******************");
        return springCacheMapper.selectByPrimaryKey(id);
    }

    //删除
    /****** 增删改查的key都是"SPRING:CACHE:实际id"  *****/
    @CacheEvict(value = RedisConst.SPRING_CACHE, key = "@redisConst.SPRING_CACHE + #id")
    public int deleteByPrimaryKey(String id){
        return springCacheMapper.deleteByPrimaryKey(id);
    }

    /**
     * 清空RedisConst.SPRING_CACHE的缓存
     *
     * 执行过程:通过RedisConst.SPRING_CACHE找到一个zset,zset中存储了此缓存下的key。
     */
    @CacheEvict(value = RedisConst.SPRING_CACHE, allEntries=true)
    public void deleteAllEntries(){

    }

}

测试类

package com.example.cache.springcache;

import com.example.cache.springcache.model.SpringCache;
import com.example.cache.springcache.service.SpringCacheService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.Random;
import java.util.UUID;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test1 {

    @Autowired
    SpringCacheService springCacheService;

    //测试插入
    @Test
    public void insert(){
        SpringCache springCache = new SpringCache();
        springCache.setId(UUID.randomUUID().toString());
        springCache.setName("name1111");
        springCache.setNum(new Random().nextInt(100));
        springCache.setDate(new Date());
        springCacheService.insertSelective(springCache);
    }

    //测试、更新
    @Test
    public void update(){
        SpringCache springCache = new SpringCache();
        springCache.setId("f143a4c7-536c-4cc2-b82d-6258a56ecd7a");
        springCache.setName("name改变");
        springCache.setNum(new Random().nextInt(100));
        springCache.setDate(new Date());
        springCacheService.updateByPrimaryKey(springCache);
    }

    //测试、获取
    @Test
    public void get(){
        SpringCache sc = springCacheService.selectByPrimaryKey("aae78c61-81af-4471-bfce-06e5c09f364b");
        System.out.println(sc);
    }

    //测试、删除
    @Test
    public void delete(){
        springCacheService.deleteByPrimaryKey("4c77c71c-c2c9-4803-9d1f-dbe8a3ba5a15");
    }

    //测试、删除缓存名下的所有缓存
    @Test
    public void deleteAll(){
        springCacheService.deleteAllEntries();
    }

}

执行多次insert之后,我们看下redis中的数据

 

 

@Cacheable、@CachePut、@CacheEvict注解中value、key:

        value在redis中也是一个的键,键指向一个zset

        key直接指向SpringCacheService.java方法返回的数据,zset中存储了key

通过上图也可以看出,使用

@CachePut(value = RedisConst.SPRING_CACHE, key = "@redisConst.SPRING_CACHE + #record.getId()")

@Cacheable(value = RedisConst.SPRING_CACHE, key = "@redisConst.SPRING_CACHE + #id" )

这种方式编写key,可读性很强,尤其在生产环境中查找问题会非常方便

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值