07-Redis在SpringBoot工程中的综合应用

本文详细介绍了如何在Spring Boot中使用RedisTemplate进行Redis操作,包括StringRedisTemplate和RedisTemplate的基本操作,以及如何自定义RedisTemplate以实现序列化。此外,还展示了如何通过AOP实现缓存一致性,并在Service层和Controller层进行缓存优化。最后,讨论了Redis集群配置以及本地缓存的一致性问题。
摘要由CSDN通过智能技术生成

目录

RedisTemplate应用

简介

准备工作

准备工作

快速入门实现

StringRedisTemplate 应用

RedisTemplate 应用

定制RedisTemplate对象(拓展)

业务描述

准备工作

初始化数据

添加项目依赖

添加数据库访问配置

业务逻辑代码设计及实现

Domain对象设计

Dao 逻辑对象设计

Service 逻辑对象设计

Controller逻辑对象设计

业务逻辑代码优化

定制RedisTemplate对象

Service中缓存应用优化

Controller中添加本地缓存


RedisTemplate应用







简介

RedisTemplate为SpringBoot工程中操作redis数据库的一个Java对象,此对象封装了对redis的一些基本操作。

准备工作

添加依赖

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

准备工作

第一步:创建工程配置文件application.yml,其内容如下:

spring:
  redis: # redis-cli -c -h 192.168.126.129 -p 8010
    host: 192.168.126.129
    port: 6379
  datasource: #mysql连接
    url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: 123456

#日志配置
logging:
  level:
    com.jt: debug

第二步:创建工程启动类,例如:

package com.jt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

快速入门实现







StringRedisTemplate 应用

StringRedisTemplate 是一个专门用于操作redis字符串类型数据的一个对象,其应用方式如下:

@SpringBootTest
public class StringRedisTemplateTests {
    /**
     * 此对象为操作redis的一个客户端对象,这个对象
     * 对key/value采用了字符串的序列化(StringRedisSerialize)
     * 方式进行,redis
     * 数据的读写操作
     * */
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Test
    void testHashOper01(){
        //1.获取hash操作对象
        HashOperations<String,Object,Object> vo =stringRedisTemplate.opsForHash();

        //2.读写redis数据
        //2.1存储一个对象
        vo.put("user","id","100");
        vo.put("user","username","tony");
        vo.put("user","status","1");
        //2.2获取一个对象
        Object status = vo.get("user","status" );
        System.out.println(status);
        List<Object> user = vo.values("user");
        System.out.println(user);
    }
    
    @Test
    void testStringOper02() throws JsonProcessingException {
        //1.获取字符串操作对象
        ValueOperations<String,String> vo = stringRedisTemplate.opsForValue();

        //2.读写redis中的数据
        Map<String,String> map = new HashMap<>();
        map.put("id","100");
        map.put("title","StringRedisTemplate");
        String jsonStr = new ObjectMapper().writeValueAsString(map);
        vo.set("blog",jsonStr);
         jsonStr = vo.get("blog");
        System.out.println(jsonStr);
        map = new ObjectMapper().readValue(jsonStr,Map.class);
        System.out.println(map);
    }
    
    @Test
    void testStringOper01(){
        //1.获取字符串操作对象
        ValueOperations<String,String> vo = stringRedisTemplate.opsForValue();

        //2.读写redis中的数据
        vo.set("xx","100");
        vo.increment("xx");
        vo.set("yy","200",1, TimeUnit.SECONDS);
        String xx = vo.get("xx");
        String yy = vo.get("yy");
        System.out.println("xx:"+xx);
        System.out.println("yy:"+yy);
    }
    
    @Test
    void testGetConnection(){
        RedisConnection connection =
        stringRedisTemplate.getConnectionFactory().getConnection();
        String ping = connection.ping();
        System.out.println(ping);
    }
}

RedisTemplate 应用

RedisTemplate是一个专门用于实现对远端redis中复杂数据的操作的对应,应用案例如下:

@SpringBootTest
public class RedisTemplateTests {
    //这个对象在springboot工程的RedisAutoConfiguration类中已经做了配置
    //次对象在基于redis存取数据时

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testSetOper01(){
        //1.获取操作set类型数据的操作对象
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        SetOperations so = redisTemplate.opsForSet();
        //2.读写redis数据set类型数据
        so.add("set1","a","b","c","c");
        Set set1 = so.members("set1");

        System.out.println(set1);

    }


