六.缓存
1. 引入模块
- Cache(似乎已经默认带缓存)
- Web(为了方便测试)
- Mysql
- JDBC
- Mybatis
2. 环境搭建
- 创建数据库文件,创建实验表
- 创建Java Bean(pojo),用于封装我们的数据
- 整合Mybatis操作数据库(参照第四章)
- 简单写个MVC三层架构,查询数据库
3. 快速缓存体验
两步走
3.1 开启基于注解的缓存
使用@EnableCaching开启缓存注解
3.2 标注缓存注解
常用缓存注解
@Cacheable | 该注解能够根据方法请求参数对其结果进行缓存 |
---|---|
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 |
在体验之前,我们先自己配置个Bean用于打印方法执行的情况
@Configuration
public class MyConfig {
@Bean
public Logger logger(){
return LoggerFactory.getLogger(this.getClass());
}
}
或者直接在类方法里取logger,这样能获取到执行打印的具体类
在Service中添加logger
@Service
public class BaseServiceImpl implements BaseService {
@Autowired
private BaseMapper baseMapper;
@Autowired
private Logger logger;
@Override
public User getUserByGyh(String gyh) {
logger.info("取出用户");
return baseMapper.getUserByGyh(gyh);
}
}
或者直接在yml文件中调高日志级别
#调高日志级别能看到底层sql语句
logging:
level:
com:
scj:
mana: debug
能看到我们每次访问url底层都会调用sql语句(实际上就是访问了数据库)
3.2.1 @Cacheable 不调用方法的缓存
使用@Cacheable会把我们的结果缓存起来(方法级别,方法名和参数相同则直接调用结果,不经过数据库)
@Cacheable的常用参数(参数遵循spEL语法)
cacheNames = “user” | 指定缓存块名称 |
---|---|
key = “#gyh” | 指定缓存的key |
condition = “#gyh ne ‘’” | 指定缓存的条件 |
unless = “#gyh eq ‘’” | 指定不缓存的条件 |
value = “user” | 指定缓存的值 |
完整注解
@Cacheable(cacheNames = "user",key = "#gyh",condition = "#gyh ne ''",unless = "#gyh eq ''")
或者可以同时缓存到多个块中
@Cacheable(cacheNames = {"user","student","person"},key = "#gyh",condition = "#gyh ne ''",unless = "#gyh eq ''")
我们在方法上添加注解后观察后台是否还打印logger?
@Service
public class BaseServiceImpl implements BaseService {
@Autowired
private BaseMapper baseMapper;
@Autowired
private Logger logger;
@Cacheable(cacheNames = "user",key = "#gyh",condition = "#gyh ne ''",unless = "#gyh eq ''")
@Override
public User getUserByGyh(String gyh) {
logger.info("取出用户");
return baseMapper.getUserByGyh(gyh);
}
}
第一次执行后就不再执行该方法,从缓存中取出
3.2.2 @CachePut 调用方法的缓存
应用场景:当修改了数据库的某个数据,同时更新缓存
- 先调用目标方法
- 然后更新缓存中的数据(如果存在,不存在创建)
因为在Rest中的Put实际上就是Update的替换,所以我们@CachePut一般对应加载我们的update方法上(service层)
延伸:MVC架构我们应该怎么去合理安排每层干的事情?
我们先创建一个普通的update方法
开发顺序从前往后
首先设计我们的页面index.html
<!doctype html>
<html lang="zh" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>欢迎</title>
</head>
<body>
当前时间:<p th:text="${time}">Red Chair</p>
系统配置信息:<p th:text="${siteSettings}">Red Chair</p>
<form action="/user/put" method="post">
id:<input type="text" name="uId">
gyh:<input type="text" name="uGyh">
mobile:<input type="text" name="uMobile">
name:<input type="text" name="uName">
role:<input type="text" name="userRole">
<button>提交</button>
</form>
</body>
</html>
编写控制器代码BaseCtrl
@ResponseBody
@PostMapping("/user/put")
public String putUser(User user){
Logger logger = LoggerFactory.getLogger(BaseCtrl.class);
logger.info(user.toString());
baseService.putUser(user);
return "更新成功";
}
编写Service层代码(不编写接口)
public void putUser(User user) {
logger.info("更新用户");
baseMapper.putUser(user);
}
最后编写Mapper
@Update("UPDATE `live`.`user` SET `u_gyh` = #{uGyh}, `u_mobile` = #{uMobile}, `u_name` = #{uName}, `user_role` = NULL WHERE `u_id` = #{uId}")
void putUser(User user);
运行后更新成功
测试每次更新是否调用Service层方法
[外链图片转存失败(img-VHhC74HV-1567948149028)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566013681391.png)]
可见每次更新都调用了底层sql
我们在Service方法上加上@CachePut注解
@CachePut(cacheNames = "user")
public void putUser(User user) {
logger.info("更新用户");
baseMapper.putUser(user);
}
然后按照get,put,get的顺序进行测试
我们发现查出来的是原来的数据
为什么呢?
因为我们在@CachePut里指定了存储块,并没有指定key
修改BaseService中的代码
@CachePut(cacheNames = "user",key = "#user.uId")
public void putUser(User user) {
logger.info("更新用户");
baseMapper.putUser(user);
}
修改结束后我们再测试
还是查不出来
因为我们指定的键不一致(查询用的gyh,更新用的uId)
统一后
@CachePut(cacheNames = "user",key = "#user.uGyh")
public void putUser(User user) {
logger.info("更新用户");
baseMapper.putUser(user);
}
测试
发现更新后查不出来?
why?
因为缓存机制规定,即使是不需要返回值的update函数,我们仍需要将需要缓存的数据return回去,作为缓存的数据,所以我们的Service方法修正如下
@CachePut(value = "user",key = "#user.uGyh")
public User putUser(User user) {
logger.info("更新用户");
baseMapper.putUser(user);
return user;
}
以下的写法也是可以的(上面是使用参数,下面是使用返回值)
@CachePut(value = "user",key = "#result.uGyh")
public User putUser(User user) {
logger.info("更新用户");
baseMapper.putUser(user);
return user;
}
3.2.3 @CacheEvict 缓存清除
参数同上,但是多了
参数 | 作用 |
---|---|
allEntries = {boolean} | 用于是否删除块中的所有数据,使用这个时不用指定key,默认false |
beforeInvocation = {boolean} | 缓存清除是否在方法之前,默认false |
3.2.4 @Caching 组合注解
实际上是以上三个注解的组合注解,一般用于场景比较复杂的情况下(略)
3.2.5 @CacheConfig 类级别的配置注解
通过@CacheConfig(cacheNames = “user”)加在类上可以将整个类中的方法都默认存到user块中,只用指定key即可
4. Redis 环境搭建
SpringBoot默认使用的缓存中间件是ConcurrentMapCacheManager
一般用于教学或者测试
在实际的开发中我们很少会用到
实际开发过程中我们更愿意使用性能更高,功能更强大的Redis
比较著名的缓存中间件还有:
- Redis
- Memcache
- Ehcache
以上的缓存中间件导入后SpringBoot会根据你导入的中间件来自动配置(也可以自己手动配置)
Redis是内存中的数据库缓存系统,用C语言编写,它不仅仅可以作为缓存中间件来用,同样也可以作为nosql数据库来存储海量数据,和MongoDB齐名,学习Reids是我们迈向企业级开发的必经之路
SpringBoot也为Redis提供了良好的支持(starter)
4.1 安装Redis
http://www.redis.cn该网站上有所有Redis命令的参考文档
详细Windows下安装参考https://www.runoob.com/note/36178
4.1.1 下载Windows下的Redis(不讲Linux,因为Redis有Docker镜像,无需手动安装)
[外链图片转存失败(img-2fo7rW5l-1567948149029)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566093512641.png)]
4.1.2 安装过程略
4.1.3 安装完成后测试
进入到Redis的安装目录,找到Redis的脚手架程序redis-cli.exe,然后进行下图的测试
[外链图片转存失败(img-Bn7Jqv7U-1567948149030)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566798581132.png)]
[外链图片转存失败(img-TbvmqaYj-1567948149030)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566798627564.png)]
这样我们的Redis就安装完成了
如果需要类似navicat一样的数据库管理软件我们可以安装Redis Desktop Manager(收费的,有破解教程)
https://www.jianshu.com/p/6895384d2b9e
4.2 SpringBoot整合Redis
详细命令请进入Redis中文网Redis中文网
4.2.1 引入对应的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.2.2 配置Redis
redis:
host: 127.0.0.1
4.2.3 RedisTemplate 和 StringRedisTemplate
4.2.3.1 字符串操作
Redis常见的5大数据类型
- String:字符串
- List:列表
- Set:集合
- Hash:散列
- ZSet:有序集合
模板中提供了操作以上数据类型的方法,方法名结构如下
stringRedisTemplate.opsFor{type}();
除了String是
stringRedisTemplate.opsForValue();
别的都是加上数据类型名字即可;
编写一个测试类
@Autowired
RedisTemplate redisTemplate;//操作对象的
@Autowired
StringRedisTemplate stringRedisTemplate; //操作字符串的
@Test
public void redisTest(){
/*字符串操作*/
ValueOperations<String, String> stringOps = stringRedisTemplate.opsForValue();
/*保存字符串*/
stringOps.set("msg","hello");
Logger logger = LoggerFactory.getLogger(ManaApplicationTests.class);
logger.info(stringOps.get("msg"));
/*追加字符串*/
stringOps.append("msg"," beijing");
logger.info(stringOps.get("msg"));
/*列表操作*/
ListOperations<String, String> listOps = stringRedisTemplate.opsForList();
listOps.leftPush("list","1");
listOps.leftPush("list","2");
listOps.rightPush("list","3");
logger.info(listOps.leftPop("list"));
logger.info(listOps.leftPop("list"));
logger.info(listOps.leftPop("list"));
/*更多命令请参照redis.cn*/
}
4.2.3.2 对象操作
保存一个对象,编写测试方法
@Autowired
BaseMapper baseMapper;
@Test
public void redisObjectTest(){
User userByGyh = baseMapper.getUserByGyh("0033081800192");
redisTemplate.opsForValue().set("user",userByGyh);
}
报错信息如下
[外链图片转存失败(img-3M55luOK-1567948149031)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566808385435.png)]
提示我们没有序列化我们的对象
@Data
public class User implements Serializable{
private int uId;
private String uGyh;
private String uMobile;
private String uName;
private Integer userRole;
}
序列化后我们就可以在数据库中看到我们的对象
[外链图片转存失败(img-3IyKExTC-1567948149032)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566827620543.png)]
问题:既然Redis是内存中的数据库,重启后还有没有值?
有,因为Redis也会做持久化,缓存只是它的功能之一,数据库就是为了持久化!
如何将对象以json形式存储(不以java自带序列化形式存储)
使用阿里的fastjson,然后用String方式存储(常用)
代码略
自定义序列化器
摁两次shift唤出全局搜索,步骤如下,找出SpringBoot为我们定义的序列化器
[外链图片转存失败(img-eKBBHewn-1567948149033)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566828414091.png)]
拷贝如下代码
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
定义我们自己的序列化器MyRedisConfig
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, User> template = new RedisTemplate();
Jackson2JsonRedisSerializer<User> serializer = new Jackson2JsonRedisSerializer<User>(User.class);
template.setDefaultSerializer(serializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
编写测试方法
@Autowired
RedisTemplate<Object,User> userRedisTemplate;
@Test
public void userObjectTest(){
User userByGyh = baseMapper.getUserByGyh("0033081800192");
userRedisTemplate.opsForValue().set("user",userByGyh);
}
查看数据库中,多出了json序列化数据
[外链图片转存失败(img-ScMGlDcH-1567948149034)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566829307435.png)]
4.2.3.3 自定义序列化器(较复杂,可以自己百度)
4.2.4 实战开发
实际开发中,我们基本不会用RedisTemplate
都是使用3节中讲的注解交给SpringBoot帮我们管理
在一些场景下,需要我们手动操作缓存的时候
可以在Service层使用以下代码(必须去掉@Cacheable,否则自己操作不了缓存)
@Autowired
RedisCacheManager redisCacheManager;
/*@Cacheable(value = "user",key = "#gyh",condition = "#gyh ne ''",unless = "#gyh eq ''")*/
public User getUserByGyh(String gyh) {
logger.info("取出用户");
Cache user = redisCacheManager.getCache("user");
user.put("user:2",baseMapper.getUserByGyh(gyh));
return baseMapper.getUserByGyh(gyh);
}
查看数据库,多出一条缓存
[外链图片转存失败(img-SJAkktXE-1567948149035)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1566833458049.png)]
以上就是缓存的所有内容