SpringBoot(九) 缓存

内容概览:JSR-107、Spring缓存抽象、整合Redis

1、JSR-107简介

Java Caching定义五个核心接口:CachingProvider、CacheManager、Cache、Entry和Expiry;

  • CachingProvider 定义了创建,配置,获取,管理和控制多个CacheManager,一个应用可以在运行期间访问多个CacheProvider;
  • CacheManager 定义了创建、配置、获取,管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有;
  • Cache是一个类似于Map的数据结构,并临时存储以key为索引的值,一个Cache仅被一个CacheManager所拥有;
  • Entry是一个存储在Cache中的key-value对;
  • Expiry是每一个存储在Cache的条目中的的一个有效期,一旦超过这个时间,条目为过期状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置;

在这里插入图片描述

如果要使用,需要导入下面的依赖:

<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.1</version>
</dependency>

2、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用Spring缓存抽象时我们需要关注以下两点;
    • 1、确定方法需要被缓存以及他们的缓存策略;
    • 2、从缓存中读取之前缓存存储的数据。

3、几个重要概念&缓存注解

Cache缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@CachePut保证方法被调用,又希望结果被缓存。
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

3.1 @Cacheable/@CachePut/@CacheEvict 主要的参数

属性名称作用举例
value缓存的名称,在spring 配置文件中定义,必须指定至少一个例如:@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}
key缓存的key,可以为空,如果指定 要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用SpEL 编写,返回true 或者false,只有为true 才进行缓存/清除缓存例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries(@CacheEvict )是否清空所有缓存内容,缺省为false,如果指定为true,则方法调用后将立即清空所有缓存例如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation(@CacheEvict)是否在方法执行前就清空,缺省为false,如果指定为true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存例如:@CachEvict(value=”testcache”,beforeInvocation=true)

#4、演示缓存

4.1 环境搭建

使用SpringBoot的向导创建一个工程:springboot-01-cache,选中web(为了测试)和Spring cache abstraction ,还有mysql和mybatis模块。

4.1.1 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springboot.cache</groupId>
    <artifactId>springboot-01-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-01-cache</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--缓存模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis模块-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
     
        <!--mysql模块-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!--   json 在使用StringRedisTemplate存储的时候,可以把对象转成json存入redis,下次取出的时候再把json转成对象    -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4.1.2 创建数据库环境

在本地的mysql创建cache数据库,执行下面创建表和添加数据的sql:


CREATE TABLE `department` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `departmentName` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO department (departmentName) VALUES ('开发部');


CREATE TABLE `employee` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `lastName` VARCHAR(255) DEFAULT NULL,
  `email` VARCHAR(255) DEFAULT NULL,
  `gender` INT(2) DEFAULT NULL,
  `d_id` INT(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO employee (lastName,email,gender,d_id) VALUES ('张三','zhangsan@qq.com',1,1);

4.1.3 整合MyBatis

1、application.yml配置文件
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cache?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true  #开启驼峰命名
    
logging:
  level:
    com.springboot.cache.mapper: debug  #控制台打印sql
2、主程序类配置扫描Mapper

@MapperScan指定需要扫描的mapper接口所在的包

package com.springboot.cache;

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

//@MapperScan指定需要扫描的mapper接口所在的包
@MapperScan("com.springboot.cache.mapper")
@SpringBootApplication
public class Springboot01CacheApplication {

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

}
3、创建Mapper,Service和Controller

EmployeeMapper:因为配置了@MapperScan(“com.springboot.cache.mapper”),所以不需要单独添加@Mapper注解:

package com.springboot.cache.mapper;

import com.springboot.cache.bean.Employee;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

public interface EmployeeMapper {
    @Select("SELECT * FROM employee WHERE id = #{id}")
    public Employee getEmpById(Integer id);

    @Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
    public void updateEmp(Employee employee);

    @Delete("DELETE FROM employee WHERE id=#{id}")
    public void deleteEmpById(Integer id);

    @Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
    public void insertEmployee(Employee employee);

    @Select("SELECT * FROM employee WHERE lastName = #{lastName}")
    Employee getEmpByLastName(String lastName);
}

EmployeeSrivice:

package com.springboot.cache.service;

import com.springboot.cache.bean.Employee;
import com.springboot.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    public Employee getEmp(Integer id){
         System.out.println("从数据库查询"+id+"号员工");
        Employee employee = employeeMapper.getEmpById(id);
        return employee;
    }
}