    @Test
    void testListOper01(){
        //1.获取操作List类型数据的操作对象
        ListOperations lo = redisTemplate.opsForList();
        //2.读写redis数据hash类型数据

        lo.leftPush("list1","A");
        lo.leftPushAll("list1","B","C","D");
        lo.leftPop("list1");
        List list = lo.range("list1", 0, -1);
        System.out.println(list);
    }


    @Test
    void testHashOper01(){
        //1.获取操作hash类型数据的操作对象
        HashOperations ho = redisTemplate.opsForHash();
        //2.读写redis数据hash类型数据
        Map<String,Object> map = new HashMap<>();
        map.put("id",100);
        map.put("title","spring boot");
        //直接存储一个map
        ho.putAll("blog",map);// hash 存储。序列化
        //存储单个字段,值
        ho.put("blog","content","spring boot redis");
        //取blog对象中id属性的值
        Object o = ho.get("blog", "id");
        System.out.println(o);
        //获取整个blog对象所有的属性和值
        Map blog = ho.entries("blog");// 反序列化
        System.out.println(blog);

    }

    @Test
    void testStringOper01(){
        //自己指定key/value序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        ValueOperations vo = redisTemplate.opsForValue();
        //key和value默认会采用JDK的序列化方式进行存储
        vo.set("token", UUID.randomUUID().toString());
        Object token = vo.get("token");
        System.out.println(token);
    }


    @Test
    void testGetConnection(){
        RedisConnection connection =redisTemplate.getConnectionFactory().getConnection();
        String ping = connection.ping();
        System.out.println(ping);
    }

}

定制RedisTemplate对象(拓展)

对于系统默认的RedisTemplate默认采用的是JDK的序列化机制,假如我们不希望实用JDK的序列化,可以采用的定制RedisTemplate,并采用自己指定的的序列化方式,例如:

package com.jt.redis.config;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //1.构建RedisTemplate对象
        RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>();
        //2.设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //3.定义序列化方式(在这里选择jackson)
        Jackson2JsonRedisSerializer redisSerializer= new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        //设置要序列化的域(属性)
        //any表示任意级别访问修饰符修饰的属性 private,public,protected
        objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        //启动输入域检查(类不能是final修饰的)
        objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        redisSerializer.setObjectMapper(objectMapper);
        //4.设置RedisTemplate的序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(redisSerializer);
        //spring规范中假如修改bean对象的默认特性,建议调用一下afterPropertiesSet()
        redisTemplate.afterPropertiesSet();
       return redisTemplate;
    }
}

创建Blog对象,然后基于RedisTemplate进行序列化实践,Blog代码如下

package com.jt.redis.pojo;

import java.io.Serializable;

