栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring boot集成redis做方法级别的缓存初体验

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring boot集成redis做方法级别的缓存初体验

Spring boot集成redis做方法级别的缓存初体验 前言

spring cache 集成 redis 做方法层面的缓存示例,查询时缓存key和结果到redis,更新时缓存,和缓存清除。示例总结:

  • 做缓存的要求是热数据的读远大于写的数据,才适合用缓存来优化
  • spring cache提供的三个注解@Cacheable,@CachePut,@CacheEvict(用来清除缓存)
  • redis的key资源很宝贵,spring cache提供的这三个注解针仅仅对的是对单个key的缓存操作,无法做到对key是redis set这种集合的类型操作, 这种需求可以通过自定义aop切面完成,本示例没有给出,后面有时间可以继续完善下。
环境
  • windows 系统
  • 基于 spring boot
下载安装redis(windows环境)连接
  • 下载地址 找zip包下载解压就能用

  • 下载后解压进入可执行命令的目录里打开cmd 或 powershell窗口

  • 执行命令启动redis服务: redis-server.exe redis.windows.conf ; powershell窗口是.redis-server.exe redis.windows.conf

  • 另外开启一个客户端窗口输入连接命令 cmd窗口是:redis-cli.exe -h 127.0.0.1 -p 6379; powershell窗口是:.redis-cli.exe -h 127.0.0.1 -p 6379
    连接服务端。

  • 也可以用第三方图像化工具来连接redis服务 推荐一款:Redis Assistant

一、添加依赖

加入下面的依赖后,要在启动类上添加 @EnableCaching注解开启 spring 缓存

        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.2.2
        

        
            mysql
            mysql-connector-java
            runtime
        
        
        
            net.sf.ehcache
            ehcache
        
        
        
            org.springframework.boot
            spring-boot-starter-cache
        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
二、示例数据库表的sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for bsc_dict_info_test
-- ----------------------------
-- DROp TABLE IF EXISTS `bsc_dict_info_test`;
CREATE TABLE IF NOT EXISTS `bsc_dict_info_test`  (
  `dict_id` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ' ' COMMENT '字典标识',
  `dict_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ' ' COMMENT '字典名称',
  `dict_sitm_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ' ' COMMENT '字典子项标识',
  `dict_sitm_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ' ' COMMENT '字典子项名称',
  `suse_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '启用标志',
  `disp_orde` int(11) NOT NULL DEFAULT 0 COMMENT '展示顺序',
  `memo` varchar(4000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ' ' COMMENT '备注'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of bsc_dict_info_test
-- ----------------------------
INSERT INTO `bsc_dict_info_test` VALUES ('bbbbbtest', '担保人/被担保人与上市公司关联关系', '999', '无关联关系', '0', 190, '更新操作!');
INSERT INTO `bsc_dict_info_test` VALUES ('aaaaatest', '担保人/被担保人与上市公司关联关系', '999', '无关联关系', '0', 190, '1');
INSERT INTO `bsc_dict_info_test` VALUES ('bbbbbtest', '担保人/被担保人与上市公司关联关系', '999', '无关联关系', '0', 190, '更新操作!');
INSERT INTO `bsc_dict_info_test` VALUES ('aaaaatest', '担保人/被担保人与上市公司关联关系', '999', '无关联关系', '0', 190, '1');
INSERT INTO `bsc_dict_info_test` VALUES ('bbbbbtest', '担保人/被担保人与上市公司关联关系', '999', '无关联关系', '0', 190, '更新操作!');
INSERT INTO `bsc_dict_info_test` VALUES ('bbbbbtest', '担保人/被担保人与上市公司关联关系', '99911', '无关联关系', '0', 190, '更新操作!');

SET FOREIGN_KEY_CHECKS = 1;
三、properties文件

这里我既用了 application.yaml,又使用了application.properties,也可以合并。参考:ymal中引入其他的yml或properties

  • application.properties
spring.datasource.type=com.zaxxer.hikari.HikariDataSource

#Spring jdbc 数据源配置 需要mysql-connector-java驱动依赖
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/gefrm?useUnicode=true&characterEncoding=utf-8&useSSL=false&useInformationSchema=true&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false
spring.datasource.username=root
spring.datasource.password=123456

# 缓存方式一: ehcache.xml
#配置文件中配置目标缓存管理器,支持 Ehcache、Generic、Redis、Jcache
#spring.cache.type=ehcache
#spring.cache.ehcache.config=classpath:ehcache.xml

#mybatis
mybatis.mapper-locations=classpath:mapper
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    @Resource
    private RedisConnectionFactory factory;

    
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return (o, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(o.getClass().getName()).append(".");
            sb.append(method.getName()).append(".");
            for (Object obj : objects) {
                sb.append(obj.toString()).append(".");
            }
            //System.out.println("keyGenerator=" + sb.toString());
            return sb.toString();
        };
    }

    
    @Bean
    public RedisTemplate redisTemplate() {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 序列化器
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        return redisTemplate;
    }

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }

    @Bean
    @Override
    public CacheManager cacheManager() {
        RedisCacheConfiguration cacheConfiguration =
                defaultCacheConfig()
                        .disableCachingNullValues()
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
    }
}
六、示例代码 (一)bean 实体
  • BscDictInfoTestDO
