缓存是一种介于数据永久存储介质与应用程序之间的数据临时存储介质,使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能。此外缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间。而springboot提供了对市面上几乎所有的缓存技术进行整合的方案。
SpringBoot技术提供内置缓存解决方案,并使用缓存技术进行数据的快速操作,例如读取缓存数据和写入数据到缓存,下面简单案例:
在Boot项目中的pom.xml导入缓存的对应坐标
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
启用缓存,在引导类上添加@EnableCaching
注解配置springboot程序可以使用缓存。
@SpringBootApplication
//开启缓存功能
@EnableCaching
public class Springboot16CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot16CacheApplication.class, args);
}
}
在需要使用缓存的数据上定义缓存相关配置,这里简单在service类中写个根据id查数据
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
/**
* 根据id查询
* @param id
* @return
*/
@Override
@Cacheable(value = "caheSpce",key="#id")
public Book getById(Integer id) {
Book book = bookDao.selectById(id);
return book;
}
在方法上添加@Cacheable
注解@value
代表缓存的储存位置,可以理解为是一个存储空间的名字。key
代表缓存的数据名字,使用”#id“来表示储存的是方法形参中的id值作为缓存名称。
在执行添加缓存注解的方法时,程序会先判断对应名称在缓存中是否有数据,如果不是的话就访问数据库,并且将数据存到缓存,如果是的话,则直接在缓存中查数据。
在登入某个平台时,经常使用手机号获取验证码的形式登入,而验证码一般是放到缓存中保存,并设置个有效时间,下面将用各种缓存技术来模拟验证码在缓存中使用,需求如下:
下面将使用两个表现层接口来模拟这两个需求。
导入SpringBoot提供的缓存技术坐标到pom.xml中
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
在启动类上添加开启缓存的注解
@SpringBootApplication
//开启缓存功能
@EnableCaching
public class Springboot16CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot16CacheApplication.class, args);
}
}
创建验证码的实体类,里面有手机号和验证码两个属性
@Data
@Service
public class sms {
private String tell;
private String code;
}
创建service接口与实体类
public interface SmsService {
public String SmsCode(String tele);//通过电话号码获取验证码并存到缓存
public Boolean SmsBoolean(sms sms);//比对用户输入的验证码是否与缓存中的一致
}
这里提供两个方法,分别为获取验证码和比对验证码
下面实体类中使用了步骤5的工具类,用来生成验证码和获取缓存中的验证码。
@Service
public class SmsSericeImpl implements SmsService {
@Autowired
private com.itheima.utils.mima mima;
@Override
@CachePut(value = "huangcun",key = "#tele")
public String SmsCode(String tele) {
String passworld = mima.passworld(tele);
return passworld;
}
@Override
public Boolean SmsBoolean(sms sms) {
String code1 = sms.getCode();
String ps = mima.get(sms.getTell());
boolean equals = code1.equals(ps);
return equals;
}
}
获取验证码后,当验证码失效时必须重新获取验证码,因此在获取验证码的功能上不能使用
@Cacheable
注解,@Cacheable注解是缓存中没有值则放入值,缓存中有值则取值。此处的功能仅仅是生成验证码并放入缓存,并不具有从缓存中取值的功能,因此不能使用@Cacheable注解,应该使用仅具有向缓存中保存数据的功能,使用@CachePut
注解即可。
创建获取验证码的工具类:接收一个手机号并随机返回一个六位数的号码即可
@Component
public class mima {
private String [] patch = {"000000","00000","0000","000","00","0",""};
public String passworld(String t){
int i = t.hashCode();
int e = 20206666;
long i1 = i ^ e;
long l = System.currentTimeMillis();
i1 = i1 ^ l;
long l1 = i1 % 1000000;
l1 = l1<0? -l1:l1;
String code = l1+"";
int x = code.length();
return patch[x]+code;
}
@Cacheable(value = "huangcun",key = "#tele")
public String get(String tele){
return null;
}
}
该工具类包含了两个方法,第一个是通过手机号获取一个随机的六位数,第二个是获取缓存中的值。
使用
@Cacheable
注解,如果缓存中有值就获取缓存中的值,没有就返回一个null。
创建Controller层接口
@RestController
@RequestMapping("/sms")
public class SmsController {
@Autowired
private SmsService smsService;
@GetMapping
public String get(String tele){
String ps = smsService.SmsCode(tele);
return ps;
}
@PostMapping
public Boolean yanzhen(sms sms){
Boolean aBoolean = smsService.SmsBoolean(sms);
return aBoolean;
}
}
使用postmain测试
- 对比验证码
手机验证码的功能已经完成,可以看到整体还是较为简单的,上面的案例默认是使用SpringBoot内置的缓存simple。下面将基于手机验证码案例使用各种各样的缓存技术来代替simple。
SpringBoot整合Ehcache缓存
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
dependency>
在application.xml配置文件中配置
cache:
type: ehcache
cache: 是在spring下一级。type:选择哪种缓存技术
添加ehcahe的配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="D:\ehcache" />
<defaultCache
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="60"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU" />
<cache
name="huangcun"
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="10"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU" />
ehcache>
这里的配置文件中有两组配置,第一组是默认的,第二组是我们根据自己的需求设置的,可以看到比默认的多了一个name属性,这里的name属性要与设置缓存注解中的value相同。
@CachePut(value = "huangcun",key = "#tele")
public String SmsCode(String tele) {
String passworld = mima.passworld(tele);
return passworld;
}
在postmain中测试。
总结:可以发现SpringBoot中更换缓存技术还是十分简单便捷的。
导入对应坐标
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
在application.yml中进行相应的配置
redis:
host: localhost
port: 6379
cache:
redis:
use-key-prefix: false
key-prefix: sms_
cache-null-values: false
time-to-live: 600s
type: redis
开始先配置连接redis的信息,在cache中配置缓存的相应设置。
在postmain中测试
总结:使用Redis做缓存比ehcache还省了一步添加配置文件,直接在yml文件中就可以设置redis的对应配置。
上面都是单独使用缓存技术,如果在开发中需要使用多种缓存技术的话就需要使用jetcache了,目前jetcache支持4种缓存技术。
下面在手机验证码中使用此技术
导入springboot整合jetcache对应的坐标starter,当前坐标默认使用的远程方案是redis
<dependency>
<groupId>com.alicp.jetcachegroupId>
<artifactId>jetcache-starter-redisartifactId>
<version>2.6.2version>
dependency>
在application中配置相应设置
jetcache:
remote:
default:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50
这里的
jetcache
是和spring配置同一级。
在启动类添加@EnableCreateCacheAnnotation
注解开启缓存
@SpringBootApplication
@EnableCreateCacheAnnotation
public class Springboot17JetcacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot17JetcacheApplication.class, args);
}
}
编写service实现类,这里的就在上面手机验证码的案例进行改造
@Service
public class SmsSericeImpl implements SmsService {
@Autowired
private com.itheima.utils.mima mima;
@CreateCache(name = "jetcache_",expire = 10,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.BOTH)
private Cache<String,String> jetCache;
@Override
public String SmsCode(String tele) {
String passworld = mima.passworld(tele);
jetCache.put(tele,passworld);
return passworld;
}
@Override
public Boolean SmsBoolean(sms sms) {
/*String code1 = sms.getCode();
String ps = mima.get(sms.getTell());*/
String code = jetCache.get(sms.getTell());
boolean equals = sms.getCode().equals(code);
return equals;
}
}
通过上面的代码可以看到,创建了一个cache
缓存对象,泛型都为String,在创建对象上边添加@CreateCache
来设置缓存信息。
编写Controller
@RestController
@RequestMapping("/msm")
public class SmsController {
@Autowired
private SmsService smsService;
@GetMapping
public String huoqu(String tele){
String ps = smsService.SmsCode(tele);
return ps;
}
@PostMapping
public Boolean yaqnz(sms sms){
Boolean aBoolean = smsService.SmsBoolean(sms);
return aBoolean;
}
}
工具类
@Component
public class mima {
private String [] patch = {"000000","00000","0000","000","00","0",""};
public String passworld(String t){
int i = t.hashCode();
int e = 20206666;
long i1 = i ^ e;
long l = System.currentTimeMillis();
i1 = i1 ^ l;
long l1 = i1 % 1000000;
l1 = l1<0? -l1:l1;
String code = l1+"";
int x = code.length();
return patch[x]+code;
}
public String get(String tele){
return null;
}
}
这里的工具类去除了缓存注解也依然可以使用
在Postmain中测试
在使用jetcache时可以进行多组配置,默认是default。可以工具需求自己更改,更改后创建缓存对象时添加注解需要使用area
属性来选择,不写改属性默认选择default,因此上面的案例中选择的就是默认配置
jetcache:
remote:
default:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50
huangcun:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50
@CreateCache(area = "hangcun"name = "jetcache_",expire = 10,timeUnit = TimeUnit.SECONDS)
private Cache<String,String> jetCache;
本地缓存
在导入jetcache的坐标后,直接在yml文件中设置本地缓存的相关配置
jetcache:
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
poolConfig:
maxTotal: 50
为了加速数据获取时key的匹配速度,jetcache要求指定key的类型转换器。简单说就是,如果你给了一个Object作为key的话,我先用key的类型转换器给转换成字符串,然后再保存。等到获取数据时,仍然是先使用给定的Object转换成字符串,然后根据字符串匹配。由于jetcache是阿里的技术,这里推荐key的类型转换器使用阿里的fastjson。
接着直接在service实体类中添加一个属性将其更换到使用本地缓存
@CreateCache(name = "jetcache_",expire = 10,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
private Cache<String,String> jetCache;
添加了cacheType属性CacheType.LOCAL表示使用本地缓存,默认使用的是REMOTE表示使用远程缓存。BOTH表示远程和本地缓存都使用。
以上方案仅支持手工控制缓存,但是springcache方案中的方法缓存特别好用,给一个方法添加一个注解,方法就会自动使用缓存。jetcache也提供了对应的功能,即方法缓存。下面通过一个查询图书案例来演示
案例准备:
添加一些数据
INSERT INTO `tbl_book` VALUES (1, '测试数据2', 'Spring实战 第六版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
INSERT INTO `tbl_book` VALUES (2, '测试数据2', 'Spring 5核心原理与30个类手写实践', '十年沉淀之作,手写Spring精华思想');
INSERT INTO `tbl_book` VALUES (3, '测试数据2', 'Spring 5设计模式', '深入Spring源码刨析Spring源码中蕴含的10大设计模式');
INSERT INTO `tbl_book` VALUES (4, '测试数据2', 'Spring MVC+Mybatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
INSERT INTO `tbl_book` VALUES (5, '测试数据2', '轻量级Java Web企业应用实战', '源码级刨析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tbl_book` VALUES (6, '测试数据2', 'Java核心技术 卷Ⅰ 基础知识(原书第11版)', 'Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
INSERT INTO `tbl_book` VALUES (7, '测试数据2', '深入理解Java虚拟机', '5个纬度全面刨析JVM,大厂面试知识点全覆盖');
INSERT INTO `tbl_book` VALUES (8, '测试数据2', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
INSERT INTO `tbl_book` VALUES (9, '测试数据2', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
INSERT INTO `tbl_book` VALUES (10, '测试数据2', '直播就这么做:主播高效沟通实战指南', '李子柒、李佳奇、薇娅成长为网红的秘密都在书中');
INSERT INTO `tbl_book` VALUES (11, '测试数据2', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
INSERT INTO `tbl_book` VALUES (12, '测试数据2', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
INSERT INTO `tbl_book` VALUES (17, '测试数据2', '书名测试数据', '描述测试数据');
INSERT INTO `tbl_book` VALUES (18, '测试数据2', '书名测试数据', '描述测试数据');
INSERT INTO `tbl_book` VALUES (19, '测试数据2', '书名测试数据', '描述测试数据');
INSERT INTO `tbl_book` VALUES (20, '测试数据2', '书名测试数据', '描述测试数据');
INSERT INTO `tbl_book` VALUES (21, '测试数据2', '书名测试数据', '描述测试数据');
INSERT INTO `tbl_book` VALUES (22, '测试数据2', '书名测试数据', '描述测试数据');
INSERT INTO `tbl_book` VALUES (24, '测试数据2', '书名测试数据', '描述测试数据');
准备好一个springboot项目,里面所用技术为web,mysql,mybatisplus并工具自己的数据库信息在yml文件中进行配置
在启动类上开启缓存注解和方法缓存注解
@SpringBootApplication
@EnableCreateCacheAnnotation
@EnableMethodCache(basePackages = "com.itheima")
public class Springboot17JetcacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot17JetcacheApplication.class, args);
}
}
在yml中进行配置
jetcache:
statIntervalMinutes: 1
local:
default:
type: linkedhashmap
keyConvertor: fastjson
remote:
default:
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecode: java
poolConfig:
maxTotal: 50
由于redis缓存中不支持保存对象,因此需要对redis设置当Object类型数据进入到redis中时如何进行类型转换。需要配置keyConvertor表示key的类型转换方式,同时标注value的转换类型方式,值进入redis时是java类型,标注valueEncode为java,值从redis中读取时转换成java,标注valueDecode为java
创建对应的实体类,Dao,Service,Controller
实体类:使用lombok的@Data注解
@Data
public class Book implements Serializable {
private int id;
private String type;
private String name;
private String description;
}
注意,为了实现Object类型的值进出redis,需要保障进出redis的Object类型的数据必须实现序列化接口。
Dao
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
service:这里就只演示根据id查找对应数据
public interface BookService {
public Book getById(Integer id);//根据id查询单个
public List<Book> getAll();//查询所有
public Boolean Update(Book book);//修改
public Boolean Delete(Integer id);//根据id删除
public Boolean Save(Book book);//增加对象;
}
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
/**
* 根据id查询
* @param id
* @return
*/
@Override
@Cached(name = "book_",key = "#id",expire = 3600,cacheType = CacheType.REMOTE)
@CacheRefresh(refresh = 5)
public Book getById(Integer id) {
Book book = bookDao.selectById(id);
return book;
}
这里可以看到在方法上添加了@Cache
注解就代表此方法的返回值到缓存中,当访问此方法时会先从缓存中查询,缓存中没有再查数据库。
Controller层
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id){
Book byId = bookService.getById(id);
System.out.println(byId);
return byId;
}
}
在postmain中测试
由于远程方案中redis保存的数据可以被多个客户端共享,这就存在了数据同步问题。jetcache提供了3个注解解决此问题,分别在更新、删除操作时同步缓存数据,和读取缓存时定时刷新数据
更新缓存
@CacheUpdate(name="book_",key="#book.id",value="#book")
public boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
删除缓存
@CacheInvalidate(name="book_",key = "#id")
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
定时刷新缓存
@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
@CacheRefresh(refresh = 5)
public Book getById(Integer id) {
return bookDao.selectById(id);
}
jetcache还提供有简单的数据报表功能,帮助开发者快速查看缓存命中信息,只需要添加一个配置即可
jetcache:
statIntervalMinutes: 1
设置后,每1分钟在控制台输出缓存数据命中信息
[DefaultExecutor] c.alicp.jetcache.support.StatInfoLogger : jetcache stat from 2022-02-28 09:32:15,892 to 2022-02-28 09:33:00,003
cache | qps| rate| get| hit| fail| expire| avgLoadTime| maxLoadTime
---------+-------+-------+------+-------+-------+---------+--------------+--------------
book_ | 0.66| 75.86%| 29| 22| 0| 0| 28.0| 188
---------+-------+-------+------+-------+-------+---------+--------------+--------------
总结