目录
简介
Spring Cache并不是一种缓存的实现方式,而是缓存使用的一种方式,其基于Annotation形式提供缓存存取,过期失效等各种能力,这样设计的理由大概是缓存和业务逻辑本身是没有关系的,不需要耦合到一起,因此使用Annotation修饰方法,使得方法中只需要关心具体的业务逻辑,并不需要去关心缓存逻辑。
Spring Cache相关实现逻辑都在Spring Context的org.springframework.cache
包中,有兴趣可以直接翻阅源代码学习。
原理篇
注解篇
@Cacheable,@CachePut , @CacheEvict,@Caching
实战篇
源码
本Demo使用MyBatis-Plus
,其使用方式可参考SpringBoot2.x整合MyBatis-Plus
下文只涵盖部分核心代码,全部代码请clone源码!
SQL脚本
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
`age` int(3) NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci;
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 http://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.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot2.2.x_redis</artifactId>
<version>1.0.0</version>
<name>springboot2.2.x_redis</name>
<description>SpringBoot2.2.x demo project for Redis</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 支持 @ConfigurationProperties 注解 读取properties或yml配置文件中的值 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 80
spring:
cache:
type: redis
redis:
# redis默认情况下有16个分片,这里配置具体使用的分片,默认为0
database: 0
host: 127.0.0.1
port: 6379
password: root
lettuce:
pool:
# 连接池最大连接数(使用负数表示没有限制),默认8
max-active: 8
#最大阻塞等待时间(负数表示没限制)
max-wait: -1
#最大空闲
max-idle: 8
#最小空闲
min-idle: 0
#数据源
datasource:
url: jdbc:mysql://localhost:3306/cache?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
mybatis-plus:
# 如果是放在src/main/java目录下 classpath:/com/yourpackage/*/mapper/*Mapper.xml
# 如果是放在resource目录 classpath:/mapper/*Mapper.xml
mapper-locations: classpath*:mapper/*.xml
# 别名包扫描,多个package用逗号或者分号分隔
type-aliases-package: com.example.springboot_redis.domain
configuration:
#配置返回数据库(column下划线命名&&返回java实体是驼峰命名),自动匹配无需as(没开启这个,SQL需要写as: select user_id as userId)
map-underscore-to-camel-case: true
cache-enabled: false
#配置JdbcTypeForNull, oracle数据库必须配置
jdbc-type-for-null: 'null'
global-config:
#主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
id-type: 0
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
field-strategy: 2
#驼峰下划线转换
db-column-underline: true
#mp2.3+ 全局表前缀 t_
table-prefix: t_
#刷新mapper 调试神器
refresh-mapper: true
#数据库大写下划线转换
capital-mode: true
#逻辑删除配置(下面3个配置)
logic-delete-value: 4
logic-not-delete-value: 0
# 日志
logging:
level:
com.example.springboot_redis.mapper: debug # 显示执行sql
SpringbootRedisApplication
package com.example.springboot_redis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching
@SpringBootApplication
@MapperScan(basePackages = "com.example.springboot_redis.mapper")
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
UserController
package com.example.springboot_redis.contoller;
import com.example.springboot_redis.domain.User;
import com.example.springboot_redis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 测试Redis的controller
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 新增用户
@PostMapping
public void save(@RequestBody User user) {
userService.save(user);
}
// 查询用户
@GetMapping("/{userCode}")
public User get(@PathVariable(name = "userCode") String userCode) {
return userService.get(userCode);
}
// 更新用户
@PutMapping
public void update(@RequestBody User user){
userService.update(user);
}
// 删除用户
@DeleteMapping("/{userCode}")
public void delete(@PathVariable(name = "userCode") String userCode) {
userService.delete(userCode);
}
}
UserServiceImpl
package com.example.springboot_redis.service.impl;
import com.example.springboot_redis.domain.User;
import com.example.springboot_redis.mapper.UserMapper;
import com.example.springboot_redis.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;
@Slf4j
@Service
@CacheConfig(cacheNames = "USER")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@CachePut(key = "#user.userCode")
public User save(User user) {
log.info("保存用户");
userMapper.insert(user);
return user;
}
@Override
@Cacheable(key = "#userCode")
public User get(String userCode) {
log.info("获取用户userCode = {}", userCode);
return userMapper.selectByUserCode(userCode);
}
@Override
@CachePut(key = "#user.userCode")
public User update(User user) {
log.info("更新用户userCode = {}", user.getUserCode());
userMapper.updateById(user);
return user;
}
@Override
@CacheEvict(key = "#userCode")
public void delete(String userCode) {
log.info("删除用户userCode = {}", userCode);
userMapper.deleteByUserCode(userCode);
}
}
这里使用了@Cacheable、@CachePut、@CacheEvict
三个注解
@CacheConfig(cacheNames = "USER")
统一设置cache
的名称,否则需要上面三个注解都填写value
值
测试
新增用户
日志:
查询用户
日志:
如上图所示无日志,却有结果返回,说明请求走的是缓存,@CachePut
注解在新增用户时新增到数据库然后把数据放进了缓存
更新用户
日志:
查询用户
查询结果已更新
日志:
如上图所示无日志,却有结果返回,说明请求走的是缓存,@CachePut注解在做更新操作时更新了数据库然后更新了缓存。
删除用户
日志:
查询用户
日志:
如上图所示,说明@CacheEvict
注解将缓存清除,所以查询时走的数据库,因为数据库中的数据也已经被删除所以查询结果为空
删除Redis缓存
手动删除Redis中的缓存
查询用户
日志:
@Cacheable
注解在删除缓存后再次查询时,会走数据库查询,并把查询结果缓存到缓存中(通过再次查询有结果无日志可以证明见下图)
日志:
总结
@Cacheable
用于查询
操作,当要查询的结果在缓存中则直接返回结果,不执行业务逻辑;当要查询的结果不在缓存中时执行业务逻辑查询数据库,并将数据库查询的结果返回并缓存到缓存中。并不是每次都会执行业务逻辑
@CachePut
用于新增
和更新
操作,执行完新增和更新业务逻辑后将结果缓存到缓存中。每次都会执行业务逻辑
@CacheEvict
用于删除
操作,执行完删除逻辑后删除缓存