public class Blog implements Serializable {//{"id":10,"title":"redis"}
    private static final long serialVersionUID = -6721670401642138021L;
    private Integer id;
    private String title;
    public Blog(){
        System.out.println("Blog()");
    }
    public Blog(Integer id,String title){
        this.id=id;
        this.title=title;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

在RedisTemplateTests类中添加如下单元测试方法,进行测试,例如:

@Test
void testJsonOper() throws JsonProcessingException {
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Blog blog=new Blog(10,"study redis");
    valueOperations.set("blog",blog);//序列化
    blog=(Blog)valueOperations.get("blog");//反序列化
    System.out.println("blog="+blog);
}

业务描述

从一个博客数据库中查询所有的文章标签,然后存储到缓存(Cache),后续查询时可从缓存获取。提高其查询性能。







准备工作







初始化数据

初始化数据库中数据,SQL脚本如下:

DROP DATABASE IF EXISTS `blog`;
CREATE DATABASE `blog` DEFAULT character set utf8mb4;
SET names utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
USE `blog`;

CREATE TABLE `tb_tag` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) NOT NULL COMMENT 'data_id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tb_tag';

insert into `tb_tag` values (null,"mysql"),(null,"redis");

添加项目依赖

在jt-template工程的原有依赖基础上添加mysql数据库访问依赖,例如:

<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

添加数据库访问配置

在项目的配置文件(例如application.yml)中添加数据库访问配置,例如:

spring:
  datasource:
    url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: root

业务逻辑代码设计及实现







Domain对象设计

创建一个Tag类,基于此类型的对象存储Tag(标签信息),代码如下:

package com.jt.blog.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

/**
 * 标签类的设计
 */
@TableName("tb_tag")
public class Tag implements Serializable {
    private static final long serialVersionUID = 4504013456197711455L;
    /**标签id*/
    @TableId(type = IdType.AUTO)
    private Long id;
    /**标签名*/
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Tag{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Dao 逻辑对象设计

创建Tag信息的数据访问接口,代码如下:

package com.jt.blog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.blog.domain.Tag;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface TagMapper
        extends BaseMapper<Tag> {
}

创建单元测试类,TagMapper中的相关方法进行单元测试,例如:

package com.jt.blog.dao;

import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class TagMapperTests {
    @Autowired
    private TagMapper tagMapper;
    @Test
    void testSelectList(){
        List<Tag> tags =
        tagMapper.selectList(null);
        for(Tag t:tags){
            System.out.println(t);
            //System.out.println(t.getId()+"/"+t.getName());
        }
    }
}

Service 逻辑对象设计

设计TagService接口及实现类,定义Tag(标签)业务逻辑。
第一步:定义TagService接口,代码如下:

package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import java.util.List;
public interface TagService {
    /**
     * 查询所有的标签
     * @return
     */
    List<Tag> selectTags();
}

第二步:定义TagServiceImpl类,代码如下:

package com.jt.blog.service.impl;

import com.jt.blog.dao.TagMapper;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TagServiceImpl implements TagService {
    //RedisAutoConfiguration 类中做的RedisTemplate的配置
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private TagMapper tagMapper;
    @Override
    public List<Tag> selectTags() {
        //1.从redis查询Tag信息,redis有则直接返回
        ValueOperations<String,List<Tag>> valueOperations =
        redisTemplate.opsForValue();
        List<Tag> tags=valueOperations.get("tags");
        if(tags!=null&&!tags.isEmpty())return tags;
        //2.从redis没有获取tag信息,查询mysql
        tags = tagMapper.selectList(null);
        //3.将从mysql查询到tag信息存储到redis
        valueOperations.set("tags", tags);
        //4.返回查询结果
        return tags;
    }
}

说明,假如将List存储到redis,此时Tag必须实现Serializable接口。

第三步:定义TagServiceTests单元测试类并进行单元测试,代码如下:

package com.jt.blog.service;

import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class TagServiceTests {
    @Autowired
    private TagService tagService;
    
    @Test
    void testSelectTags(){
        List<Tag> tags=
        tagService.selectTags();
        System.out.println(tags);
    }
}

Controller逻辑对象设计

创建Tag控制逻辑对象,用于处理请求和响应逻辑,代码如下:

package com.jt.blog.controller;

import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/tag")
public class TagController {
    @Autowired
    private TagService tagService;
    
    @GetMapping
    public  List<Tag> doSelectTags(){
      return  tagService.selectTags());//1.redis,2.mysql
    }
}

启动服务,打开浏览器进行访问测试。同时思考,我们是否可以在这个层加一个本地cache。

业务逻辑代码优化





定制RedisTemplate对象

RedisTemplate默认采用的是JDK的序列化方式,假如对系统对序列化做一些调整,可以自己定义RedisTemplate对象,例如:

package com.jt;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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 org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class RedisCacheConfig {
    //代码定制参考RedisAutoConfiguration类
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(
         RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        System.out.println("===redisTemplate===");
        RedisTemplate<Object,Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        //定义redisTemplate对象的序列化方式
        //1.定义key的序列化方式
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        //2.定义Value的序列化方式
        Jackson2JsonRedisSerializer jsonRedisSerializer=
                new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        objectMapper.setVisibility(
                PropertyAccessor.GETTER,
                JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
                objectMapper.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        jsonRedisSerializer.setObjectMapper(objectMapper);
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        //3.redisTemplate默认特性设置(除了序列化,其它原有特性不丢)
        template.afterPropertiesSet();
        return template;
    }
}

Service中缓存应用优化

目标:简化缓存代码的编写
解决方案:基于AOP(面向切面编程)方式实现缓存应用
实践步骤:
第一步:在启动上类添加@EnableCaching注解(开启AOP方式的缓存配置),例如:

@EnableCaching //启动AOP方式的缓存配置
@SpringBootApplication
public class RedisApplication {
 ....
}

第二步:重构TagServiceImpl中的selectTags()方法,方法上使用@Cacheable注解,例如:

@Cacheable(value = "tagCache")
@Override
public List<Tag> selectTags() {
    return tagMapper.selectList(null);
}

其中,@Cacheable描述的方法为AOP中的一个切入点方法,访问这个方法时,系统底层会通过一个拦截器,检查缓存中是否有你要的数据,假如有则直接返回,没有则执行方法从数据库查询数据.

我们还可以定义Redis中key和value的序列化方式,修改key的生成策略,例如:

package com.jt.blog;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
    /**
     * 定义缓存key生成器,不定义也可以使用默认的。
     * @return
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {

                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append("::");
                sb.append(method.getName());
                for(Object param: params){
                    sb.append(param);
                }

                return sb.toString();
            }
        };
    }
    /**
     * 自定义Cache管理器对象,不定义也可以,有默认的,但假如希望基于AOP
     * 方式实现Redis的操作时,按照指定的序列化方式进行序列化,
     * 可以对CacheManager进行自定义。
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(60)) // 60s缓存失效
                // 设置key的序列化方式
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                                      .fromSerializer(new StringRedisSerializer()))
                // 设置value的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                                      .fromSerializer(
                   new Jackson2JsonRedisSerializer<Object>(Object.class)))
                // 不缓存null值
                .disableCachingNullValues();
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }
    
    .....
    
}

其中,写好这个配置类后,可以进行单元测试,检测redis中数据的存储.

第三步:进行单元测试,检测redis中数据的存储.

package com.jt.blog.service;

import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagServiceTests {
    @Autowired
    private TagService tagService;
    @Test
    void testSelectTags(){
        List<Tag> tags=
        tagService.selectTags();
        System.out.println(tags);
    }
}


Service中缓存一致性分析

当我们从数据库查询数据以后,假如将数据存入到了缓存,后续更新了数据库的数据,但假如没有更新缓存就会出现缓存数据与数据库数据不一致的这样的现象,对于这样问题,有时允许在一定时间范围之内存在。假如我们希望在更新了数据库数据以后要更新缓存,如何实现呢?接下来通过一个案例,来演示和解决一下这个问题.
第一步:修改TagService接口,添加相关方法,例如:

package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import java.util.List;
public interface TagService {
    /**
     * 查询所有的标签
     * @return
     */
    List<Tag> selectTags();
    /**
     * 创建一个新的tag对象
     * @param tag
     */
    void insertTag(Tag tag);

    /**
     * 更新tag对象
     * @param tag
     * @return
     */
    Tag updateTag(Tag tag);

    /**
     * 基于id查询tag信息
     * @param id
     * @return
     */
    Tag selectById(Long id);
}

第二步:修改TagServiceImpl类,在类中重写TagService接口方法,例如:

package com.jt.blog.service.impl;

import com.jt.blog.dao.TagMapper;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.List;

@Service
public class TagServiceImpl implements TagService {

