配置Spring Cache
<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);
}
}
自定義緩存key生成類
@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";
}
}
Spring緩存的基本用法
@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配置定義
/**
* @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);
}
}