SpringBoot整合各种缓存技术

SpringBoot整合各种缓存技术

​ 缓存是一种介于数据永久存储介质与应用程序之间的数据临时存储介质,使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能。此外缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间。而springboot提供了对市面上几乎所有的缓存技术进行整合的方案。

SpringBoot内部缓存解决方案

​ SpringBoot技术提供内置缓存解决方案,并使用缓存技术进行数据的快速操作,例如读取缓存数据和写入数据到缓存,下面简单案例:

  1. 在Boot项目中的pom.xml导入缓存的对应坐标

    		<dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-cacheartifactId>
            dependency>
    
  2. 启用缓存,在引导类上添加@EnableCaching注解配置springboot程序可以使用缓存。

    @SpringBootApplication
    //开启缓存功能
    @EnableCaching
    public class Springboot16CacheApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot16CacheApplication.class, args);
        }
    
    }
    
  3. 在需要使用缓存的数据上定义缓存相关配置,这里简单在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值作为缓存名称。

    ​ 在执行添加缓存注解的方法时,程序会先判断对应名称在缓存中是否有数据,如果不是的话就访问数据库,并且将数据存到缓存,如果是的话,则直接在缓存中查数据。


    手机验证码缓存案例

    ​ 在登入某个平台时,经常使用手机号获取验证码的形式登入,而验证码一般是放到缓存中保存,并设置个有效时间,下面将用各种缓存技术来模拟验证码在缓存中使用,需求如下:

    1. 接收手机号后返回一个6位数的验证码并将其存到缓存中
    2. 将用户输入的验证码与缓存中的验证码做比较,若一致则返回成功

    下面将使用两个表现层接口来模拟这两个需求。

    使用SpringBoot内置的缓存技术来实现
    1. 导入SpringBoot提供的缓存技术坐标到pom.xml中

      		<dependency>
                  <groupId>org.springframework.bootgroupId>
                  <artifactId>spring-boot-starter-cacheartifactId>
              dependency>
      
    2. 在启动类上添加开启缓存的注解

      @SpringBootApplication
      //开启缓存功能
      @EnableCaching
      public class Springboot16CacheApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(Springboot16CacheApplication.class, args);
          }
      
      }
      
    3. 创建验证码的实体类,里面有手机号和验证码两个属性

      @Data
      @Service
      public class sms {
          private String tell;
          private String code;
      
      }
      
    4. 创建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注解即可。

    5. 创建获取验证码的工具类:接收一个手机号并随机返回一个六位数的号码即可

      @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。

    6. 创建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;
          }
      }
      
    7. 使用postmain测试

      • 获取验证码

SpringBoot整合各种缓存技术_第1张图片

  - 对比验证码

SpringBoot整合各种缓存技术_第2张图片

​ 手机验证码的功能已经完成,可以看到整体还是较为简单的,上面的案例默认是使用SpringBoot内置的缓存simple。下面将基于手机验证码案例使用各种各样的缓存技术来代替simple。

SpringBoot整合Ehcache缓存

  1. 导入坐标,使用什么技术就导入什么坐标
      <dependency>
            <groupId>net.sf.ehcachegroupId>
            <artifactId>ehcacheartifactId>
        dependency>
  1. 在application.xml配置文件中配置

      cache:
        type: ehcache
    

    cache: 是在spring下一级。type:选择哪种缓存技术

  2. 添加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;
        }
    
  3. 在postmain中测试。

总结:可以发现SpringBoot中更换缓存技术还是十分简单便捷的。

  • 先导入使用技术的坐标
  • 在配置文件中进行相应的配置,将cache:type:改成对应的技术类型,再添加ehcache的配置类。

SpringBoot整合Redis缓存
  1. 导入对应坐标

    	   <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            
  2. 在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中配置缓存的相应设置。

  3. 在postmain中测试

总结:使用Redis做缓存比ehcache还省了一步添加配置文件,直接在yml文件中就可以设置redis的对应配置。


上面都是单独使用缓存技术,如果在开发中需要使用多种缓存技术的话就需要使用jetcache了,目前jetcache支持4种缓存技术。

  • 本地缓存(local)
    • LinkedHashMap
    • Caffeine
  • 远程缓存(Remtoe)
    • Redis
    • Tair

下面在手机验证码中使用此技术

SpringBoot整合jetcache
  • 远程访问
  1. 导入springboot整合jetcache对应的坐标starter,当前坐标默认使用的远程方案是redis

    		<dependency>
                <groupId>com.alicp.jetcachegroupId>
                <artifactId>jetcache-starter-redisartifactId>
                <version>2.6.2version>
            dependency>
    
  2. 在application中配置相应设置

    jetcache:
      remote:
        default:
          type: redis
          host: localhost
          port: 6379
          poolConfig:
            maxTotal: 50
    

    这里的jetcache是和spring配置同一级。

    • remote:代表这组配置是远程访问
    • default:代表这组配置是默认配置
    • type:缓存技术的类型,默认为Redis
    • host:连接redis的地址
    • port:端口号
    • poolCanfig: maxTotal: 设置连接池最大连接数量
  3. 在启动类添加@EnableCreateCacheAnnotation注解开启缓存

    @SpringBootApplication
    @EnableCreateCacheAnnotation
    public class Springboot17JetcacheApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot17JetcacheApplication.class, args);
        }
    
    }
    
  4. 编写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来设置缓存信息。

    • name:为缓存key的前置名称
    • expire:缓存存在的时间
    • timeUnit:时间单位,什么设置的是秒
  5. 编写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;
        }
    }
    

    这里的工具类去除了缓存注解也依然可以使用

  6. 在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也提供了对应的功能,即方法缓存。下面通过一个查询图书案例来演示

图书查询演示jetcache的方法缓存

案例准备:

  1. 准备好对应的数据库,表。在navicat中快捷创建如下表
    SpringBoot整合各种缓存技术_第3张图片

    添加一些数据

    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', '书名测试数据', '描述测试数据');
    
  2. 准备好一个springboot项目,里面所用技术为web,mysql,mybatisplus并工具自己的数据库信息在yml文件中进行配置

  3. 在启动类上开启缓存注解和方法缓存注解

    @SpringBootApplication
    @EnableCreateCacheAnnotation
    @EnableMethodCache(basePackages = "com.itheima")
    public class Springboot17JetcacheApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Springboot17JetcacheApplication.class, args);
        }
    
    }
    
  4. 在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

  5. 创建对应的实体类,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;
        }
    }
    
  6. 在postmain中测试

  7. 远程方案的数据同步

    ​ 由于远程方案中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
    ---------+-------+-------+------+-------+-------+---------+--------------+--------------
    

    总结

    1. jetcache是一个类似于springcache的缓存解决方案,自身不具有缓存功能,它提供有本地缓存与远程缓存多级共同使用的缓存解决方案
    2. jetcache提供的缓存解决方案受限于目前支持的方案,本地缓存支持两种,远程缓存支持两种
    3. 注意数据进入远程缓存时的类型转换问题
    4. jetcache提供方法缓存,并提供了对应的缓存更新与刷新功能
    5. jetcache提供有简单的缓存信息命中报表方便开发者即时监控缓存数据命中情况

你可能感兴趣的:(Spring,缓存,spring,boot,java)