场景: 访问controller层(其实是service),需要将其结果缓存到redis,下一次直接从缓存找值,从而减少sql操作,减轻数据库压力
技术: redis,springboot,jpa,mysql
1, 新建项目
2, 导入依赖
<?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>
<!--springboot-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<!-- <version>2.1.6.RELEASE</version>-->
</parent>
<groupId>org.example</groupId>
<artifactId>redis-cache-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--lombok⼯具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!--jpa操作redis,可以使用redis缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Data Jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
3, 创建mysql表
create table user
(
id int auto_increment
primary key,
name varchar(10) not null,
password varchar(15) default '123456' not null,
address varchar(25) null,
phone varchar(15) null,
createTime timestamp null,
updateTime timestamp null
)
4, 创建springboot启动类
package org.malred;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching // 开启缓存
@SpringBootApplication
public class demoApplication {
public static void main(String[] args) {
SpringApplication.run(demoApplication.class,args);
}
}
5, Base通用类
package org.malred.Base;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 通用controller,定义通用crud方法
*/
public abstract class BaseController<T> {
/**
* 获取表中所有数据
* @return
*/
@GetMapping("/findAll")
public abstract List<T> findAll();
/**
* 根据id获取表中数据
* @param id
* @return
*/
@GetMapping("/find/{id}")
public abstract T findById(@PathVariable Long id);
/**
* 插入数据(没有id字段)
* @param t
* @return
*/
@PostMapping("/save")
public abstract T insert(@RequestBody T t);
/**
* 修改数据(要有id字段)
* @param t
* @return
*/
@PutMapping("/save")
public abstract T update(@RequestBody T t);
/**
* 根据id删除数据
* @param id
*/
@DeleteMapping("/delete/{id}")
public abstract void delete(@PathVariable Long id);
}
package org.malred.Base;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Date;
/**
* 基础实体类
* T是主键的数据类型(Long?String?)
*/
@Data
@MappedSuperclass // 表示是实体类的父类,jpa会识别子类里的父类属性,作为表字段
public class BaseEntity<T extends Serializable> {
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public T id;
/**
* 创建时间
*/
@CreationTimestamp
public Date createTime;
/**
* 更新时间
*/
@UpdateTimestamp
public Date updateTime;
}
package org.malred.Base;
import org.malred.pojo.User;
import java.util.List;
/**
* 基础service类,对应baseController的crud
*/
public interface BaseService<T> {
/**
* 查询表中所有数据
* @return
*/
List<T> findAll();
/**
* 根据id获取表中数据
* @param id
* @return
*/
T findById(Long id);
/**
* 保存或更新(有id是更新,没id是保存)
* @param t
* @return
*/
T save(T t);
/**
* 根据id删除表内数据
* @param id
*/
void delete(Long id);
}
6, 创建mvc三层架构
controller层
package org.malred.controller;
import org.malred.Base.BaseController;
import org.malred.pojo.User;
import org.malred.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/demo")
public class DemoController extends BaseController<User> {
@Autowired
DemoService demoService;
@Override
public List<User> findAll() {
return demoService.findAll();
}
@Override
public User findById(@PathVariable Long id) {// 不加@PathVariable,获取不到id
return demoService.findById(id);
}
@Override
public User insert(@RequestBody User user) {// 不加@RequestBody,获取不到对象
return demoService.save(user);
}
@Override
public User update(@RequestBody User user) {
return demoService.save(user);
}
@Override
public void delete(@PathVariable Long id) {
demoService.delete(id);
}
}
service层
package org.malred.service;
import org.malred.Base.BaseService;
import org.malred.pojo.User;
public interface DemoService extends BaseService<User> {
}
package org.malred.service.impl;
import org.malred.dao.UserDao;
import org.malred.pojo.User;
import org.malred.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private UserDao userDao;
@Override
public List<User> findAll() {
return userDao.findAll();
}
@Override
public User findById(Long id) {
return userDao.findById(id).get();
}
@Override
public User save(User user) {
return userDao.save(user);
}
@Override
public void delete(Long id) {
userDao.deleteById(id);
}
}
dao层: 操作数据
package org.malred.dao;
import org.malred.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserDao extends JpaRepository<User,Long> { }
pojo层: 实体类
package org.malred.pojo;
import lombok.Data;
import org.malred.Base.BaseEntity;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
/**
* 测试用实体类
*/
@Data
@Entity
@Table(name = "user")
// 要使用非默认缓存方式,需要实现序列号接口!
public class User extends BaseEntity<Long> implements Serializable {
private String name;
private String password;
private String address;
private String phone;
// 需要重写tostring,包含上父类的属性
@Override
public String toString() {
return "User{" +
"id=" + id +
", createTime='" + createTime + '\'' +
", updateTime='" + updateTime + '\'' +
", name='" + name + '\'' +
", password='" + password + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
*7, 缓存注解(加在service层的方法上)
*7.1, @EnableCaching
加在启动类上,表示支持缓存
*7.2, @Cacheable 表示 将该方法查询结果存放在springboot默认缓存中
通常加在从数据库取出数据的地方
*7.3, @CachePut 会将数据的变更同步到缓存
通常加在从数据库更新数据的方法上
*7.4 @CacheEvict 会删除缓存
通常放在删除数据库数据的方法上
7.5, 修改后的service
package org.malred.service.impl;
import org.malred.dao.UserDao;
import org.malred.pojo.User;
import org.malred.service.DemoService;
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.stereotype.Service;
import java.util.List;
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private UserDao userDao;
// @Cacheable -> 将该方法查询结果存放在springboot默认缓存中
// cacheNames -> 起一个缓存命名空间,对应缓存唯一标识
// springboot缓存的map结构 ->
// value: 缓存结果;
// key: 默认只有一个参数情况下,key值就是方法参数值,如果没有参数或多个参数,会自动生成(simpleKeyGenerate类)
@Cacheable(
cacheNames = "user",
// 如果结果为空就不缓存
unless = "#result==null"
)
@Override
public List<User> findAll() {
return userDao.findAll();
}
@Cacheable(
cacheNames = "user",
// 如果结果为空就不缓存
unless = "#result==null"
)
@Override
public User findById(Long id) {
return userDao.findById(id).get();
}
// 更新方法,会把变更同步到缓存
@CachePut(
cacheNames = "user",
// 将修改结果的id作为缓存的key
key = "#result.id"
)
@Override
public User save(User user) {
return userDao.save(user);
}
// 删除方法,会删除缓存
@Override
@CacheEvict(
cacheNames = "user"
)
public void delete(Long id) {
userDao.deleteById(id);
}
}
*7.6 配置文件
server:
port: 11111
spring:
# Redis服务连接配置
redis:
# 服务地址
host: 127.0.0.1
# 服务器连接端口
port: 6379
# 服务器连接密码(默认为空)
password:
cache:
redis:
# 基于注解的redis缓存使用的缓存时间配置
time-to-live: 60000
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
jpa:
database: MySQL
show-sql: true # 显示sql
hibernate:
naming:
physical-strategy:
#避免将驼峰命名转换为下划线命名(jpa会自动把实体类的驼峰字段转为 _ 连接的字段名)
org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
8, 启动测试
8.1, 启动redis
8.2, 启动项目
8.3, 打开redis图形化软件
8.4, 测试获取数据(findAll,findById),是否自动缓存到redis
这里的 http://bd 的bd是我在本地host配置的解析,结果是127.0.0.1, 你可以使用 http://127.0.0.1:11111来访问,没有区别
8.5, 可以看到确实缓存到了,但是为什么是这种样子呢???
*9, 配置 自定义redis缓存序列化 解决缓存数据看不懂的问题
config/redisConfig.java
package org.malred.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* redis配置类
*/
@Configuration
public class RedisConfig {
/**
* 自定义redisCacheManager,定义序列化方式
*/
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和json序列化对象,对缓存数据进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
// 定制缓存序列化方式及其时效
RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(objectJackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
}
10, 最终测试
10.1, 测试 findAll和findById
http://127.0.0.1:11111/demo/findAll
10.2, 测试 save (新增和更新)
这里使用apifox(postman好像delete方法不能用)
10.2.1, 新增
10.2.2, 修改
10.3, 测试 delete
11, 代码仓库 已经上传到GitHub和Gitee
https://gitee.com/malguy/redis-cache-boot
https://github.com/malred/redis-cache-boot