• spring boot项目13:缓存-基础使用


    JAVA 8

    Spring Boot 2.5.3

    MySQL 5.7.21(单机)

    Redis 4.0.9(单机)

    ---

    授人以渔:

    1、Spring Boot Reference Documentation

    This document is also available as Multi-page HTML, Single page HTML and PDF.

    有PDF版本哦,下载下来!

    Caching 一章

    2、Spring Data Redis

    无PDF,网页上可以搜索。

    3、Spring Framework Core

    Chapter 4. Spring Expression Language (SpEL)

    目录

    前言

    启用缓存-@EnableCaching

    使用默认缓存Simple

    使用Redis实现缓存

    参考文档

    前言

    本文介绍在S.B.项目中使用缓存(Caching),首先使用默认的1)基于内存的simple版缓存,然后,升级为使用2)Redis版的缓存。

    本文使用的项目:来自博客园

    mysql-hello

    项目相关配置——后面会用到:

    MySQL配置
    #
    # MySQL on Ubuntu
    spring.datasource.url=jdbc:mysql://mylinux:3306/db_example?serverTimezone=Asia/Shanghai
    spring.datasource.username=springuser
    spring.datasource.password=ThePassword
    #spring.datasource.driver-class-name =com.mysql.jdbc.Driver # This is deprecated
    spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
    # 是否输出sql语句,调试用,,缓存有效时,可以看到不执行SQL语句
    #spring.jpa.show-sql: true
    Redis及缓存配置
    # 使用Redis做缓存
    #
    # Redis
    # mylinux 是虚拟机的本地域名,配置到 hosts文件中
    #spring.redis.host=mylinux
    #spring.redis.port=6379
    
    # 缓存类型(非必须,Spring Boot会按照顺序检测,当然,也可以指定)
    #spring.cache.type=REDIS

    启用缓存-@EnableCaching

    应用入口类添加 @EnableCaching

    @SpringBootApplication
    // 开启缓存
    @EnableCaching
    @Slf4j
    public class MysqlHelloApplication {

    启动后,Spring容器中存在以下名称包含 cache的Bean:来自博客园

    name=org.springframework.cache.annotation.ProxyCachingConfiguration
    name=org.springframework.cache.config.internalCacheAdvisor
    name=cacheOperationSource
    name=cacheInterceptor
    
    name=org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
    # cache管理器
    name=cacheManager
    name=org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration$CacheManagerEntityManagerFactoryDependsOnPostProcessor
    name=org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
    # cache管理器定制器
    name=cacheManagerCustomizers
    name=cacheAutoConfigurationValidator
    name=spring.cache-org.springframework.boot.autoconfigure.cache.CacheProperties
    name=org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration
    name=cachesEndpoint
    name=cachesEndpointWebExtension

    其中,名为 cacheManager 的Bean是重点,编写程序检查其信息:来自博客园

    @RestController
    @RequestMapping(value="/cacheManager")
    @Slf4j
    public class CacheManagerController {
    
    	@Autowired
    	private CacheManager cacheManager;
    	
    	@GetMapping(value="/test")
    	public Boolean test() {
    		log.info("cacheManager检查:cacheManager={}, cacheManager", 
    				cacheManager.getClass(), cacheManager);
    		Collection<String> names = cacheManager.getCacheNames();
    		log.info("cacheNames={}", names);
    		names.forEach(name->{
    			log.info("name={}, value={}", name, cacheManager.getCache(name));
    			
    			Cache cache = cacheManager.getCache(name);
    			log.info("cache.name={}", cache.getName());
    			log.info("cache.get={}", cache.get(name));
    			log.info("cache.getNativeCache={}", cache.getNativeCache());
    		});
            
            return true;
        }
    }

    调用接口 /cacheManager/test,日志输出(部分):

    o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=
    class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    o.l.m.tcache.CacheManagerController      : cacheNames=[]

    从上面的日志可以看出,默认的 cacheManager 为 ConcurrentMapCacheManager 类型。

    注意,使用默认的 cacheManager ,甚至不要引入 spring-boot-starter-data-redis 包,否则,其类型会变为 RedisCacheManager。

    注意,上面的接口 可以用来检查cacheManager的情况来自博客园

    测试实体类:

    BOOK.java
    package org.lib.mysqlhello.tcache.try1;
    
    import java.io.Serializable;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * 书
     * 使用Redis缓存时,Book类必须implements Serializable!!
     * @author ben
     * @date 2021-08-26 10:34:44 CST
     */
    @Entity
    @NoArgsConstructor
    @Data
    //public class Book implements Serializable {
    public class Book {
    
    	/**
    	 * serialVersionUID
    	 */
    //	private static final long serialVersionUID = 210826L;
    
    	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private Long id;
    	
    	/**
    	 * 书名
    	 */
    	@Column(columnDefinition = "VARCHAR(500) NOT NULL")
    	private String name;
    	/**
    	 * 作者
    	 */
    	@Column(columnDefinition = "VARCHAR(100) NOT NULL")
    	private String author;
    	/**
    	 * 销量
    	 */
    	@Column(columnDefinition = "INT DEFAULT 0")
    	private Integer sales;
    	
    	/**
    	 * 构造函数
    	 * @param name
    	 * @param author
    	 */
    	public Book(String name, String author) {
    		this.name = name;
    		this.author = author;
    		this.sales = 0;
    	}
    	
    }
    

    使用默认缓存Simple

    实体类 Book,Long id为主键,存于MySQL。

    编写Controller、Service、Dao等,其中,在 执行 CRUD 的 BookServiceImpl 上使用缓存机制:来自博客园

    增加、删除时,移除缓存;

    获取时,从缓存中拿,没有去数据库拿;

    更新时,更新后,存入缓存

    @Service
    @Slf4j
    public class BookServiceImpl implements BookService {
    	
    	@Autowired
    	private BookDao bookDao;
    	
    	private final static String BOOK_CACHE_VALUE = "mysql-hello";
    	// key 的 单引号 必须有!
    	private final static String BOOK_CACHE_KEY = "'book'";
    	
    	@Override
    	@CacheEvict(value=BOOK_CACHE_VALUE, key=BOOK_CACHE_KEY)
    	public Book add(AddDTO dto) {
    		Book newbook = new Book(dto.getName(), dto.getAuthor());
    		Book savedBook = bookDao.save(newbook);
    		log.info("新增书:id={}", savedBook.getId());
    		return savedBook;
    	}
    
    	@Override
    	@CacheEvict(value = BOOK_CACHE_VALUE, key="'book_' + #id")
    	public Boolean del(Long id) {
    		bookDao.deleteById(id);
    		return bookDao.existsById(id);
    	}
    
    	@Override
    	@CachePut(value=BOOK_CACHE_VALUE, key="'book_' + #dto.getId()")
    	public Book update(UpdateDTO dto) {
    		Long id = dto.getId();
    		if (Objects.isNull(id)) {
    			return null;
    		}
    		
    		Book oldBook = bookDao.findById(id).orElse(null);
    		if (Objects.isNull(oldBook)) {
    			return null;
    		}
    		
    		oldBook.setSales(oldBook.getSales() + dto.getSalesIncr());
    		Book savedBook = bookDao.save(oldBook);
    		
    		return savedBook;
    	}
    
    	@Override
        // 必须使用unless
    	@Cacheable(value=BOOK_CACHE_VALUE, key="'book_' + #id", unless="#result == null")
    	public Book findById(Long id) {
    		if (Objects.isNull(id)) {
    			return null;
    		}
    		return bookDao.findById(id).orElse(null);
    	}
    
    }

    执行 /try1/book/add,添加成功;

    执行 /cacheManager/test,检查日志:cacheManager.getCacheNames() 返回了一个 [mysql-hello]—— /try1/book/add 接口的 @CacheEvict注解 的 value值。来自博客园

    try1.BookController         : add Book
    try1.BookServiceImpl        : 新增书:id=20
    CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    CacheManagerController      : cacheNames=[mysql-hello]
    CacheManagerController      : name=mysql-hello, value=org.springframework.cache.concurrent.ConcurrentMapCache@29b34e8a
    CacheManagerController      : cache.name=mysql-hello
    CacheManagerController      : cache.get=null
    CacheManagerController      : cache.getNativeCache={}

    添加后,此时缓存中 只有一个 name,其下还没有数据。

    执行 /try1/book/findById?id=id,id为一个存在的记录;

    注,使用缓存时,调用查询接口前不需要调用 添加接口。

    注,findById函数的 @Cacheable不能少,否则,查询结果为null时也会被添加到缓存,,错误情景:查询不存的id=22,返回null,添加新记录id=22,此时调用查询接口是无效的,一直返回null。来自博客园

    执行 /cacheManager/test,检查日志:

    try1.BookController         : findById Book
    CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    CacheManagerController      : cacheNames=[mysql-hello]
    CacheManagerController      : name=mysql-hello, value=org.springframework.cache.concurrent.ConcurrentMapCache@533dba02
    CacheManagerController      : cache.name=mysql-hello
    CacheManagerController      : cache.get=null
    CacheManagerController      : cache.getNativeCache={book_21=Book(id=21, name=Spring Cloud, author=Pivot, sales=0)}

    可以看到,cache.getNativeCache 有值了,以 book_21 开头——21为测试的id。

    再查询 id=20 的记录,此时,cacheManager变化如下:多了 book_20,共2条记录了

    cache.getNativeCache={book_20=Book(id=20, name=Spring Cloud, author=Pivot, sales=0), 
    book_21=Book(id=21, name=Spring Cloud, author=Pivot, sales=0)}

    疑问:最多可以存多少呢?和JVM内存大小有关系吧?来自博客园

    缓存的目的是加快查询速度,使用缓存,不使用缓存的查询速度怎么测试呢?Jemeter。TODO

    打开文首 MySQL配置中的配置:

    spring.jpa.show-sql: true

    可以看到,使用缓存后,只有首次查询时会使用SQL查询数据库,其后——有效期内(怎么配置?),都是直接从缓存中获取。见下面两图:

    注释掉 findById 函数的 @Cacheable 注解后测试: 每次都执行SQL查询数据库,浪费资源啊!来自博客园

    更新测试

    执行 /try1/book/findById 查询,检查cacheManager;

    然后,执行 /try1/book/update 更新,检查cacheManager,此时,缓存中上一部查询的数据已经发生变化;

    再次执行 /try1/book/findById 查询,现实从缓存中获取了最新的数据——没有执行SQL语句。来自博客园

    更新测试日志
    # 1、cacheManager检查
    2021-09-04 12:37:31.706  INFO 2828 --- [io-30000-exec-2] o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    2021-09-04 12:37:31.706  INFO 2828 --- [io-30000-exec-2] o.l.m.tcache.CacheManagerController      : cacheNames=[]
    
    # 2、查询 从数据库查询
    2021-09-04 12:37:34.961  INFO 2828 --- [io-30000-exec-3] o.l.m.tcache.try1.BookController         : findById Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    
    # 3、cacheManager检查 有数据了
    2021-09-04 12:37:36.583  INFO 2828 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    2021-09-04 12:37:36.583  INFO 2828 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cacheNames=[mysql-hello]
    2021-09-04 12:37:36.583  INFO 2828 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : name=mysql-hello, value=org.springframework.cache.concurrent.ConcurrentMapCache@7e6e18fa
    2021-09-04 12:37:36.583  INFO 2828 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cache.name=mysql-hello
    2021-09-04 12:37:36.583  INFO 2828 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cache.get=null
    2021-09-04 12:37:36.583  INFO 2828 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cache.getNativeCache={book_21=Book(id=21, name=Spring Cloud, author=Pivot, sales=101)}
    
    # 4、更新
    2021-09-04 12:37:47.622  INFO 2828 --- [io-30000-exec-5] o.l.m.tcache.try1.BookController         : update Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    Hibernate: update book set author=?, name=?, sales=? where id=?
    
    # 5、查询 没有从数据库查询
    2021-09-04 12:37:56.675  INFO 2828 --- [io-30000-exec-6] o.l.m.tcache.try1.BookController         : findById Book
    
    # 6、cacheManager检查 这一步可以放到 4、5之间
    2021-09-04 12:38:04.280  INFO 2828 --- [io-30000-exec-7] o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    2021-09-04 12:38:04.280  INFO 2828 --- [io-30000-exec-7] o.l.m.tcache.CacheManagerController      : cacheNames=[mysql-hello]
    2021-09-04 12:38:04.281  INFO 2828 --- [io-30000-exec-7] o.l.m.tcache.CacheManagerController      : name=mysql-hello, value=org.springframework.cache.concurrent.ConcurrentMapCache@7e6e18fa
    2021-09-04 12:38:04.281  INFO 2828 --- [io-30000-exec-7] o.l.m.tcache.CacheManagerController      : cache.name=mysql-hello
    2021-09-04 12:38:04.281  INFO 2828 --- [io-30000-exec-7] o.l.m.tcache.CacheManagerController      : cache.get=null
    2021-09-04 12:38:04.281  INFO 2828 --- [io-30000-exec-7] o.l.m.tcache.CacheManagerController      : cache.getNativeCache={book_21=Book(id=21, name=Spring Cloud, author=Pivot, sales=202)}

    删除测试

    执行 /try1/book/findById 接口,cacheManager检查;

    执行 /try1/book/del 接口,cacheManager检查;

    执行 /try1/book/findById 接口——此时返回null,cacheManager检查;来自博客园

    删除测试日志
    # 1、查询2次
    2021-09-04 12:43:57.784  INFO 17032 --- [io-30000-exec-2] o.l.m.tcache.try1.BookController         : findById Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    2021-09-04 12:44:06.036  INFO 17032 --- [io-30000-exec-3] o.l.m.tcache.try1.BookController         : findById Book
    
    # 2、cacheManager检查 有数据
    2021-09-04 12:44:10.118  INFO 17032 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    2021-09-04 12:44:10.118  INFO 17032 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cacheNames=[mysql-hello]
    2021-09-04 12:44:10.119  INFO 17032 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : name=mysql-hello, value=org.springframework.cache.concurrent.ConcurrentMapCache@5a161c5a
    2021-09-04 12:44:10.119  INFO 17032 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cache.name=mysql-hello
    2021-09-04 12:44:10.119  INFO 17032 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cache.get=null
    2021-09-04 12:44:10.119  INFO 17032 --- [io-30000-exec-4] o.l.m.tcache.CacheManagerController      : cache.getNativeCache={book_20=Book(id=20, name=Spring Cloud, author=Pivot, sales=0)}
    
    # 3、删除
    2021-09-04 12:44:20.261  INFO 17032 --- [io-30000-exec-5] o.l.m.tcache.try1.BookController         : del Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    Hibernate: delete from book where id=?
    Hibernate: select count(*) as col_0_0_ from book book0_ where book0_.id=?
    
    # 4、cacheManager检查 没有数据了
    2021-09-04 12:44:32.404  INFO 17032 --- [io-30000-exec-6] o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.cache.concurrent.ConcurrentMapCacheManager, cacheManager
    2021-09-04 12:44:32.404  INFO 17032 --- [io-30000-exec-6] o.l.m.tcache.CacheManagerController      : cacheNames=[mysql-hello]
    2021-09-04 12:44:32.404  INFO 17032 --- [io-30000-exec-6] o.l.m.tcache.CacheManagerController      : name=mysql-hello, value=org.springframework.cache.concurrent.ConcurrentMapCache@5a161c5a
    2021-09-04 12:44:32.404  INFO 17032 --- [io-30000-exec-6] o.l.m.tcache.CacheManagerController      : cache.name=mysql-hello
    2021-09-04 12:44:32.404  INFO 17032 --- [io-30000-exec-6] o.l.m.tcache.CacheManagerController      : cache.get=null
    2021-09-04 12:44:32.404  INFO 17032 --- [io-30000-exec-6] o.l.m.tcache.CacheManagerController      : cache.getNativeCache={}
    
    # 5、查询2次 都执行了SQL
    2021-09-04 12:44:36.937  INFO 17032 --- [io-30000-exec-7] o.l.m.tcache.try1.BookController         : findById Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    
    2021-09-04 12:44:41.631  INFO 17032 --- [io-30000-exec-8] o.l.m.tcache.try1.BookController         : findById Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    

    疑问:删除数据后,查询时会执行SQL,要是一直查询 不存在的数据,数据库服务器的压力岂不是很大?怎么解决?设置缓存时间?TODO

    小结,

    默认的缓存使用的是JVM里面的ConcurrentMap,单应用可用在,不能在应用间共享缓存数据;

    应用重启后,缓存数据丢失;

    在分布式系统时,不能使用:A应用缓存数据到自己的JVM,而B应用更新了数据,此时,从A获取的数据是脏数据——有效期内,,A、B应用是同一个应用的不同实例;

    怎么解决?使用Redis作为缓存系统。来自博客园

    补充:

    在S.B.手册中,提到了其缓存功能有下面的实现(优先级从高到低(in the indicated order)):

    1. Generic
    2. JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
    3. EhCache 2.x
    4. Hazelcast
    5. Infinispan
    6. Couchbase
    7. Redis
    8. Caffeine
    9. Simple

     上面介绍的默认的实现,是其中的Simple——优先级最低。这也是前面提到的,使用默认 缓存实现时,不要引入 spring-boot-starter-data-redis 的原因。

    使用Redis实现缓存

    添加依赖包:

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    Redis配置——前文已展示。

    修改 /cacheManager/test 接口:cacheManager 为 RedisCacheManager类型时的信息输出

    修改cacheManager检查
    	@GetMapping(value="/test")
    	public Boolean test() {
    		log.info("cacheManager检查:cacheManager={}, cacheManager", 
    				cacheManager.getClass(), cacheManager);
    		Collection<String> names = cacheManager.getCacheNames();
    		log.info("cacheNames={}", names);
    		names.forEach(name->{
    			log.info("name={}, value={}", name, cacheManager.getCache(name));
    			
    			Cache cache = cacheManager.getCache(name);
    			log.info("cache.name={}", cache.getName());
    			log.info("cache.get={}", cache.get(name));
    			log.info("cache.getNativeCache={}", cache.getNativeCache());
    		});
    		
    		// 输出 RedisCacheManager 的配置
    		if (RedisCacheManager.class.equals(cacheManager.getClass())) {
    			log.info("RedisCacheManager探究:");
    			RedisCacheManager rcm = (RedisCacheManager) cacheManager;
    			Map<String, RedisCacheConfiguration> cconfig = rcm.getCacheConfigurations();
    			cconfig.forEach((key, val)->{
    				log.info("key={}, value={}", key, val);
    				log.info("val-5: {}, {}, {}, {}, {}", val.getAllowCacheNullValues(), 
    						val.getKeyPrefix().orElse(""),
    						val.getTtl(), 
    						val.getKeySerializationPair(),
    						val.getValueSerializationPair());
    			});
    		}
    		
    		return true;
    	}

    添加Book,成功;

    执行 cacheManager检查:

    试验1日志
    2021-09-04 14:27:26.240  INFO 14868 --- [io-30000-exec-1] o.l.m.tcache.try1.BookController         : add Book
    Hibernate: insert into book (author, name, sales) values (?, ?, ?)
    2021-09-04 14:27:26.369  INFO 14868 --- [io-30000-exec-1] o.l.m.tcache.try1.BookServiceImpl        : 新增书:id=24
    
    
    2021-09-04 14:27:34.588  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : cacheManager检查:cacheManager=class org.springframework.data.redis.cache.RedisCacheManager, cacheManager
    2021-09-04 14:27:34.588  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : cacheNames=[mysql-hello]
    2021-09-04 14:27:34.589  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : name=mysql-hello, value=org.springframework.data.redis.cache.RedisCache@6a7c7921
    2021-09-04 14:27:34.589  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : cache.name=mysql-hello
    2021-09-04 14:27:34.595  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : cache.get=null
    2021-09-04 14:27:34.595  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : cache.getNativeCache=org.springframework.data.redis.cache.DefaultRedisCacheWriter@3f0c00da
    2021-09-04 14:27:34.595  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : RedisCacheManager探究:
    2021-09-04 14:27:34.596  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : key=mysql-hello, value=org.springframework.data.redis.cache.RedisCacheConfiguration@11308dc7
    2021-09-04 14:27:34.596  INFO 14868 --- [io-30000-exec-3] o.l.m.tcache.CacheManagerController      : val-5: true, ::, PT0S, org.springframework.data.redis.serializer.RedisSerializerToSerializationPairAdapter@4e6545eb, org.springframework.data.redis.serializer.RedisSerializerToSerializationPairAdapter@1aaf6f81
    

    日志分析:

    cache.getNativeCache 的值看不懂,倒数 2行 的日志 看不懂

    cache.getNativeCache 的 DefaultRedisCacheWriter 类型及公共函数:来自博客园

    查询上面添加成功的id=24的记录——发生异常

    Failed to serialize object using DefaultSerializer...Failed to deserialize payload

    2021-09-04 14:31:56.435  INFO 14868 --- [io-30000-exec-6] o.l.m.tcache.try1.BookController         : findById Book
    Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.name as name3_0_0_, book0_.sales as sales4_0_0_ from book book0_ where book0_.id=?
    2021-09-04 14:31:56.493 ERROR 14868 --- [io-30000-exec-6] o.l.m.common.AppExceptionHandler         : 发生异常: e=class 
    org.springframework.data.redis.serializer.SerializationException, e.message=Cannot serialize; nested exception is 
    org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using 
    DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a 
    Serializable payload but received an object of type [org.lib.mysqlhello.tcache.try1.Book]

    反序列化异常

    修改cacheManager等Bean?可以。其实,这里的问题是 payload无法被序列化,Book对象无法被序列化——Book没有实现Serializable接口

    改造:Book实现Serializable

    @Entity
    @NoArgsConstructor
    @Data
    public class Book implements Serializable {
    
    	/**
    	 * serialVersionUID
    	 */
    	private static final long serialVersionUID = 210826L;
    // 省略

    再次执行 查询记录(/try1/book/findById):执行成功。但是,首次查询耗时1秒多

    cacheManager检查 结果没变化,但没有看到缓存的记录数据。

    直接检查Redis服务器:存在一个记录对应的 key = mysql-hello::book_24,但是,有效期却是 永不失效

    127.0.0.1:6379> keys *
    1) "xacxedx00x05tx00x05test3"
    2) "xacxedx00x05tx00x04set1"
    3) "mysql-hello::book_24"
    4) "xacxedx00x05tx00x05test1"
    127.0.0.1:6379>
    127.0.0.1:6379>
    127.0.0.1:6379> ttl mysql-hello::book_24
    (integer) -1
    

    上面的解决方式需要:

    1)实体类必须是 可序列化的

    2)Redis中的缓存值是永不过期的

    怎么解决上面的限制条件呢?定制 RedisCacheConfiguration

    取消上面 Book类的序列化,添加下面的配置——缓存有效期300秒(5分钟)。

    AppCacheCofig.java
    @Configuration
    public class AppCacheCofig {
    
    	/**
    	 * RedisCacheConfiguration定制
    	 * 修改后,实体类不需要 implements Serializable
    	 * @author ben
    	 * @date 2021-08-26 19:50:38 CST
    	 * @return
    	 */
    	@Bean
    	public RedisCacheConfiguration redisCacheConfiguration() {
    		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    		ObjectMapper om = new ObjectMapper();
    		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    		
    		// 标记为过时
    //		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    		// 替代上面的过时方法
    		om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, 
    				JsonTypeInfo.As.WRAPPER_ARRAY);
    		
    		jackson2JsonRedisSerializer.setObjectMapper(om);
    		
    		RedisSerializationContext.SerializationPair<Object> pair = 
    				RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer);
    		
    		RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
    				.entryTtl(Duration.ofSeconds(300))
    				.serializeValuesWith(pair);
    		
    		return redisCacheConfiguration;
    	}
        
    }

    执行 /book/findById——查询存在的记录:执行失败,发生异常

    查询时,之前缓存中的数据还存在——和目前的序列化方式不同,因此,发生了错误。

    解决方案:删除Redis中旧的值即可。来自博客园

    删除后,再查询:成功。

    缓存中的键值的有效期也变化了:不再是 -1。

    成功。

    补充:AppCacheCofig 中还可以配置 RedisCacheManager

    但是,不使用下面的代码,也可以完成本文的试验。

    	@Bean
    	public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    		// 初始化一个RedisCacheWriter
    		RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
    		RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, this.redisCacheConfiguration());
    		return cacheManager;
    	}

    缓存有效期到了,缓存中的键值就消失了:

    测试更新、测试删除:成功。

    注意,更新后,key的有效期被设置为 300秒 了

    补充:Redis中怎么存缓存数据的呢

    1)默认+序列化实体类

    从Redis中看不懂

    127.0.0.1:6379> get mysql-hello::book_23
    "xacxedx00x05srx00#org.lib.mysqlhello.tcache.try1.Bookx00x00x00x00x00x037x8ax02x00x04Lx00x06
    authortx00x12Ljava/lang/String;Lx00x02idtx00x10Ljava/lang/Long;Lx00x04nameqx00~x00x01Lx00x05salest
    x00x13Ljava/lang/Integer;xptx00x05Pivotsrx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00
    x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x00x00x00x00x00
    x17tx00x0cSpring Cloudsrx00x11java.lang.Integerx12xe2xa0xa4xf7x81x878x02x00x01Ix00x05valuexq
    x00~x00ax00x00x00x00"
    

    2)使用 Jackson2JsonRedisSerializer

    127.0.0.1:6379> get mysql-hello::book_25
    "["org.lib.mysqlhello.tcache.try1.Book",{"id":25,"name":"Spring Cloud","author":"Pivot","sales":0}]"

    试验期间发现,使用Redis做缓存时,首次访问数据的速度非常慢——秒级

    怎么优化呢?和惰性加载有关系吗?

    我的解决方案:项目启动时,执行一次 redis操作,以便和Redis服务器建立好连接。

    	@Resource
    	RedisTemplate<String, Object> redisTemplate;
    	
    	@Bean
    	public CommandLineRunner redisInit() {
    		return (args) -> {
    			cs.accept("redisInit...");
    			redisTemplate.opsForValue().set("redisinit", 1, Duration.ofSeconds(1));
    		};
    	}

    测试情况:

    首次查询速度大大加快,有1秒多,变成了300多毫秒。

    还有更好的解决方法吗?300毫秒,是我的服务器太差了?毕竟是本机的虚拟机。TODO

    试验:启动两个实例测试缓存使用

    测试通过。

    实例A查询,使用了SQL语句;

    实例B查询时,直接从缓存拿数据。

    》》》全文完《《《

    后记:

    博文写完了,其实,内心是忐忑的,还有好多东西没掌握的,能用,用起来了。

    上面的缓存使用的是 实体类 的主键-ID字段,是否可以使用其它的 唯一索引键(UNIQUE)呢?

    spring.cache.*、spring.cache.redis.* 的相关配置,这些配置和代码中的定制化配置的优先级如何?

    RedisCacheConfiguration 还有哪些配置?里面的超时时间 限定了300秒,多个实体类需要不同的超时时间呢?怎么弄?

    参考了不少博文才解决了问题,有优质的博文,会补充到参考文档里面。

    参考文档

    1、Spring Cacheable注解不缓存null值

    2、spring-boot-2 redis-cache序列化配置注意点

    3、Spring Boot 自带缓存及结合 Redis 使用

    4、

  • 相关阅读:
    多帐套,多组织 登录系统设计
    Git常用命令速查05
    一步步搭建java信息管理系统00
    Git常用命令速查04
    Git常用命令速查03
    Git常用命令速查02
    Git常用命令速查01
    无法创建k/3中间层组件或组件正在调用中间层问题解决
    jQuery.i18n.properties实现前端国际化
    ORACLE telnet 1521 不通及ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务的解决
  • 原文地址:https://www.cnblogs.com/luo630/p/15192342.html
Copyright © 2020-2023  润新知