    @Autowired
    private TagMapper tagMapper;

    /*@Cacheable注解描述的方法为缓存切入点方法
     *访问此方法时系统底层会先从缓存查找数据,假如缓存缓存没有,
     *会查询mysql数据库,这个注解假如想生效需要在启动类或者配置
     *类上添加@EnableCaching注解.
     *其中,这里的value用于指定一个key前缀,
     *没有指定key属性,则默认会使用 KeyGenerator对象创建key
     */
    @Cacheable(value = "tagCache")
    @Override
    public List<Tag> selectTags() {
        return tagMapper.selectList(null);
    }
    
    /**
     * @CacheEvict注解的作用是定义缓存切入点方法,执行此注解描述的方法
     * 时,底层通过AOP方式执行缓存数据的清除操作.
     * 其中,allEntries表示清除指定key所有数据,beforeInvocation用于定义
     * 在何时清除缓存数据,是更新数据库之后还是之前,false表示之后
     */
    @CacheEvict(value = "tagCache",allEntries = true,beforeInvocation = false)
    @Override
    public void insertTag(Tag tag) {
          tagMapper.insert(tag);
    }
    /**
     * 缓存数据时,可以自己指定key,key的值为spring中的el表达式,语法可以打开@Cacheable注解源码进行查看,
     * 这里的#id表示基于id的值作为key
     */
     @Cacheable(value="tagCache",key="#id")
     @Override
     public Tag selectById(Long id){
          return tagMapper.selectById(id);
     }

