• 九、Spring Boot 缓存(1)


    本章概要

    • Ehcache 2.x 缓存

    Spring 3.1 中开始对缓存提供支持,核心思路是对方法的缓存,当开发者调用一个方法时,将方法的参数和返回值作为 key/value 缓存起来,当再次调用改方法时,如果缓存中有数据,就直接从缓存中获取,否则再去执行该方法。但是,Spring 中并未提供缓存的实现,而是提供了一套缓存 API ,开发者可以自由选择缓存的实现,目前 Spring Boot 支持的缓存有如下几种:

    • JCache(JSR-107)
    • EhCache 2.x
    • Hazelcast
    • Infinispan
    • Couchbase
    • Redis
    • Caffeine
    • Simple

    此处只介绍常用的缓存实现 Ehcache 2.x 和 Redis,由于 Spring 早已将缓存领域统一,因此无论使用哪种缓存实现,不同的只是缓存配置,开发者使用的缓存注解是一致的(Spring 缓存注解和各种缓存实现的关系就像 JDBC 和各种数据库驱动的关系一样)。

    9.1 Ehcache 2.x 缓存

    Ehcache 缓存在Java开发领域久负盛名,在Spring Boot 中,只需要一个配置文件就可以将 Ehcache 集成到项目中。步骤如下:

    1.创建项目,添加缓存依赖

    创建 Spring Boot 项目,添加 spring-boot-starter-cache 依赖以及 Ehcache 依赖

    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-cacheartifactId>
    dependency>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
      <groupId>net.sf.ehcachegroupId>
      <artifactId>ehcacheartifactId>
    dependency>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-testartifactId>
      <scope>testscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. 添加缓存配置文件

    如果 Ehcache 的依赖存在,并且在 classpath 下有一个名为 echache.xml 的 Ehcache 配置文件,那么 EhCacheCacheManager 将会自动作为缓存的实现。因此,在 resources 目录下创建 ehcache.xml 文件作为 Ehcache 缓存的配置文件,如下:

    <ehcache>
        <diskStore path="java.io.tmpdir/cache"/>
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="false"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
        />
        
        <cache name="book_cache"
               maxElementsInMemory="10000"
               eternal="true"
               timeToIdleSeconds="120"
               timeToLiveSeconds="120"
               overflowToDisk="true"
               diskPersistent="true"
               diskExpiryThreadIntervalSeconds="600"/>
    ehcache>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这是一个常规的 Ehcache 配置文件,提供了两个缓存策略,一个是默认的,另一个名为 book_cache 。还有更为详细的 Ehcache 配置,此处不再一一介绍。如果开发者想自定义 Ehcache 配置文件的名称和位置,可以在 application.properties 中添加如下配置:

    spring.cache.ehcache.config=classpath:ehcache2.xml
    
    • 1

    3. 开启缓存

    在项目的入口类添加 @EnableCaching 注解开启缓存,如下

    @SpringBootApplication
    @EnableCaching
    public class CacheApplication {
        public static void main(String[] args) {
            SpringApplication.run(CacheApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. 创建 BookDao

    Book

    public class Book implements Serializable {
        private Integer id;
        private String name;
        private String author;
    
        @Override
        public String toString() {
            return "Book{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", author='" + author + '\'' +
                    '}';
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getAuthor() {
            return author;
        }
    
        public void setAuthor(String author) {
            this.author = author;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    BookDao

    @Repository
    @CacheConfig(cacheNames = "book_cache")
    public class BookDao {
        @Cacheable
        public Book getBookById(Integer id) {
            System.out.println("getBookById");
            Book book = new Book();
            book.setId(id);
            book.setName("三国演义");
            book.setAuthor("罗贯中");
            return book;
        }
        @CachePut(key = "#book.id")
        public Book updateBookById(Book book) {
            System.out.println("updateBookById");
            book.setName("三国演义2");
            return book;
        }
        @CacheEvict(key = "#id")
        public void deleteBookById(Integer id) {
            System.out.println("deleteBookById");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    代码解释:

    • 在 BookDao 上添加 @CacheConfig 注解指明使用的缓存名字,这个配置可选,若不使用 @CacheConfig ,则直接在 @Cacheable 注解中指明缓存名字

    • 在 getBookById 方法上添加 @Cacheable 注解表示对该方法进行缓存,默认情况下,缓存的key是方法的参数,缓存的 value 是方法的返回值。当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,则直接使用缓存数据,该方法不会执行,否则执行该方法,执行成功后将返回值缓存起来,但若是在当前类中调用该方法,则缓存不会生效

    • @Cacheable 注解中还有一个属性 condition 用来描述缓存的执行时机,例如 @Cacheable(“#id%2==0”) 表示 id 对 2 取模为0时才进缓存,否则不缓存

    • 如果开发者不想使用默认到的 key ,也可以像 updateBookById 和 deleteBookById 一样自定义 key,@CachePut(key = “#book.id”) 表示缓存的key 为参数book 对象中 id 的值,@CacheEvict(key = “#id”)表示缓存的key为参数id。除了这种使用参数定义 key 的方式外,Spring 还提供了一个 root 对象用来生成 key ,如图
      | 属性名称 | 属性描述 | 用法示例 |
      | — | — | — |
      | methodName | 当前方法名 | #root.methodName |
      | method | 当前方法对象 | #root.method.name |
      | caches | 当前方法使用的缓存 | #root.caches[0].name |
      | target | 当前被调用的对象 | #root.target |
      | targetClass | 当前被调用的对象的class | #root.targetClass |
      | args | 当前方法参数数组 | #root.args[0] |

    • 如果这些 key 不能满足开发需求,开发者也可以自定义缓存 key 的生成器 KeyGenerator,如下

    @Component
    public class MyKeyGenerator implements KeyGenerator {
        @Override
        public Object generate(Object target, Method method, Object... params) {
            return Arrays.toString(params);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后在 @Cacheable 注解中引用 MyKeyGenerator 实例即可

    @Service
    @CacheConfig(cacheNames = "book_cache")
    public class BookDao {
        @Autowired
        MyKeyGenerator myKeyGenerator;
        @Cacheable(keyGenerator = "myKeyGenerator")
        public Book getBookById(Integer id) {
            System.out.println("getBookById");
            Book book = new Book();
            book.setId(id);
            book.setName("三国演义");
            book.setAuthor("罗贯中");
            return book;
        }
        @CachePut(key = "#book.id")
        public Book updateBookById(Book book) {
            System.out.println("updateBookById");
            book.setName("三国演义2");
            return book;
        }
        @CacheEvict(key = "#id")
        public void deleteBookById(Integer id) {
            System.out.println("deleteBookById");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    MyKeyGenerator 中的 generate 方法的参数分别是当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息组成一个新的 key 返回,返回值就是缓存的 key。

    • @CachePut 注解一般用于数据更新方法上,与 @Cacheable 注解不同,添加了 @CachePut 注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来,如果 key 对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。同时 @CachePut 具有和 @Cacheable 类似的属性
    • @CacheEvict 注解一般用于删除方法上,表示移除一个 key 对应的缓存。@CacheEvict 注解由两个特殊属性:allEntries 和 beforeInvocation,其中 allEntries 表示是否将所有的缓存数据都移除,默认为 false,beforeInvocation 表示是否在方法执行之前移除缓存中的数据,默认为 false ,即在方法执行之后移除缓存中的数据

    5. 创建测试类

    创建测试类,对 Service 中的方法进行测试

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class CacheApplicationTests {
        @Autowired
        BookDao bookDao;
        @Test
        public void contextLoads() {
            bookDao.deleteBookById(1);
            bookDao.getBookById(1);
            bookDao.getBookById(1);
            bookDao.deleteBookById(1);
            Book b3 = bookDao.getBookById(1);
            System.out.println("b3:"+b3);
            Book b = new Book();
            b.setName("三国演义");
            b.setAuthor("罗贯中");
            b.setId(1);
            bookDao.updateBookById(b);
            Book b4 = bookDao.getBookById(1);
            System.out.println("b4:"+b4);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    执行该方法,控制台打印日志如下:

    deleteBookById
    getBookById
    deleteBookById
    getBookById
    b3:Book{id=1, name='三国演义', author='罗贯中'}
    updateBookById
    b4:Book{id=1, name='三国演义2', author='罗贯中'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    为了防止来回测试缓存的影响,这里先执行删除操作(同时也会删除缓存)。然后执行了一次查询,正常打印,接着又执行了一次查询没打印(直接读取的缓存),然后执行删除,接着再执行查询正常打印(删除操作也删除了缓存),再接着执行更新操作(同时更新了缓存),最后再次查询,打印更新后的数据。

  • 相关阅读:
    商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c
    Maven 管理多模块应用
    如何让照片动起来?几个制作方法和注意事项分享
    ETL为什么经常变成ELT甚至LET?
    Java并发编程—synchronized
    杰理之复位源【篇】
    数字藏品值得探究,依然是广阔的大海播
    数据库基础(一)
    【Vulhub靶场】】zabbix-SQL注入(CVE-2016-10134)漏洞复现
    Bigemap中如何添加21级的影像图
  • 原文地址:https://blog.csdn.net/GXL_1012/article/details/126232056