EmployeeController:

package com.springboot.cache.controller;

import com.springboot.cache.bean.Employee;
import com.springboot.cache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getById(@PathVariable("id") Integer id){ 
        return employeeService.getEmp(id);
    }
}

4.1.4 测试

启动工程,浏览器访问:http://localhost:8080/emp/1,能正常的查询到数据说明环境整合成功。

4.2 演示缓存

缓存使用,只需要简单的两步:

1、开启基于注解的缓存 :在主程序类上标注:@EnableCaching

2、在对应的的方法上加上缓存注解:@Cacheable、@CacheEvict、@CachePut

4.2.1 开启基于注解的缓存

在主程序类上添加注解 @EnableCaching:

package com.springboot.cache;

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

//@MapperScan指定需要扫描的mapper接口所在的包
@MapperScan("com.springboot.cache.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot01CacheApplication.class, args);
    }
}

4.2.2 @Cacheable

在service的方法上添加注解@Cacheable

@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
    System.out.println("从数据库查询"+id+"号员工");
    Employee employee = employeeMapper.getEmpById(id);
    return employee;
}

@Cacheable 的属性:

属性名说明
cacheNames/value指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
key缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值,编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0]
keyGeneratorkey的生成器;可以自己指定key的生成器的组件id,key/keyGenerator:二选一使用;
cacheManager指定缓存管理器;或者cacheResolver指定获取解析器
condition指定符合条件的情况下才缓存; condition = “#id>0”,condition = “#a0>1”:第一个参数的值>1的时候才进行缓存
unless否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断:unless = “#result == null”,unless = “#a0==2”:如果第一个参数的值是2,结果不缓存;
sync是否使用异步模式,设置为true的时候,unless就会失效。

在这里插入图片描述

4.3 缓存原理分析

  • 1、自动配置类;CacheAutoConfiguration

  • 2、缓存的配置类

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
  • 3、哪个配置类默认生效:SimpleCacheConfiguration;
  • 4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
  • 5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;

运行流程:以@Cacheable注解为例:
1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;

  •              ​           如果没有参数;key=new SimpleKey();
    
  •              ​           如果有一个参数:key=参数的值
    
  •              ​           如果有多个参数:key=new SimpleKey(params);
    

3、没有查到缓存就调用目标方法;
4、将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存, 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;

核心:
* 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
* 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator

4.4 自定义缓存Key的生成策略

可以定义一个缓存配置类,往容器中注册一个Bean,里面定义Key的生成规则,到时候就会使用我们的:

package com.springboot.cache.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 缓存的配置类
 */
@Configuration
public class MyCacheConfig {

    //自定义缓存key的生成策略
    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }
}

在@Cacheable注解中使用:

@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")

4.5 @CachePut

@CachePut:既调用方法,又更新缓存数据;同步更新缓存;修改了数据库的某个数据,同时更新缓存;

运行时机:1、先调用目标方法;2、将目标方法的结果缓存起来

测试步骤:

1、查询1号员工;查到的结果会放在缓存中

2、以后查询还是之前的结果

3、更新1号员工;【lastName:zhangsan;gender:0】,将方法的返回值也放进缓存了;key:传入的employee对象 值:返回的employee对象;

4、查询1号员工?应该是更新后的员工;key = “#employee.id”:使用传入的参数的员工id;key = “#result.id”:使用返回后的id

要保证查询和更新员工,缓存的key要相同,否则,不同key取不到缓存的数据。

@Cacheable的key是不能用#result

keyGenerator = "myKeyGenerator 要注释掉

@Cacheable(cacheNames = "emp"/*,keyGenerator = "myKeyGenerator"*/)
public Employee getEmp(Integer id){
    System.out.println("从数据库查询"+id+"号员工");
    Employee employee = employeeMapper.getEmpById(id);
    return employee;
}

@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
    System.out.println("updateEmp:"+employee);
    employeeMapper.updateEmp(employee);
    return employee;
}
@GetMapping("/emp")
public Employee update(Employee employee){
    Employee emp = employeeService.updateEmp(employee);
    return emp;
}