package com.example.mybatis_cache.bean;

import lombok.*;
import lombok.experimental.Accessors;

import java.io.Serializable;


@Getter
@Setter
@Accessors(chain = true)
public class BscDictInfoTestDO implements Serializable {
    private String dictId;
    private String dictName;
    private String dictSitmId;
    private String dictSitmName;
    private String suseFlag;
    private String dispOrde;
    private String memo;

}
(二)mapper 和 mapper.xml
  • BscDictInfoTestMapper
package com.example.mybatis_cache.mapper;

import com.example.mybatis_cache.bean.BscDictInfoTestDO;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;


@Mapper
public interface BscDictInfoTestMapper {
    
    List selectAll();

    
    List selectByDictId(String dictId);

    
    int updateDataInfo(BscDictInfoTestDO data);

    
    int deleteByDictIds(List data);

}
  • BscDictInfoTestMapper.xml




    
    

    
    

    
    

    
        dict_id
        ,
        dict_name,
        dict_sitm_id,
        dict_sitm_name,
        suse_flag,
        disp_orde,
        memo
    

    
    
        select
        
        from bsc_dict_info_test
        where dict_id = #{dictId}
    

    
        update bsc_dict_info_test
        set memo = #{memo}
        where dict_id = #{dictId}
    


    
        delete
        from bsc_dict_info_test
        
            dictId in
            
                
                    #{item}
                
            
        
    

(三)service 和 iml
  • BscDictService
package com.example.mybatis_cache.service;

import com.example.mybatis_cache.bean.BscDictInfoTestDO;

import java.util.List;


public interface BscDictService {
    void info(String name, int age);

    
    List getDicInfoList(String dictId);

    
    BscDictInfoTestDO updateDictInfo(BscDictInfoTestDO dictInfo);

    
    int deleteAll();

    
    int insertDictInfo(List data);

    
    int deleteDictInfo(List dictList);
    
}
  • BscDictServiceImpl
package com.example.mybatis_cache.service.impl;


import com.example.mybatis_cache.bean.BscDictInfoTestDO;
import com.example.mybatis_cache.mapper.BscDictInfoTestMapper;
import com.example.mybatis_cache.service.BscDictService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;


// 注意:必须对应配置文件ehcache.xml中cache节点的name属性值,
// 这里开启@CacheConfig后,下面的Cacheable的value默认就使用这里的cacheNames指定的值
@CacheConfig(cacheNames = "otherCache")
@Slf4j
@Service
public class BscDictServiceImpl implements BscDictService {

    
    private static final String CACHE_NAME = "MyCache";
    private static final String OTHER_CACHE = "otherCache";


    @Resource
    private BscDictInfoTestMapper mapper;

    @Override
    public void info(String name, int age) {
        log.warn("name:{}, age:{}", name, age);
        List list = mapper.selectAll();
        list.forEach(item -> {
            log.error("{}", item);
        });
    }

    @Cacheable(value = CACHE_NAME, key = "#dictId")
    @Override
    public List getDicInfoList(String dictId) {
        log.warn(">>>>>>>>> 执行查询字典dictId:{} <<<<<<<<<", dictId);
        return mapper.selectByDictId(dictId);
    }

    @CachePut(value = CACHE_NAME, key = "#dictInfo.dictId")
//    @CachePut
    @Override
    public BscDictInfoTestDO updateDictInfo(BscDictInfoTestDO dictInfo) {
        mapper.updateDataInfo(dictInfo);
        return dictInfo;
    }

    @CacheEvict(value = {CACHE_NAME, OTHER_CACHE}, allEntries = true)
    @Override
    public int deleteAll() {
        log.info("删除所有信息,清空所有缓存");
        return 0;
    }

    @Override
    public int insertDictInfo(List data) {
        return 0;
    }

