纯手写SpringBoot教案系列-SpringBoot进阶课程-缓存

六.缓存

1. 引入模块

  • Cache(似乎已经默认带缓存)
  • Web(为了方便测试)
  • Mysql
  • JDBC
  • Mybatis

2. 环境搭建

  1. 创建数据库文件,创建实验表
  2. 创建Java Bean(pojo),用于封装我们的数据
  3. 整合Mybatis操作数据库(参照第四章)
  4. 简单写个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” 指定缓存的值

完整注解

扫描二维码关注公众号,回复: 8499797 查看本文章
@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 调用方法的缓存

应用场景:当修改了数据库的某个数据,同时更新缓存

  1. 先调用目标方法
  2. 然后更新缓存中的数据(如果存在,不存在创建)

因为在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)

redis恶补链接

4.1 安装Redis

linux下载地址

windows下载地址

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)]

以上就是缓存的所有内容

发布了37 篇原创文章 · 获赞 35 · 访问量 6538

猜你喜欢

转载自blog.csdn.net/itkfdektxa/article/details/100638732
今日推荐