SpringBoot与Redis缓存


准备

在Docker安装Redis

ATwKnP.png

ATw8hQ.png

连接成功

ATw639.png

对于Redis不熟悉的同学可以在本站搜索Redis的文章阅读。

整合Redis

在pom文件中加入

 <!--引入Redis-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    //简化操作KV 都是对象的
    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;
    }

}

准备

package com.hph.cache;

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;

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

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate redisTemplate;

    /**
     * Redis常用的数据类型
     * String(字符串)、List(列表)、Set(集合)、Hash(散列)、(有序集合)
     * String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
     * stringRedisTemplate.opsForValue()[String(字符串)]
     * stringRedisTemplate.opsForList()[List(列表)]
     * stringRedisTemplate.opsForSet()[Set(集合)]
     * stringRedisTemplate.opsForHash()[Hash(散列)]
     * stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
     */
    @Test
    public void redis() {
        stringRedisTemplate.opsForValue().append("msg","hello");

    }
}

ATsOk6.png

读取数据

更新原有数据

ATyCnA.png

    @Test
    public void getmesg() {
        String msg = stringRedisTemplate.opsForValue().get("msg");
        System.out.println(msg);
    }

ATyMBn.png

@Test
public void listops(){
     stringRedisTemplate.opsForList().leftPush("mylist","1");
     stringRedisTemplate.opsForList().leftPush("mylist","2");
     stringRedisTemplate.opsForList().leftPush("mylist","3");
     stringRedisTemplate.opsForList().leftPush("mylist","4");
}

ATyRud.png

    @Test
    public void cachObject(){
        Employee empById = employeeMapper.getEmpById(1);
        redisTemplate.opsForValue().set("emp-001",empById);
    }

AT6K2D.png

序列化

报错Employee需要序列化。

public class Employee implements Serializable 

AT6Dqs.md.png

RedisTemplate默认的是JdkSerializationRedisSerializer的序列化器。

    if (defaultSerializer == null) {
            defaultSerializer = new JdkSerializationRedisSerializer(
                    classLoader != null ? classLoader : this.getClass().getClassLoader());
        }

我们可以使用RedisSerializer的序列化器设置默认的序列化器。

    public void setDefaultSerializer(RedisSerializer<?> serializer) {
        this.defaultSerializer = serializer;
    }

ATcs6e.png

自定义序列化器

package com.hph.cache.config;

import com.hph.cache.bean.Employee;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(ser);
        return template;
    }
}
    @Autowired
    RedisTemplate<Object, Employee> empRedisTemplate;
    @Test
    public void ObjectToJsonRedis(){
        Employee empById = employeeMapper.getEmpById(1);
        //默认保存对象使用jdk序列化机制,序列化的数据保存在redis中
        empRedisTemplate.opsForValue().set("emp-001",empById);
    }

成功

ATg63T.png

好的我们开始运行以下看看使用Redis缓存的效果。

package com.hph.cache.service;

import com.hph.cache.bean.Employee;
import com.hph.cache.mapper.EmployeeMapper;
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.cache.annotation.Caching;
import org.springframework.stereotype.Service;

@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;

    //condition = "#a0>1 第一个参数的值>>1的时候可以进行缓存 除非a0参数是2
    @Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")
    public Employee getEmp(Integer id) {
        System.out.println("查询" + id + "号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }

    /**
     * @CachePut即调用方法,又更新缓存数据 修改了数据库的某个数据, 同时更新缓存
     * 运行时机:先调用目标方法,现将目标方法的结果缓存起来
     */
    @CachePut(value = "emp", key = "#result.id ")
    public Employee updateEmp(Employee employee) {
        System.out.println("updateEmp" + employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }

    /**
     * @Cachevict :缓存清除
     * key: 指定要清除的数据
     */
    //缓存默认清除才足在方法执行之后执行;如果出现异常缓存不会被清除
    @CacheEvict(value = "emp", beforeInvocation = true)
    public void deleteEmp(Integer id) {
        System.out.println("deletEmp" + id);
        //  employeeMapper.deleteEmpById(id);
        int i = 10 / 0;
    }
}

流程分析

原理:CacheManager==某一个Cache缓存组件来给实际给缓存中存取得数据,比如我们使用Redis缓存,那么SimpleCacheManager就不匹配进而不能使用,从而使用Redis

ATbUJg.png

ATb6oT.png

引入Redis的starter,容器中保存的是RedisCcheManager;

RedisCacheManager帮我们创建RedisCache作为缓存组件,RedisCache通过Redis缓存数据。

ATq81J.png

我们可以看到SQL只执行了一次。

ATqtn1.png

Redis中已经缓存我们查询过的数据。

默认保留数据的K-V都是Object;利用序列化保存可以保存为Json。

引入了redis的stater,cacheManager变为RedisCacheManager;默认创建的RedisCacheManager

    @Bean
    public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }
        return this.customizerInvoker.customize(cacheManager);
    }

