1、JSR-107(少用)
2、Spring 缓存抽象(重要)
2.1 基本环境搭建
(1)新建一个 Spring Boot 工程
(2)导入依赖,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>
<groupId>com.yanghui</groupId>
<artifactId>cache</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.4.10</version>
</dependency>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--添加配置跳过测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
(3)创建 application.properties 配置文件,内容为:
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_cache
spring.datasource.username=root
spring.datasource.password=1234567890
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
(4)创建启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
(5)创建数据库 springboot_cache,建表 sql 为:
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`departmentName` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`lastName` VARCHAR(255) DEFAULT NULL,
`email` VARCHAR(255) DEFAULT NULL,
`gender` INT(2) DEFAULT NULL,
`d_id` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
(6)实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Department {
private Integer id;
private String departmentName;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
/**
* 1 男
* 2 女
*/
private Integer gender;
private Integer dId;
}
(7)mapper 接口
@Mapper
public interface DepartmentMapper {
/**
* 查询
* @param id
* @return
*/
@Select("SELECT * FROM department WHERE id = #{id}")
Department getDeptById(Integer id);
}
@Mapper
public interface EmployeeMapper {
/**
* 查询
* @param id
* @return
*/
@Select("SELECT * FROM employee WHERE id = #{id}")
public Employee getEmpById(Integer id);
/**
* 修改
* @param employee
*/
@Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
public void updateEmp(Employee employee);
/**
* 删除
* @param id
*/
@Delete("DELETE FROM employee WHERE id=#{id}")
public void deleteEmpById(Integer id);
/**
* 添加
* @param employee
*/
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
/**
* 查询
* @param lastName
* @return
*/
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
(8)测试 mapper 接口
@SpringBootTest
class EmployeeMapperTest {
@Autowired
private EmployeeMapper employeeMapper;
@Test
void getEmpById() {
System.out.println(employeeMapper.getEmpById(1));
}
}
(9)编写 Service
@Service
@Slf4j
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
public Employee getEmpById(Integer id) {
System.out.println("查询" + id + "号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
}
(10)编写 Controller
@RestController
@RequestMapping("/emp")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping("/getEmpById/{id}")
public Employee getEmpById(@PathVariable("id") Integer id) {
Employee employee = employeeService.getEmpById(id);
return employee;
}
}
(11)启动 Spring Boot 进行测试
2.2 @Cacheable
初体验
(1)在启动类上添加注解 @Cacheable
@EnableCaching
(2)在方法上添加注解 @Cacheable
/**
* @Cacheable:将方法的返回结果进行缓存,以后再要相同的数据,直接从缓存中获取,不用调用方法
* CacheManager 管理多个 Cache 组件的,对缓存的真正 CRUD 操作是在 Cache 组件中,每一个缓存组件有自己唯一一个名字
* 属性:
* cacheNames/value:指定缓存组件的名字
* key:缓存数据使用的 key,默认使用方法参数的值作为 key,value 为返回值
* keyGenerator:key 的生成器可以自己指定 key 的生成器的组件 id
* key/keyGenerator 二选一使用
* cacheManager:指定缓存管理器,或者 cacheResolver 指定获取解析器,二选一使用
* condition:符合条件才缓存,否则反之
* unless:符合条件不缓存,否则反之
* sync:是否使用异步模式
*
* key = "#root.args[0]", condition = "#id>0", unless = "#result==null"
*/
@Cacheable(cacheNames = "emp")
(3)进行测试
2.3 缓存工作原理
/**
* 默认使用 ConcurrenMapCacheManager 组件
* 默认使用 SimpleCacheConfiguration
* 运行流程
* @Cacheable
* 1、方法运行之前,先去查询 Cache(缓存组件),按照 CacheNames 指定的名字获取
* (CacheManager 先去获取相应的缓存),第一次获取缓存如果没有 Cache 组件会自动创建
* 2、去 Cache 中查找缓存的内容,使用一个 key,默认是方法的参数
* key是按照某种策略生成的,默认是使用 keyGenerator 生成的,默认使用 SimpleKeyGenerator 生成 key
* SimpleKeyGenerator 生成 key 的默认策略:
* 如果没有参数:key = new SimpleKey();
* 如果有一个参数:key = 参数的值;
* 如果有多个参数:key = new SimpleKey(params);
* 3、没有查询到缓存就调用目标方法
* 4、将目标方法的返回结果放进缓存中
*
* @Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认是按照参数的值作为 key 去查询缓存的
* 如果没有就执行方法并将返回结果放入缓存中,以后再调用就直接调用缓存中的方法即可
*/
2.4 @CachePut
/**
* @CachePut:既调用方法,又更新缓存数据
* 修改了数据库的某个数据,同时更新缓存
* 运行时机:
* 1、先调用目标方法
* 2、将目标方法中的结果缓存起来
*
* 注意默认 key 是第一个参数
* SPEL 表达式
*/
@CachePut(value = "emp", key = "#employee.id")
2.5 @CacheEvict
/**
* @CacheEvict:缓存清除
* 按照指定 key 删除缓存
* key:指定要清除的数据
* allEntries = true 删除所有缓存(按照缓存组件)
* 默认是 false
* beforeInvocation = true 是否在方法执行之前执行
* 无论方法是否出现异常,都会清除缓存
* 默认是 false
*/
@CacheEvict(value = "emp", key = "#id")
2.6 @Caching
/**
* 定义复杂缓存规则
*/
@Caching(
cacheable = {
@Cacheable(value = "emp", key = "#root.method")
},
put = {
@CachePut(value = "emp")
},
evict = {
@CacheEvict(value = "emp")
}
)
2.7 @CacheConfig
/**
* 抽取缓存公共配置,标注在类上
*/
@CacheConfig(cacheNames = "emp")
3、整合 Redis(重要)
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
3.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 编写配置
application.properties
文件中添加配置:
spring.redis.host=127.0.0.1
spring.redis.port=6379
# 用户名密码默认为空
spring.redis.username=
spring.redis.password=
spring.redis.database=0
spring.redis.timeout=5000
3.3 注入对象
/**
* 操作字符串
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 操作对象
*/
@Autowired
private RedisTemplate redisTemplate;
3.4 进行操作
/**
* Redis 五大数据类型
* String(字符串)、List(列表)、Hash(散列)、Set(集合)、ZSet(有序集合)
* stringRedisTemplate.opsForValue():操作字符串
* stringRedisTemplate.opsForList();操作列表
* stringRedisTemplate.opsForHash();操作散列
* stringRedisTemplate.opsForSet();操作集合
* stringRedisTemplate.opsForZSet();操作有序集合
* redisTemplate 和这个一样的,只不过一个是对象,一个是字符串
*/
3.5 配置类
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
// key 序列化方式
template.setKeySerializer(redisSerializer);
// value 序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// value hashmap 序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间 600 秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}