4.6 @CacheEvict

清楚缓存的作用:

key:指定要清除的数据;

allEntries = true:指定清除这个缓存中所有的数据;

beforeInvocation = false:缓存的清除是否在方法之前执行,默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除

beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除

/**
 * @CacheEvict:缓存清除
 *  key:指定要清除的数据
 *  allEntries = true:指定清除这个缓存中所有的数据
 *  beforeInvocation = false:缓存的清除是否在方法之前执行
 *      默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
 *
 *  beforeInvocation = true:
 *      代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
 *
 *
 */
@CacheEvict(value="emp",beforeInvocation = true/*,key = "#id"*/)
public void deleteEmp(Integer id){
    System.out.println("deleteEmp:"+id);
    //employeeMapper.deleteEmpById(id);
    int i = 10/0;
}
@GetMapping("/delemp")
public String deleteEmp(Integer id){
    employeeService.deleteEmp(id);
    return "success";
}

4.7 Caching

定义复杂的缓存规则:

@Caching(
    cacheable = {
        @Cacheable(/*value="emp",*/key = "#lastName")
    },
    put = {
        @CachePut(/*value="emp",*/key = "#result.id"),
        @CachePut(/*value="emp",*/key = "#result.email")
    }
)
public Employee getEmpByLastName(String lastName){
    return employeeMapper.getEmpByLastName(lastName);
}
@GetMapping("/emp/lastname/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
    return employeeService.getEmpByLastName(lastName);
}

4.8 @CacheConfig

@CacheConfig(cacheNames=“emp”/,cacheManager = “employeeCacheManager”/) //抽取缓存的公共配置

可以在类上加上该注解:

@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@Service
public class EmployeeService {

5、整合Redis

默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>中;

开发中使用缓存中间件;redis、memcached、ehcache;

我们要想使用Redis作为缓存,只需要导入Redis坐标就可以了;

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

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

5.1 安装redis

1、安装redis:使用docker;

执行命令:docker pull redis (也可以使用:docker pull registry.docker-cn.com/library/redis,registry.docker-cn.com/library 是docker中国的镜像加速)

查看镜像:docker images

[root@localhost ~]# docker images
REPOSITORY          TAG                         IMAGE ID            CREATED             SIZE
docker.io/redis     latest                      4cdbec704e47        6 days ago          98.2 MB
docker.io/tomcat    jdk8-adoptopenjdk-hotspot   afc0ee76aa56        2 weeks ago         324 MB
docker.io/mysql     5.6                         c8078e8ab06d        4 weeks ago         303 MB

启动redis,执行命令:docker run -d -p 6379:6379 --name myredis 4cdbec704e47

[root@localhost ~]# docker run -d -p 6379:6379 --name myredis 4cdbec704e47
507e7ec030fb99092c130d93cc5aa559f9107edf34f1270c090e93f7a0412566
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
507e7ec030fb        4cdbec704e47        "docker-entrypoint..."   25 seconds ago      Up 24 seconds       0.0.0.0:6379->6379/tcp   myredis
[root@localhost ~]# 

启动成功了,就可以使用redis的客户端连接工具连接:

5.2 引入redis组件

在pom文件中,添加redis的启动器坐标

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

5.3 配置redis

5.3.1 application.yml

在application.yml中配置redis的主机和端口,192.168.0.108 是我们的虚拟机的ip;

spring:
  redis:
    host: 192.168.0.108
    port: 6379
    # Redis数据库索引(默认为0)
    database: 1
    jedis:
      pool:
        #连接池最大连接数
        max-active: 8
        #最小空闲连接
        min-idle: 0
        #最大阻塞等待时间,负值表示没有限制
        max-wait: -1ms
        #最大空闲连接
        max-idle: 8
    #连接超时时间(毫秒)
    timeout: 20ms
    # 无密码可不写
    # password:

5.3.2 自定义RedisTemplate

当我们引入Redis组件之后,SpringBoot会自动帮我们配置RedisTemplate(k-v都是对象的)和StringRedisTemplate(操作k-v都是字符串的),但是RedisTemplate默认使用的是JdkSerializationRedisSerializer,这样序列化的数据就是编码之后的,不易读。所以我们需要自定义RedisTemplate,使用Jackson2JsonRedisSerializer,这样保存对象就不是编码的,是json类型的数据。

package com.springboot.cache.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
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.RedisSerializationContext;

import java.time.Duration;

/**
 * 缓存配置类
 * 用于注解的缓存配置,如 @Cacheable 的序列化配置
 *
 * 说明:
 * 我们 redisUtils 序列化方式采用 json序列化
 * @Cacheable 默认序列化方式为 二进制的
 * 两个不能混用,为了解决这个问题,这里设置 @Cacheable 默认序列化方式为 json
 */
@Configuration
public class MyRedisConfig extends CachingConfigurerSupport {