RedisCacheManager操作Redis使用的是RedisTemplate默认使用的是JdkSerializationRedisSerializer

    if (defaultSerializer == null) {

            defaultSerializer = new JdkSerializationRedisSerializer(
                    classLoader != null ? classLoader : this.getClass().getClassLoader());
        }

缓存管理器

接下来我们定制以下缓存管理器。

package com.hph.cache.config;

import com.hph.cache.bean.Employee;
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 java.net.UnknownHostException;

@Configuration
public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(ser);
        return template;
    }

    //CacheManagerCustomizers可以定制缓存的一些规则。
    @Bean
    public RedisCacheManager  employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
        RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

}

而创建缓存管理器的条件是。

@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)  //没有CacheManager的时候创建RedisCacheConfiguration
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

运行结果。

ATX81g.png

我们在创建一个关于Department的缓存查询。

package com.hph.cache.bean;

public class Department {
    
    private Integer id;
    private String departmentName;
    
    
    public Department() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Department(Integer id, String departmentName) {
        super();
        this.id = id;
        this.departmentName = departmentName;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getDepartmentName() {
        return departmentName;
    }
    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }
    @Override
    public String toString() {
        return "Department [id=" + id + ", departmentName=" + departmentName + "]";
    }

}
package com.hph.cache.mapper;

import com.hph.cache.bean.Department;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface DeptMapper {
    @Select("SELECT * FROM department WHERE id = #{id}")
    Department getDeptById(Integer id);
}
package com.hph.cache.service;

import com.hph.cache.bean.Department;
import com.hph.cache.mapper.DeptMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class DeptService {
    @Autowired
    DeptMapper deptMapper;

    @Cacheable(cacheNames = "dept")
    public Department getDeptById(Integer id) {
        System.out.println("查询部门" + id);
        Department department = deptMapper.getDeptById(id);

        return department;
    }
}
package com.hph.cache.controller;

import com.hph.cache.bean.Department;
import com.hph.cache.service.DeptService;
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.RestController;

@RestController
public class DeptContorller {

    @Autowired
    DeptService deptService;

    @GetMapping("/dept/{id}")
    public Department getDept(@PathVariable("id") Integer id) {
        return deptService.getDeptById(id);
    }
}

ATjaKH.png

ATjBVI.png

然而当我们再次请求缓存的时候。

ATjDat.png

5个属性要映射,这是因为我们自定义的那个缓存管理器是属于员工的而不是部门,因此字段对应不上去。同时我们也发现了缓存数据第一次可以存入Redis,第二次从缓存中查询就不能够反序列化回来了。这是因为当我们存储dept的json数据时,CacheManager默认使用的是RedisTemplate<Object,Employee>操作Redis,只能将Employee数据反序列化。

我们需要配置以下缓存管理器。

   @Bean
    public RedisTemplate<Object, Department> deptRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Department> ser = new Jackson2JsonRedisSerializer<Department>(Department.class);
        template.setDefaultSerializer(ser);
        return template;
    }

    @Bean
    public RedisCacheManager  deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate){
        RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

给Employee Dept分别指定缓存类别

@CacheConfig(cacheNames = "emp",cacheManager = "employeeCacheManager")
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;
    ....
}
@Service
public class DeptService {
    @Autowired
    DeptMapper deptMapper;

    @Cacheable(cacheNames = "dept",cacheManager = "deptCacheManager")
    public Department getDeptById(Integer id) {
        System.out.println("查询部门" + id);
        Department department = deptMapper.getDeptById(id);
        return department;
    }
}

ATzTit.png

在MyRedisConfig中指定一个主类

    @Primary
    @Bean
    public RedisCacheManager  employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
        RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

我们把Rdis的数据清空下

A79E5R.png

由于默认指定的是employeeCacheManager我们可以在

@CacheConfig(cacheNames = "emp")EmployeeService上省略。
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;

在实际开发中不应该将employeeCacheManager作为默认指定,应该将RedisCacheManager作为默认指定。@Primary将某个缓存管理器作为默认的

另外我们可以选择编码的方式l来配置缓存管理器。

在DeptService中。

    @Qualifier("deptCacheManager")
    @Autowired
    RedisCacheManager deptCacheManager;

    public Department getDeptById(Integer id) {
        System.out.println("查询部门" + id);
        Department department = deptMapper.getDeptById(id);
        //获取某个缓存
        Cache dept = deptCacheManager.getCache("dept");
        dept.put("缓存操作1", department);
        return department;
    }

A7Ptu8.png


文章作者: 清风笑丶
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 清风笑丶 !
 上一篇
消息队列RabbitMQ 消息队列RabbitMQ
消息队列(Message Queue)消息: 网络中的两台计算机或者两个通讯设备之间传递的数据。例如说:文本、音乐、视频等内容。 队列:一种特殊的线性表(数据元素首尾相接),特殊之处在于只允许在首部删除元素和在尾部追加元素。入队、出队。 消
2019-04-11
下一篇 
SpringBoot和缓存 SpringBoot和缓存
简介JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增
2019-04-09
  目录