1.1.介绍三个注解@Cacheable、@CacheEvict、@CachePut
1.1.1@Cacheable
概述:
1)标记在类上:表示该类所有的方法都是支持缓存的。
2)标记在方法上:如果一个方法上添加了@Cacheable标记,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
用法:
@Cacheable可以指定三个属性,value、key和condition。
value必须指定,表示当前方法的返回值会被缓存在哪个Cache上,对应Cache的名称。可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
适用场景:查询接口
【示例】
@Cacheable("cache1")//Cache是发生在cache1上的
public User find(Integer id) {
return null;
}
@Cacheable(cacheNames="user", key="#id") // 使用方法参数中的id参数生成缓存键值对中的key
public User query(Integer id) {
return null;
}
key 自定义缓存的键,用来指定Spring缓存时对应的key的
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
思路:
graph TB
A[查询id为1的用户] -->|RedisOnlyController| B(调用RedisOnlyServiceImpl前被aop拦截)
B --> C{id为1的用户数据是否缓存到redis?}
C -->|是| D[从redis中获取用户数据并立刻返回]
C -->|否| E[执行service类的selectById方法并缓存结果后返回]
1.1.2@CacheEvict
概述:
本质是删除redis数据库的user命名空间下的所有键值对.原理很简单,仅仅是把缓存数据删除,无特别逻辑.
若想删除redis缓存的所有用户数据,可以把注解改成@CacheEvict(cacheNames="user", allEntries=true)
用法:
缓存被调用方法的结果(返回值),删除redis中对应的缓存
@CacheEvict(cacheNames="user", key="#id")
@Override
public boolean delete(String id) {
// 可以在这里添加删除数据库对应用户数据的操作
return true;
}
适用场景:删除接口
1.1.3@CachePut
概述:
@CachePut也可以声明一个方法支持缓存功能。不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
用法:
@Service
public class RedisOnlyServiceImpl implements UserService {
// 记录
private AtomicInteger executeCout = new AtomicInteger(0);
@CachePut(cacheNames="user", key="#user.id")
@Override
public User update(User user) {
// 每次方法执行executeCout
user.setUsername("redisOnly" + executeCout.incrementAndGet());
// 必须把更新后的用户数据返回,这样才能把它缓存到redis中
return user;
}
}
适用场景:更新操作,插入操作
2.如何搭建Redis缓存
整体结构布局
.
├── Application.java //启动类
├── controller
│ ├── RedisOnlyController.java
│ └── UserController.java
├── dao
│ └── UserDao.java //mapper接口(mybatis)
├── po
│ └── User.java // 用户po类
└── service
├── impl
│ ├── RedisOnlyServiceImpl.java
│ └── SimpleUserService.java
├── RedisOnlyService.java
└── UserService.java
pom引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置application.yml
注意:xxx.xx.xx.xx表示你需要的端口号
server:
port: 8080
spring:
datasource: #数据源配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xx.xx.xx:3310/0812erp?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
username: root
password: root
druid:
max-active: 10
min-idle: 5
max-wait: 5000
initial-size: 5
validation-query: select 1
stat-view-servlet:
enabled: true
login-username: admin
login-password: admin
allow:
deny:
url-pattern: "/druid/*"
thymeleaf:
cache: false
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
#==================redis 配置开始============================
#注意,redis是上面spring下面的,别写错了,层级关系
redis:
host: xxx.xx.xx.xx
password: xxxxxx
port: 6390
jedis:
pool:
max-active: 20
max-idle: 8
min-idle: 0
max-wait: 2000
#==================redis 配置结束============================
#shiro的配置
shiro:
hash-algorithm-name: md5
hash-iterations: 2
login-url: /index.html
unauthorized-url: /unauthorized.html
anon-urls:
- /**
- /index.html*
- /login/login*
- /resources/**
logout-url: /login/logout*
authc-urls:
# - /**
#mybatisplus的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*/*Mapper.xml
修改Application
@SpringBootApplication
@MapperScan(basePackages = {"com.sxt.system.mapper"}) // 扫描mapper
@EnableCaching // 开启缓存
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
创建RedisOnlyController
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@Resource
RedisOnlyService redisOnlyService;
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public User getById(@PathVariable("id") String id) {
return redisOnlyService.selectById(id);
}
}
创建RedisOnlyServiceImpl
查询:
@Service
public class RedisOnlyServiceImpl implements UserService {
/**
* 先用id生成key,在用这个key查询redis中有无缓存到对应的值
*
* 若无缓存,则执行方法selectById,并把方法返回的值缓存到redis
*
* 若有缓存,则直接把redis缓存的值返回给用户,不执行方法
*/
@Cacheable(cacheNames="user", key="#id")
@Override
public User selectById(String id) {
//直接new一个给定id的用户对象,来返回给用户
return new User(id,"redisOnly","password");
}
测试结果:
第一次访问http://localhost:9999/redisOnly/user/1
接口(查询id为1
的用户)时就会从redis中查找redis缓存的用户数据,没有,放入数据库和缓存中
第二次访问http://localhost:9999/redisOnly/user/1
接口(查询id为1
的用户)时就会直接从redis中返回redis缓存的用户数据
而不再执行RedisOnlyServiceImpl
的selectById()
方法
修改RedisOnlyController
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public boolean delete(@PathVariable String id) {
return redisOnlyService.delete(id);
}
}
修改RedisOnlyServiceImpl
删除:
@Service
public class RedisOnlyServiceImpl implements UserService {
@CacheEvict(cacheNames="user", key="#id")
@Override
public boolean delete(String id) {
// 可以在这里添加删除数据库对应用户数据的操作
return true;
}
}
测试:
启动程序,设置请求方法为DELETE
,用postman访问http://localhost:9999/redisOnly/user/9876
接口,即可删除id为9876
的用户数据于redis的缓存和数据库对应的数据
修改RedisOnlyController
@RequestMapping("/redisOnly")
@RestController()
public class RedisOnlyController {
@Resource
RedisOnlyService redisOnlyService;
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public User update(@PathVariable String id, @RequestBody User user) {
user.setId(id);
redisOnlyService.update(user);
return user;
}
}
修改RedisOnlyServiceImpl
修改:
@Service
public class RedisOnlyServiceImpl implements UserService {
// 记录
private AtomicInteger executeCout = new AtomicInteger(0);
@CachePut(cacheNames="user", key="#user.id")
@Override
public User update(User user) {
// 每次方法执行executeCout
user.setUsername("redisOnly" + executeCout.incrementAndGet());
// 必须把更新后的用户数据返回,这样才能把它缓存到redis中
return user;
}
}
测试:
启动程序,设置请求方法为DELETE
,用postman访问http://localhost:9999/redisOnly/user/9876
接口5次
我们发现每次访问接口时时update()
方法都会执行一次(即,executeCout
的值为5)
注意:mybatis搭建,springboot搭建都是一样的原理,不懂怎么搭建可看后续文档。
3.redis注解的其他知识点(多线程访问)
除了cacheNames
和key
,其他注解属性的作用
keyGenerator
:定义键值对中key生成的类,和key
属性的不能同时存在
sync:如果设置sync=true:
- a. 如果缓存中没有数据,多个线程同时访问这个方法,则只有一个方法会执行到方法,其它方法需要等待;
- b. 如果缓存中已经有数据,则多个线程可以同时从缓存中获取数据;
注意:
sync
不能解决缓存穿透的问题