<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>1.16</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.3</version> </dependency>
spring:
cache:
type: redis
### 配置Redis
# Redis數據庫索引(默認為0)
redis:
database: 0
# Redis服務器地址
host: mmren.edu.redis.server
# Redis服務器連接端口
port: 6379
# Redis服務器連接密碼(默認為空)
password: ENC(bIx3PYzFf0CcZ+6CdAGHruIaTgY1/CyB)
# 配置連接池
jedis:
pool:
# 連接池最大連接數(使用負值表示沒有限制)
max-active: 8
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
max-wait: -1
# 連接池中的最大空閑連接
max-idle: 500
# 連接池中的最小空閑連接
min-idle: 0
# 連接超時時間(毫秒)
timeout: 2000
lettuce:
shutdown-timeout: 0
jasypt:
encryptor:
password: gerrymmrenedu # 設置根密碼
@EnableCaching @Configuration public class RedisCacheConfig extends CachingConfigurerSupport { @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { // 創建Redis序列化對象 RedisSerializer<String> redisSerializer = new StringRedisSerializer(); // 使用Jackson的序列化對象 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解決查詢緩存轉換異常的問題 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解決亂碼的問題) RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() // 7 天緩存過期 .entryTtl(Duration.ofDays(7)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
public class Book implements Serializable { private static final long serialVersionUID = 2629983876059197650L; private String id; private String name; // 書名 private Integer price; // 價格 private Date update; // public Book(String id, String name, Integer price, Date update) { super(); this.id = id; this.name = name; this.price = price; this.update = update; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Date getUpdate() { return update; } public void setUpdate(Date update) { this.update = update; } } -------------------------------- public class BookQry { private String id; private String name; // 書名 public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString(){ return JSON.toJSONString(this); } }
public abstract class AbstractService { protected static Map<String, Book> repositoryBook = new HashMap<>(); public AbstractService() { super(); } @PostConstruct public void init() { // 1 Book book1 = new Book("1001", "牧碼人", 11, new Date()); repositoryBook.put(book1.getId(), book1); // 2 Book book2 = new Book("1002", "張三", 43, new Date()); repositoryBook.put(book2.getId(), book2); // 3 Book book3 = new Book("1003", "Gerry", 5433, new Date()); repositoryBook.put(book3.getId(), book3); // 4 Book book4 = new Book("1004", "教育", 54, new Date()); repositoryBook.put(book4.getId(), book4); } }
@Component public class MyKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { System.out.println("自定義緩存,使用第一參數作為緩存key. params = " + Arrays.toString(params)); // 僅僅用於測試,實際不可能這么寫 return params[0] + "0"; } }
@Component public class BookService extends AbstractService { private static final Logger logger = LoggerFactory.getLogger(BookService.class); // ==================== @Cacheable ======================== /** * cacheNames 設置緩存的值 * key:指定緩存的key,這是指參數id值。 key可以使用spEl表達式 * @param id * @return */ @Cacheable(cacheNames="book1", key="#id") public Book queryBookCacheable(String id){ logger.info("queryBookCacheable,id={}",id); return repositoryBook.get(id); } /** * 這里使用另一個緩存存儲緩存 * * @param id * @return */ @Cacheable(cacheNames="book2", key="#id") public Book queryBookCacheable_2(String id){ logger.info("queryBookCacheable_2,id={}",id); return repositoryBook.get(id); } /** * 緩存的key也可以指定對象的成員變量 * @param qry * @return */ @Cacheable(cacheNames="book1", key="#qry.id") public Book queryBookCacheableByBookQry(BookQry qry){ logger.info("queryBookCacheableByBookQry,qry={}",qry); String id = qry.getId(); Assert.notNull(id, "id can't be null!"); String name = qry.getName(); Book book = null; if(id != null){ book = repositoryBook.get(id); if(book != null && !(name != null && book.getName().equals(name))){ book = null; } } return book; } /** * 以上我們使用默認的keyGenerator,對應spring的SimpleKeyGenerator * 如果你的使用很復雜,我們也可以自定義myKeyGenerator的生成key * * key和keyGenerator是互斥,如果同時制定會出異常 * The key and keyGenerator parameters are mutually exclusive and an operation specifying both will result in an exception. * * @param id * @return */ @Cacheable(cacheNames="book3", keyGenerator="myKeyGenerator") public Book queryBookCacheableUseMyKeyGenerator(String id){ logger.info("queryBookCacheableUseMyKeyGenerator,id={}",id); return repositoryBook.get(id); } /*** * 如果設置sync=true, * 如果緩存中沒有數據,多個線程同時訪問這個方法,則只有一個方法會執行到方法,其它方法需要等待 * 如果緩存中已經有數據,則多個線程可以同時從緩存中獲取數據 * @param id * @return */ @Cacheable(cacheNames="book3", sync=true) public Book queryBookCacheableWithSync(String id) { logger.info("begin ... queryBookCacheableByBookQry,id={}",id); try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { } logger.info("end ... queryBookCacheableByBookQry,id={}",id); return repositoryBook.get(id); } /** * 條件緩存: * 只有滿足condition的請求才可以進行緩存,如果不滿足條件,則跟方法沒有@Cacheable注解的方法一樣 * 如下面只有id < 3才進行緩存 * */ @Cacheable(cacheNames="book11", condition="T(java.lang.Integer).parseInt(#id) < 3 ") public Book queryBookCacheableWithCondition(String id) { logger.info("queryBookCacheableByBookQry,id={}",id); return repositoryBook.get(id); } /** * 條件緩存: * 對不滿足unless的記錄,才進行緩存 * "unless expressions" are evaluated after the method has been called * 如下面:只對不滿足返回 'T(java.lang.Integer).parseInt(#result.id) <3 ' 的記錄進行緩存 * @param id * @return */ @Cacheable(cacheNames="book22", unless = "T(java.lang.Integer).parseInt(#result.id) <3 ") public Book queryBookCacheableWithUnless(String id) { logger.info("queryBookCacheableByBookQry,id={}",id); return repositoryBook.get(id); } // ==================== @CacheEvict ======================== /** * allEntries = true: 清空book1里的所有緩存 */ @CacheEvict(cacheNames="book1", allEntries=true) public void clearBook1All(){ logger.info("clearAll"); } /** * 對符合key條件的記錄從緩存中book1移除 */ @CacheEvict(cacheNames="book1", key="#id") public void updateBook(String id, String name){ logger.info("updateBook"); Book book = repositoryBook.get(id); if(book != null){ book.setName(name); book.setUpdate(new Date()); } } // ==================== @CachePut ======================== /** * 每次執行都會執行方法,無論緩存里是否有值,同時使用新的返回值的替換緩存中的值 * 這里不同於@Cacheable:@Cacheable如果緩存沒有值,從則執行方法並緩存數據,如果緩存有值,則從緩存中獲取值 * @param id * @return */ @CachePut(cacheNames="book1", key="#id") public Book queryBookCachePut(String id){ logger.info("queryBookCachePut,id={}",id); return repositoryBook.get(id); } }
/** * @CacheConfig: 類級別的注解 * 如果我們在此注解中定義cacheNames,則此類中的所有方法上@Cacheable的cacheNames默認都是此值。當然 * @Cacheable也可以重定義cacheNames的值 * */ @Component @CacheConfig(cacheNames="booksAll") public class BookService2 extends AbstractService { private static final Logger logger = LoggerFactory.getLogger(BookService2.class); /** * 此方法的@Cacheable沒有定義cacheNames,則使用類上的注解@CacheConfig里的值 cacheNames * @param id * @return */ @Cacheable(key="#id") public Book queryBookCacheable(String id){ logger.info("queryBookCacheable,id={}",id); return repositoryBook.get(id); } /** * 此方法的@Cacheable有定義cacheNames,則使用此值覆蓋類注解@CacheConfig里的值cacheNames * * @param id * @return */ @Cacheable(cacheNames="books_custom", key="#id") public Book queryBookCacheable2(String id){ logger.info("queryBookCacheable2,id={}",id); return repositoryBook.get(id); } }
public class RedisCacheTests extends ApplicationTests{ @Autowired private BookService bookService; @Test public void testCache1() throws Exception { Book book = bookService.queryBookCacheable("1001"); convertToString(book); } @Test public void testCache2() throws Exception { Book book = bookService.queryBookCacheable_2("1001"); convertToString(book); } @Test public void testCache3() throws Exception { BookQry qry = new BookQry(); qry.setId("1004"); qry.setName("教育"); Book book = bookService.queryBookCacheableByBookQry(qry); convertToString(book); } @Test public void testCache4() throws Exception { Book book = bookService.queryBookCacheableUseMyKeyGenerator("1003"); convertToString(book); } @Test public void testCache5() throws Exception { Book book = bookService.queryBookCachePut("1003"); convertToString(book); } public void convertToString(Object object) { String jsonString = JSON.toJSONString(object, true); System.out.println(jsonString); } }