    @Bean("myRedisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //默认是使用JDK的序列化,我们这里要设置使用Jackson的序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);

        return redisTemplate;
    }

    /**
     * 设置 redis 数据默认过期时间
     * 设置@cacheable 序列化方式,默认是以jdk的二进制序列化的,我们设置成是以json进行序列化的
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        Jackson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(30));
        return configuration;
    }
}

5.4 redis的自动配置原理

我们引入了redis的启动器之后,Redis的自动配置就生效了,SpringBoot就自动为我们配置RedisTemplate(k-v都是对象的)和StringRedisTemplate(操作k-v都是字符串的);

RedisAutoConfiguration的源码如下:

import java.net.UnknownHostException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean( name = {"redisTemplate"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

因为SpringBoot 约定大于配置的特点,只要我们加入了 spring-data-redis 依赖包并配置 Redis 数据库,SpringBoot 就会帮我们自动配置一个 RedisTemplate ,利用它我们就可以按照以下方式操作对应的数据类型,在下面实战中我将会对这五种数据进行操作。

\1. redisTemplate.opsForValue(); //操作字符串

\2. redisTemplate.opsForHash(); //操作hash

\3. redisTemplate.opsForList(); //操作list

\4. redisTemplate.opsForSet(); //操作set

\5. redisTemplate.opsForZSet(); //操作有序set

5.5 EmployeeController

package com.springboot.cache.controller;

import com.springboot.cache.bean.Employee;
import com.springboot.cache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getById(@PathVariable("id") Integer id){
        return employeeService.getEmp(id);
    }

    @GetMapping("/emp")
    public Employee update(Employee employee){
        Employee emp = employeeService.updateEmp(employee);
        return emp;
    }

    @GetMapping("/delemp")
    public String deleteEmp(Integer id){
        employeeService.deleteEmp(id);
        return "success";
    }

    @GetMapping("/emp/lastname/{lastName}")
    public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
        return employeeService.getEmpByLastName(lastName);
    }
}

5.6 测试

启动工程,浏览器访问:http://localhost:8080/emp/1 可以访问到数据,再去redis的客户端连接工具查看,发现员工信息也存入了redis中,并且是以json进行序列化的:

在这里插入图片描述

也可以通过测试类,手动的往redis中存取数据:

package com.springboot.cache;

import com.springboot.cache.bean.Employee;
import com.springboot.cache.mapper.EmployeeMapper;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

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

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    EmployeeMapper employeeMapper;

    @Autowired
    RedisTemplate myRedisTemplate;

    @Test
    public void contextLoads() {
        myRedisTemplate.opsForValue().set("zhoujie","中介家里就可怜见");
    }

        //测试stringRedisTemplate保存对象
    @Test
    public void test01(){
//        Employee empById = employeeMapper.getEmpById(1);
//        String empStr = JSONObject.toJSONString(empById);
//        stringRedisTemplate.opsForValue().append("empstr",empStr);
        String empstr = stringRedisTemplate.opsForValue().get("empstr");
        Employee employee = JSON.parseObject(empstr, Employee.class);
        System.out.println(employee);
    }
    
    //测试保存对象
    @Test
    public void test02(){
        Employee empById = employeeMapper.getEmpById(1);
        Employee empById2 = employeeMapper.getEmpById(2);
        //默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中

        List<Employee> list = new ArrayList<>();
        list.add(empById);
        list.add(empById2);
        //myRedisTemplate.opsForValue().set("emp-02",empById);
        //myRedisTemplate.opsForList().leftPush("empList2",list);

        Object empList2 = myRedisTemplate.opsForList().leftPop("empList2");
        System.out.println(empList2);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值