   /** @CachePut注解描述的方法为缓存切入点方法,系统底层会在执行此方法后,更新缓存数据,
     * 这里更新完数据以后,key为tag对象的id值,值为方法的返回值.
     */
     @CachePut(value = "tagCache",key="#tag.id")
     @Override
      public Tag updateTag(Tag tag){
          tagMapper.updateById(tag);
          return tag;
      }
}

第三步:修改单元测试类,测试缓存数据一致性.

package com.jt.blog.service;

import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagServiceTests {
    @Autowired
    private TagService tagService;
    @Test
    void testSelectTags(){
        List<Tag> tags=
        tagService.selectTags();
        System.out.println(tags);
    }
    @Test
     void testInsertTag(){
        Tag tag=new Tag();
        tag.setName("Oracle1");
        tagService.insertTag(tag);
    }
        @Test
    void testSelectById(){
        Tag tag = tagService.selectById(1L);
        System.out.println(tag);
    }
      @Test
    void testUpdateTag(){
        Tag tag=new Tag();
        tag.setId(1L);
        tag.setName("mysql8.0");
        tagService.updateTag(tag);
    }
}


Controller中添加本地缓存

在Controller中添加一个本地缓存,减少对远程redis缓存的访问,例如:

package com.jt.blog.controller;

import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@RestController
@RequestMapping("/tag")
public class TagController {
    @Autowired
    private TagService tagService;
    //此对象存哪了?(JVM)
    private List<Tag> tags=new CopyOnWriteArrayList<>();//本地 cache
    @GetMapping
    public  List<Tag> doSelectTags(){
       if(tags.isEmpty()) {
           synchronized (tags) {
               if(tags.isEmpty()) {
                   tags.addAll(tagService.selectTags());//1.redis,2.mysql
               }
           }
       }
       return tags;
    }
}

 

Controller中本地缓存一致性分析

此次项目案例中,我们在Controller层添加了本地缓存,这个缓存我们也需要考虑其缓存一致性,其相关代码实现如下:

package com.jt.blog.controller;

import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;

@RestController
@RequestMapping("/tag")
public class TagController {
    @Autowired
    private TagService tagService;
    //private List<Tag> tags=new ArrayList<>();
    private List<Tag> tags=new CopyOnWriteArrayList<>();//本地 cache


    @GetMapping("/{id}")
    public Tag doSelectById(@PathVariable("id") Long id){
        //查询本地缓存
        for(Tag t:tags){
            if(t.getId().equals(id)) return t;
        }
        //查询redis,mysql
        return tagService.selectById(id);
    }

    @PostMapping
    public String doInsertTag(Tag tag){
        //向数据库写入数据
        tagService.insertTag(tag);//A
        //更新本地缓存
        tags.add(tag);
        return "insert ok";
    }

    @PutMapping
    public String doUpdateTag(Tag tag){//id=1,name=mysql 8.9
        //向数据库写入数据
        tagService.updateTag(tag);
        //更新本地缓存
        for(Tag t:tags){
            if(t.getId().equals(tag.getId())){
                t.setName(tag.getName());
            }
        }
        return "update ok";
    }


    @GetMapping
    public  List<Tag> doSelectTags(){//B
       if(tags.isEmpty()) {
           synchronized (tags) {
               if(tags.isEmpty()) {
                   tags.addAll(tagService.selectTags());//1.redis,2.mysql
               }
           }
       }
       return tags;
    }
    /**Spring中Bean对象的生命周期方法,对象初始化时执行此方法*/
    @PostConstruct
    public void doInit(){
        doTimerRefreshTask();
    }
    /**Spring中Bean对象的生命周期方法,Bean对象初始化时执行此方法*/
    @PreDestroy
    public void doDestory(){
        //退出定时任务
        timer.cancel();
    }
    private Timer timer;
    //定义刷新任务
    private void doTimerRefreshTask(){
        //构建一个定时任务调度对象
        timer=new Timer();
        //构建一个任务对象
        TimerTask task=new TimerTask() {
            @Override
            public void run() {
                System.out.println("refresh cache");
                tags.clear();
            }
        };
        //执行任务对象(每隔5秒执行一次)
        timer.schedule(task, 5000, 5000);
    }

}

Redis集群链接配置实践

修改项目中的application.yml配置文件,修改redis配置,采用集群方式进行实现,例如:

spring:
  datasource: #默认配置的是HikariDataSource,应用的是HikariCP链接池(HikariPool)
    url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: root
#redis 集群配置
  redis:
    cluster: #redis 集群配置
      nodes: 192.168.126.129:8010,192.168.126.129:8011,192.168.126.129:8012,192.168.126.129:8013,192.168.126.129:8014,192.168.126.129:8015
      max-redirects: 3 #最大跳转次数
    timeout: 5000 #超时时间
    database: 0
    jedis: #连接池
      pool:
        max-idle: 8
        max-wait: 0
#日志配置
logging:
  level:
    com.jt: debug

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值