Spring Boot 缓存

Spring Boot 使用缓存

  1. 在系统访问量越来越大后,首先出现瓶颈的往往是数据库,而为了减少数据库的压力,我们可以选择如下方式优化(暂时不考虑优化数据库的硬件、索引等):
    1. 读写分离:通过将读操作分流到从节点,避免主节点过大。
    2. 分库分表:通过将读操作分摊到多个节点,避免单节点压力过大。
    3. 缓存:相比数据库来说,缓存往往能够提供更快的读速度,从而减小数据库的压力。
  2. Spring Cache:Spring 3.1 引入了激动人心的基于注释的缓存技术,它本质上不是一个具体的缓存实现方案(如 EhCache 或 OsCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能达到缓存方法的返回对象的效果。Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案。
    1. 其特点如下:
      • 通过少量的配置 annotation 注释即可使得既有代码支持缓存。
      • 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存。
      • 支持 Spring Expression Language,能使用对象的任何属性或方法来定义缓存的 key 或 condition。
      • 支持 AspectJ ,并通过其实现任何方法的缓存支持。
      • 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性。
    2. 简单来说,就是我们可以像使用@Transaction声明式事务那样,使用 Spring Cache 提供的@Cacheable等注解声明式缓存。而在实现原理上,也是基于 Spring AOP 拦截,实现缓存相关的操作。
  3. 注解
    1. @Cacheable: 添加在方法上,缓存方法的执行结果。执行过程如下:首先判断方法执行结果的缓存,如果有,直接返回缓存结果。没有的话,执行方法,获得方法结果,根据缓存条件将结果缓存,最后,返回结果。
      1. 常用属性:cacheNames: 缓存名;values: 和 cacheNames 属性相同;key: 缓存的 key,允许空;condition: 基于方法入参,判断要缓存的条件,允许空;unless:基于方法返回,判断不缓存的条件,允许空;如果空,不进行入参判断
    2. @CachePut:添加在方法上,缓存方法的执行结果。不同于 CacheNames,它缓存步骤如下:首先,执行方法获得结果,也就是说,无论是否有缓存都会执行方法,其他一样。常用属性同上。
      1. 一般来说,使用方式如下:Cacheable:搭配读操作,实现缓存的被动写;@CachePut:搭配写操作,实现缓存的主动写。
    3. @CacheEvict:添加在方法上,删除缓存
    4. @CacheConfig:添加在类上
    5. @Caching:添加在方法上,可以组合@Cacheable@CachePut@CacheEvict注解,不常用
    6. @EnableCaching:标记开启 Spring Cache 功能,一定要添加,一般添加在启动类上。
  4. Spring Boot 集成
    1. 在 Spring Boot 里,提供了 spring-boot-starter-cache库,实现缓存的自动化配置。

    2. 在 Java 中,常见的缓存工具和框架:本地缓存:Ehcache、Caffeine(Ehcache 的功能更丰富,Caffeine 的性能比 Guava 好);分布式缓存:Redis、Memcached、Tair(Redis 常用)。[区别]:[https://blog.csdn.net/weixin_43621315/article/details/124121327?spm=1001.2014.3001.5502]

    3. 那 Spring 怎么知道使用哪种缓存呢?还是在 Spring 的自动配置类里:CacheAutoConfiguration 类

在这里插入图片描述 可以看到加载顺序,最次是 SimpleCache 和 NoOpCache ,这是 Spring 的本地缓存。

  1. Ehcache : 一个纯 Java 的进程内缓存框架,具有快速、精干的特点,是 Hibernate 中默认的 CacheProvider。主要的特性有:快速、简单、多种缓存策略、缓存数据有两级:内存和磁盘,因此无需担心容量问题、缓存数据会在虚拟机重启的过程中写入磁盘、可以通过 RMI、可插入 API 等方式进行分布式缓存等。

    1. 依赖
        	<dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.25</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
    
            <dependency>
                <groupId>net.sf.ehcache</groupId>
                <artifactId>ehcache</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    
    1. yaml
    spring:
      # datasource 数据源配置内容
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/db1?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password:
      # cache 缓存配置内容
      cache:
        type: ehcache
    
    # mybatis-plus 配置内容
    mybatis-plus:
      configuration:
        map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
      global-config:
        db-config:
          id-type: auto # ID 主键自增
          logic-delete-value: 1 # 逻辑已删除值(默认为 1)
          logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
      mapper-locations: classpath*:mapper/*.xml
      type-aliases-package: com.gui.ehcachetest.dataobject
    
    # logging
    logging:
      level:
        # dao 开启 debug 模式 mybatis 输入 sql
        cn:
          iocoder:
            springboot:
              lab21:
                cache:
                  mapper: debug
    

    与yaml 同目录创建Ehcache.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
        <!-- users 缓存 -->
        <!-- name:缓存名 -->
        <!-- maxElementsInMemory:最大缓存 key 数量 -->
        <!-- timeToLiveSeconds:缓存过期时长,单位:秒 -->
        <!-- memoryStoreEvictionPolicy:缓存淘汰策略 -->
        <cache name="users"
               maxElementsInMemory="1000"
               timeToLiveSeconds="60"
               memoryStoreEvictionPolicy="LRU"/>  <!-- 缓存淘汰策略 -->
    
    </ehcache>
    
    com.gui.ehcachetest.dataobject下创建 DO
    
    @TableName(value = "users")
    @Data
    @ToString
    public class UserDo {
    
        private Integer id;
        private String username;
        private String password;
        private Date createTime;
    
        @TableLogic
        private Integer deleted;
    }
    
    //com.gui.ehcachetest.mapper 下创建mapper
    @Repository
    @CacheConfig(cacheNames = "users")
    public interface UserMapper extends BaseMapper<UserDo> {
    
        @Cacheable(value = "users",key = "#id")
        UserDo selectById(Integer id);
    
        @CachePut(value = "users",key = "#userDo.id")
        default UserDo insert0(UserDo userDo) {
            this.insert(userDo);
            return userDo;
        }
    
        @CacheEvict(key = "#id")
        int deleteById(Integer id);
    }
    

    最后在启动类加上 @EnableCaching就行了。

  2. Redis

    1. pom.xml
        	<dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.3.2</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <exclusions>
                    <!-- 去掉 Lettuce 的依赖,因为 Spring Boot 优先使用 Lettuce 作为 Redis 客户端 -->
                    <exclusion>
                        <groupId>io.lettuce</groupId>
                        <artifactId>lettuce-core</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!-- 引入 Jedis 的依赖,使 Spring Boot 实现对 Jedis 的自动化配置-->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </dependency>
    
            <!-- 实现对 json 序列化工具-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.80</version>
            </dependency>
            <!---->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.13.2.2</version>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    

    yaml

    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/db1?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: admin111111
    
      redis:
        host: 127.0.0.1
        port: 6379
        password:
        database: 0
        timeout: 0
        jedis:
          pool:
            max-active: 8
            max-idle: 8
            min-idle: 0
            max-wait: -1
    
      cache:
        type: REDIS
    
    mybatis-plus:
      configuration:
        map-underscore-to-camel-case: true
      global-config:
        db-config:
          id-type: AUTO
          logic-delete-value: 1
          logic-not-delete-value: 0
      mapper-locations:
        - classpath*:mapper/*.xml
      type-aliases-package: com.gui.dataredis.dataobject
    
    logging:
      level:
        com:
          gui:
            dataredis:
              mapper: debug
    

    其他的与 Ehcache 一致。

    直接测试:

    		@Resource
        private UserMapper userMapper;
    
        @Resource
        private CacheManager cacheManager;
    
    		@Test
        public void testSelectById() {
            UserDO userDO = userMapper.selectById(1);
            if (userDO != null) {
                System.out.println(userDO);
            }
            //
            assert userDO != null;
            Assert.notNull(new IErrorCode() {
                               @Override
                               public long getCode() {
                                   return 0;
                               }
    
                               @Override
                               public String getMsg() {
                                   return "缓存为空";
                               }
                           },
                    Objects.requireNonNull(cacheManager.getCache("users")).get(userDO.getId(), UserDO.class));
    
            userDO = userMapper.selectById(1);
    
            System.out.println(userDO);
        }
    
        @Test
        public void testInsertUser() {
    
            UserDO userDO = new UserDO();
            userDO.setUsername(UUID.randomUUID().toString());
            userDO.setPassword("cool");
            userDO.setCreateTime(new Date());
            userDO.setDeleted(0);
            userMapper.insert0(userDO);
            //
            Assert.notNull(new IErrorCode() {
                               @Override
                               public long getCode() {
                                   return 0;
                               }
    
                               @Override
                               public String getMsg() {
                                   return "缓存为空";
                               }
                           },
                    Objects.requireNonNull(cacheManager.getCache("users")).get(userDO.getId(), UserDO.class));
        }
    
        @Test
        public void testDelete() {
            UserDO userDO = new UserDO();
            userDO.setUsername(UUID.randomUUID().toString());
            userDO.setPassword("cool");
            userDO.setCreateTime(new Date());
            userDO.setDeleted(0);
            userMapper.insert0(userDO);
            Assert.notNull(new IErrorCode() {
                               @Override
                               public long getCode() {
                                   return 0;
                               }
    
                               @Override
                               public String getMsg() {
                                   return "缓存为空";
                               }
                           },
                    Objects.requireNonNull(cacheManager.getCache("users")).get(userDO.getId(), UserDO.class));
    
            userMapper.deleteById(userDO.getId());
            Assert.isNull(new IErrorCode() {
                               @Override
                               public long getCode() {
                                   return 0;
                               }
    
                               @Override
                               public String getMsg() {
                                   return "缓存bu为空";
                               }
                           },
                    Objects.requireNonNull(cacheManager.getCache("users")).get(userDO.getId(), UserDO.class));
    
        }
    

    转载于:https://www.iocoder.cn/Spring-Boot/Cache/?github


版权声明:本文为weixin_43621315原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。