环境spring cache 集成 redis 做方法层面的缓存示例,查询时缓存key和结果到redis,更新时缓存,和缓存清除。示例总结:
- 做缓存的要求是热数据的读远大于写的数据,才适合用缓存来优化
- spring cache提供的三个注解@Cacheable,@CachePut,@CacheEvict(用来清除缓存)
- redis的key资源很宝贵,spring cache提供的这三个注解针仅仅对的是对单个key的缓存操作,无法做到对key是redis set这种集合的类型操作, 这种需求可以通过自定义aop切面完成,本示例没有给出,后面有时间可以继续完善下。
- windows 系统
- 基于 spring boot
-
下载地址 找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 缓存
二、示例数据库表的sqlorg.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
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
六、示例代码
(一)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
(三)service 和 imldict_id , dict_name, dict_sitm_id, dict_sitm_name, suse_flag, disp_orde, memo
- 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实现缓存