    @Override
    public int deleteDictInfo(List dictList) {
        log.info(">>>>>>>>> 执行删除字典dictId:{} <<<<<<<<<", dictList.stream().collect(Collectors.joining(",")));
        return 0;
        // return mapper.deleteByDictIds(dictList);
    }

}
(四)Test代码
package com.example.mybatis_cache.service.impl;

import com.example.mybatis_cache.bean.BscDictInfoTestDO;
import com.example.mybatis_cache.service.BscDictService;
import com.example.mybatis_cache.support.ApplicationContextSupport;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;

import static org.junit.jupiter.api.Assertions.*;


@Slf4j
@SpringBootTest
class BscDictServiceImplTest {

    @Autowired
    private BscDictService dictService;

    
    @Test
    void levelOneCacheTest() {
        dictService.info("a", 1);
        log.warn("======================== 分割线  ======================");
        dictService.info("a", 2);
    }

    
    @Test
    void levelTwoCacheTest() {
        dictService.getDicInfoList("bbbbbtest");
        dictService.getDicInfoList("bbbbbtest");
        dictService.getDicInfoList("aaaaatest");
    }

    
    @Test
    void levelTwoCacheUpdateTest() {
        BscDictInfoTestDO dictInfo = dictService.getDicInfoList("bbbbbtest").stream().limit(1).findFirst().orElse(new BscDictInfoTestDO());
        log.warn("======================== 分割线  ======================");
        dictService.updateDictInfo(dictInfo.setMemo("更新操作!"));
    }

    
    @Test
    void cacheEviTest() {
        dictService.deleteAll();
    }


    @Test
    void testRawLevelOne() {
        System.out.println("====");
        ///dictService.getDicInfoList("bbbbbtest");
        ApplicationContext context = ApplicationContextSupport.context;
        ///dictService.getDicInfoList("bbbbbtest");
        System.out.println(context);
    }

    @Test
    void cacheTest() {
        final String path = "D:\ideaProjects\jikeshijian\mygeekbangwork\homework\mybatis_cache\src\main\resources\ehcache.xml";
        // 1. 创建缓存管理器
        CacheManager cacheManager = CacheManager.create(path);

        // 2. 获取缓存对象 MyCache HelloWorldCache
        Cache cache = cacheManager.getCache("MyCache");

        // 3. 创建元素
        Element element = new Element("key1", "value1");

        // 4. 将元素添加到缓存
        cache.put(element);

        // 5. 获取缓存
        Element value = cache.get("key1");
        System.out.println("value: " + value);
        System.out.println(value.getObjectValue());

        // 6. 删除元素
        cache.remove("key1");

        // 7. 刷新缓存
        cache.flush();

        // 8. 关闭缓存管理器
        cacheManager.shutdown();
    }
}
七、结果演示
  • 使用@Cacheable修饰dictService.getDicInfoList("bbbbbtest")方法执行2次
    ,第一次执行会缓存到redis,第二次执行时缓存该入参key已存在就不会再执行方法体里的内容,测试后查看redis缓存,都已缓存

  • 使用@CachePut修饰。执行方法,并更新redis的相应key缓存。调用 getDicInfoList会缓存一个keyMyCache::bbbbtest,
    调用updateDictInfo 会执行更新缓存,其返回值是单个对象。结果入下图。这也印证了那句话,redis的key资源很宝贵,实际上这里就是key冲突了,2个方法key一直会相互影响需设计的适合要极其注意。

  • 未指定key会使用自定义的生成key策略缓存。方法updateDictInfo 未指定key和value会使用默认的策略,详情看上面的impl代码

  • 使用@CachePut修饰清除所有缓存。执行测试代码后,redis的cache缓存清空

报错问题解决
  • 启动异常:
No cache could be resolved for 'Builder[public com.example.bean.Employeecom.example.controller.EmployeeController.getEmpById(java.lang.Integer)]caches=[] | key='' |keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false''using resolver'org.springframework.cache.interceptor.SimpleCacheResolver@540d3d5d'. At least one cache should be provided per cache operation.
  • 解决: @Cacheable 注解的value要指定ehcache.xml的cache

  • 启动异常:

Could not read JSON: Cannot construct instance of `com.example.mybatis_cache.bean.BscDictInfoTestDO` (no
  Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based
  Creator)
  • 解决: 构造函数被重写带多个入参是,实体类的无参构造函数一定要保留即可解决。
参考资料
  • Redis 安装
  • RedisAssistant:一款Redis可视化管理工具
  • spring cache 学习 —— @Cacheable 使用详解
  • Spring Boot2.X 自定义Redis的cacheManager,保存Json格式到Redis
  • SpringBoot中使用Redis实现缓存
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/921635.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号