I Phương án bộ nhớ đệm dựa trên SpringCache
1.1 Giới thiệu SpringCache
springcache được phát hành trong phiên bản spring3.1, cung cấp lớp đóng gói và trừu tượng hóa cho việc sử dụng bộ nhớ đệm. Chỉ cần dùng annotation trên phương thức là có thể lấy kết quả từ bộ nhớ đệm. Chính nhờ sử dụng annotation, nó giải quyết vấn đề phụ thuộc chặt giữa mã nghiệp vụ và mã bộ nhớ đệm, tức là giúp mã hiện có lập tức hỗ trợ bộ nhớ đệm mà không xâm lấn mã nghiệp vụ, cho phép lập trình viên sử dụng bộ nhớ đệm một cách trong suốt.
1.2 Tổng quan về SpringCache
Có rất nhiều framework bộ nhớ đệm, mỗi loại có ưu điểm riêng, chẳng hạn Redis, Memcached, Guava, Caffeine, v.v.
Nếu chương trình của chúng ta muốn sử dụng bộ nhớ đệm thì phải gắn kết với những framework này. Các kiến trúc sư khéo léo đã sử dụng giao diện để giảm mức độ gắn kết, vận dụng tính trừu tượng và đa hình của lập trình hướng đối tượng để tách mã nghiệp vụ khỏi framework cụ thể.
Nhưng chúng ta vẫn cần gọi tường minh các giao diện và phương thức liên quan đến bộ nhớ đệm trong mã, chèn dữ liệu vào bộ nhớ đệm lúc thích hợp và đọc dữ liệu từ bộ nhớ đệm lúc thích hợp. Hãy thử nghĩ xem「AOP」trong những trường hợp áp dụng ấy, chẳng phải ngay từ đầu đã nên để AOP xử lý sao?
Từ Spring
3.1, đã có hỗ trợ Cache bằng chú thích tương tự chú thích giao dịch [@Transactional](/Transactional ), đồng thời cung cấp lớp trừu tượng Cache;trước đó thường triển khai qua AOP.
Lợi ích của việc sử dụng Spring Cache:
-
Cung cấp lớp trừu tượng Cache cơ bản, giúp dễ dàng chuyển đổi giữa các Cache bên dưới;
-
Thông qua chú thích Cache, có thể áp dụng logic bộ nhớ đệm một cách trong suốt vào mã nghiệp vụ, tương tự như giao dịch, và chỉ cần ít mã hơn là có thể hoàn thành;
-
Cung cấp khả năng tự động hoàn tác bộ nhớ đệm khi giao dịch được hoàn tác;
-
Hỗ trợ logic bộ nhớ đệm tương đối phức tạp;

1.3 Thiết kế cốt lõi của Spring Cache
Từ Spring 3.1, Spring Cache, tức lớp trừu tượng hóa bộ nhớ đệm của Spring, đã được đưa vào. Bằng cách định nghĩa org.springframework.cache.Cache và org.springframework.cache.CacheManager giao diện để thống nhất các công nghệ bộ nhớ đệm khác nhau, đồng thời hỗ trợ dùng annotation JCache để đơn giản hóa quá trình phát triển.
Giao diện Cache định nghĩa quy chuẩn cho thành phần bộ nhớ đệm, bao gồm tập hợp các thao tác với bộ nhớ đệm. Trong Spring, giao diện Cache được cung cấp nhiều xxxCache triển khai của: RedisCache , EhCacheCache , ConcurrentMapCache v.v.
Tìm hiểu giao diện Cache và qua một phần mã nguồn CacheManager giao diện.
Giao diện Cache
public interface Cache { //Cache名称 String getName(); //Cache负责缓存的对象 Object getNativeCache(); /** * 获取key对应的ValueWrapper * 没有对应的key,则返回null * key对应的value是null,则返回null对应的ValueWrapper */ @Nullable Cache.ValueWrapper get(Object key); //返回key对应type类型的value @Nullable <T> T get(Object key, @Nullable Class<T> type); //返回key对应的value,没有则缓存Callable::call,并返回 @Nullable <T> T get(Object key, Callable<T> valueLoader); //缓存目标key-value(替换旧值),不保证实时性 void put(Object key, @Nullable Object value); //插入缓存,并返回该key对应的value;先调用get,不存在则用put实现 @Nullable default Cache.ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Cache.ValueWrapper existingValue = this.get(key); if (existingValue == null) { this.put(key, value); } return existingValue; } //删除缓存,不保证实时性 void evict(Object key); //立即删除缓存:返回false表示剔除前不存在制定key活不确定是否存在;返回true,表示该key之前存在 default boolean evictIfPresent(Object key) { this.evict(key); return false; } //清除所有缓存,不保证实时性 void clear(); //立即清楚所有缓存,返回false表示清除前没有缓存或不能确定是否有;返回true表示清除前有缓存 default boolean invalidate() { this.clear(); return false; } public static class ValueRetrievalException extends RuntimeException { @Nullable private final Object key; public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) { super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex); this.key = key; } @Nullable public Object getKey() { return this.key; } } //缓存值的一个包装器接口,实现类为SimpleValueWrapper @FunctionalInterface public interface ValueWrapper { @Nullable Object get(); } }
Có thể thấy, giao diện Cache trừu tượng hóa các thao tác liên quan đến bộ nhớ đệm như get put evict.
Lớp trừu tượng AbstractValueAdaptingCache
public abstract class AbstractValueAdaptingCache implements Cache { //是否允许Null值 private final boolean allowNullValues; protected AbstractValueAdaptingCache(boolean allowNullValues) { this.allowNullValues = allowNullValues; } public final boolean isAllowNullValues() { return this.allowNullValues; } @Nullable public ValueWrapper get(Object key) { return this.toValueWrapper(this.lookup(key)); } @Nullable public <T> T get(Object key, @Nullable Class<T> type) { //查询到的缓存值做fromStoreValue转换 Object value = this.fromStoreValue(this.lookup(key)); //转换后非null值且无法转换为type类型则抛出异常 if (value != null && type != null && !type.isInstance(value)) { throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value); } else { return value; } } //从缓存中获取key对应的value,子类实现 @Nullable protected abstract Object lookup(Object key); //对于从缓存中获取的值,允许为空且值为NullValue时,处理为null @Nullable protected Object fromStoreValue(@Nullable Object storeValue) { return this.allowNullValues && storeValue == NullValue.INSTANCE ? null : storeValue; } //对于要插入缓存的null值,在允许null值的情况下处理为NullValue;否则抛出异常IllegalArgumentException protected Object toStoreValue(@Nullable Object userValue) { if (userValue == null) { if (this.allowNullValues) { return NullValue.INSTANCE; } else { throw new IllegalArgumentException("Cache '" + this.getName() + "' is configured to not allow null values but null was provided"); } } else { return userValue; } } //get操作依据,查询到缓存值非null,则fromStoreValue转换后包装成SimpleValueWrapper返回 @Nullable protected ValueWrapper toValueWrapper(@Nullable Object storeValue) { return storeValue != null ? new SimpleValueWrapper(this.fromStoreValue(storeValue)) : null; } }
Lớp trừu tượng AbstractValueAdaptingCache triển khai giao diện Cache, chủ yếu trừu tượng hóa logic xử lý giá trị NULL.
-
allowNullValuesthuộc tính cho biết có cho phép xử lý bộ nhớ đệm chứa giá trị NULL hay không -
fromStoreValuecủa phương thức xử lý giá trị NULLgetthao tác, trong thuộc tínhallowNullValueskhi là true, xử lý NullValue thành NULL -
toStoreValuephương thức xử lý giá trị NULLputthao tác, trong thuộc tínhallowNullValueskhi là true, xử lý NULL thành NullValue, nếu không thì ném ngoại lệ -
toValueWrapperphương thức cung cấpCachetrong giao diệngettriển khai mặc định của phương thức, đọc giá trị từ bộ nhớ đệm, rồi thông quafromStoreValuechuyển đổi, cuối cùng đóng gói thànhSimpleValueWrapperQuay lại -
ValueWrapper get(Object key)vàT get(Object key, @Nullable Classtype)Phương thức được triển khai dựa trên phương thức nêu trên -
ValueWrapper get(Object key)và@Nullable Classtype)Phương thức được triển khai dựa trên phương thức nêu trên -
lookupPhương thức trừu tượng dùng để lớp con lấy giá trị thực sự trong bộ nhớ đệm
ConcurrentMapCache
public class ConcurrentMapCache extends AbstractValueAdaptingCache { private final String name; //定义ConcurrentMap缓存 private final ConcurrentMap<Object, Object> store; //如果要缓存的是值对象的copy,则由此序列化代理类处理 @Nullable private final SerializationDelegate serialization; public ConcurrentMapCache(String name) { this(name, new ConcurrentHashMap(256), true); } //默认允许处理null public ConcurrentMapCache(String name, boolean allowNullValues) { this(name, new ConcurrentHashMap(256), allowNullValues); } //默认serialization = null public ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues) { this(name, store, allowNullValues, (SerializationDelegate)null); } protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues, @Nullable SerializationDelegate serialization) { super(allowNullValues); Assert.notNull(name, "Name must not be null"); Assert.notNull(store, "Store must not be null"); this.name = name; this.store = store; this.serialization = serialization; } //serialization不为空,缓存值对象的copy public final boolean isStoreByValue() { return this.serialization != null; } public final String getName() { return this.name; } public final ConcurrentMap<Object, Object> getNativeCache() { return this.store; } //实现lookup:store#get @Nullable protected Object lookup(Object key) { return this.store.get(key); } //基于ConcurrentMap::computeIfAbsent方法实现;get和put的值由fromStoreValue和toStoreValue处理Null @Nullable public <T> T get(Object key, Callable<T> valueLoader) { return this.fromStoreValue(this.store.computeIfAbsent(key, (k) -> { try { return this.toStoreValue(valueLoader.call()); } catch (Throwable var5) { throw new ValueRetrievalException(key, valueLoader, var5); } })); } public void put(Object key, @Nullable Object value) { this.store.put(key, this.toStoreValue(value)); } @Nullable public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Object existing = this.store.putIfAbsent(key, this.toStoreValue(value)); return this.toValueWrapper(existing); } public void evict(Object key) { this.store.remove(key); } public boolean evictIfPresent(Object key) { return this.store.remove(key) != null; } public void clear() { this.store.clear(); } public boolean invalidate() { boolean notEmpty = !this.store.isEmpty(); this.store.clear(); return notEmpty; } protected Object toStoreValue(@Nullable Object userValue) { Object storeValue = super.toStoreValue(userValue); if (this.serialization != null) { try { return this.serialization.serializeToByteArray(storeValue); } catch (Throwable var4) { throw new IllegalArgumentException("Failed to serialize cache value '" + userValue + "'. Does it implement Serializable?", var4); } } else { return storeValue; } } protected Object fromStoreValue(@Nullable Object storeValue) { if (storeValue != null && this.serialization != null) { try { return super.fromStoreValue(this.serialization.deserializeFromByteArray((byte[])((byte[])storeValue))); } catch (Throwable var3) { throw new IllegalArgumentException("Failed to deserialize cache value '" + storeValue + "'", var3); } } else { return super.fromStoreValue(storeValue); } } }
ConcurrentMapCache kế thừa lớp trừu tượng AbstractValueAdaptingCache ,là cơ chế triển khai bộ nhớ đệm mặc định của Spring. Nó hỗ trợ lưu bản sao của đối tượng vào bộ nhớ đệm, việc tuần tự hóa do SerializationDelegate
serialization xử lý, mặc định là null tức bộ nhớ đệm dựa trên tham chiếu. Các thao tác liên quan đến bộ nhớ đệm dựa trên lớp cơ sở AbstractValueAdaptingCache việc xử lý giá trị null của
nó, mặc định cho phép là null.
CacheManager
public interface CacheManager { @Nullable //获取指定name的Cache,可能延迟创建 Cache getCache(String name); //获取当前CacheManager下的Cache name集合 Collection<String> getCacheNames(); }
CacheManager Dựa trên
name để quản lý một nhóm Cache. Tất nhiên, CacheManager cũng có nhiều lớp triển khai, như ConcurrentMapCacheManager 、 AbstractCacheManager và SimpleCacheManager ,những xxxCacheManager các lớp đều nhằm thiết lập quy tắc quản lý Cache, ở đây sẽ không bàn sâu thêm.
Hai Sử dụng SpringCache
2.1 Cách sử dụng
Spring Cache chính là một framework như vậy. Nó sử dụng AOP để triển khai chức năng bộ nhớ đệm dựa trên annotation và cung cấp sự trừu tượng hóa hợp lý. Mã nghiệp vụ không cần quan tâm bên dưới đang dùng framework bộ nhớ đệm nào, chỉ cần thêm một annotation đơn giản là có thể triển khai chức năng bộ nhớ đệm.
Thêm phần phụ thuộc
<!--springboot的cache支持--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--配置拓展还需添加的依赖--> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.2</version> </dependency>
Cấu hình Cache
Thêm chú thích @EnableCaching vào lớp khởi động Application
@SpringBootApplication @MapperScan("com.xxxx.dao") @EnableCaching public class AxxxxApplication { public static void main(String[] args) { SpringApplication.run(AxxxxApplication.class, args); } }
Cấu hình loại bộ nhớ đệm cho cache, ở đây chủ yếu giới thiệu bộ nhớ đệm cục bộ
//方式一:ConcurrentMapCache (不好设置过期时间) @Configuration public class SpringCachingConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); Set<ConcurrentMapCache> mapCaches=new HashSet<>(); //配置cache类型与目录 mapCaches.add(new ConcurrentMapCache("cacheNames1")); mapCaches.add(new ConcurrentMapCache("cacheNames2")); mapCaches.add(new ConcurrentMapCache("cacheNames3")); mapCaches.add(new ConcurrentMapCache("cacheNames4")); cacheManager.setCaches(mapCaches); cacheManager.afterPropertiesSet(); return cacheManager; } } //方式二:CaffeineCache (可设置过期时间) @Configuration public class SpringCachingConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); List<CaffeineCache> caffeineCaches = new ArrayList<>(); //SysConstants.CACHE_NAMES=new ArrayList( //Arrays.asList("cacheNames1","cacheNames2","cacheNames3","cacheNames4") //); for (String cacheName: SysConstants.CACHE_NAMES) { caffeineCaches.add(new CaffeineCache(cacheName, Caffeine.newBuilder() .initialCapacity(50) .maximumSize(30000)//最大存储数量 .expireAfterWrite(2, TimeUnit.HOURS)//过期时间2 小时 .build())); } cacheManager.setCaches(caffeineCaches); return cacheManager; } } //方式三。。。。还可以配置redis以及其他的方式,这里主要介绍以上两种方式
Triển khai bộ nhớ đệm cho giao diện
//控制器1 @RestController @RequestMapping("/path1") @Cacheable(cacheNames = "cacheNames1" ,key="#root.methodName+#a0" ) public class Controller1 { } //控制器2 @RestController @RequestMapping("/path2") @Cacheable(cacheNames = "cacheNames2" ,key="#root.methodName+#a0" ) public class Controller2 { } //控制器3 @RestController @RequestMapping("/path3") @Cacheable(cacheNames = "cacheNames3" ,key="#root.methodName+#a0" ) public class Controller3 { } //控制器4 @RestController @RequestMapping("/path4") @Cacheable(cacheNames = "cacheNames4" ,key="#root.methodName+#a0" ) public class Controller4 { }
2.2 Giải thích chi tiết các annotation SpringCache
Annotation @Cacheable
Nếu không có trong bộ nhớ đệm:truy vấn cơ sở dữ liệu, lưu vào bộ nhớ đệm, trả về kết quả,
Nếu có trong bộ nhớ đệm:trả kết quả trực tiếp
| Tham số | Giải thích | ví dụ |
| value | Tên bộ nhớ đệm, được định nghĩa trong tệp cấu hình spring , phải chỉ định ít nhất một tên |
Ví dụ: @Cacheable(value=”mycache”) @Cacheable(value={”cache1”,”cache2”} |
| key | Khóa của bộ nhớ đệm, có thể để trống; nếu chỉ định thì phải viết theo biểu thức SpEL, nếu không chỉ định thì mặc định kết hợp tất cả tham số của phương thức | @Cacheable(value=”testcache”,key=”#userName”) |
| điều kiện | Điều kiện lưu vào bộ nhớ đệm, có thể để trống, viết bằng SpEL, trả về true hoặc false, chỉ lưu vào bộ nhớ đệm khi là true | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
Tác dụng: Có thể dùng để ghi bộ nhớ đệm, lưu kết quả vào bộ nhớ đệm để những lần gọi sau có thể trả thẳng giá trị trong bộ nhớ đệm mà không cần thực thi phương thức thực tế lần nữa. Cách dùng đơn giản nhất,tên annotation=tên bộ nhớ đệm,ví dụ sử dụng như sau:
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache public Account getAccountByName(String userName) { // 方法内部实现不考虑缓存逻辑,直接实现业务 System.out.println("real query account."+userName); return getFromDB(userName); }
Chú thích @CacheEvict
@CacheEvict:Annotation xóa bộ nhớ đệm, rất hữu ích cho việc xóa dữ liệu cũ và dữ liệu không còn cần thiết. Ở đây còn có thêm một tham số (allEntries), khi đặt allEntries=true, có thể xóa hàng loạt toàn bộ các mục
| Tham số | Giải thích | ví dụ |
| value | Tên bộ nhớ đệm, được định nghĩa trong tệp cấu hình spring , phải chỉ định ít nhất một tên | @CacheEvict(value=”my cache”) |
| key | Khóa của bộ nhớ đệm, có thể để trống; nếu chỉ định thì phải viết theo biểu thức SpEL, nếu không chỉ định thì mặc định kết hợp tất cả tham số của phương thức | @CacheEvict(value=”testcache”,key=”#userName”) |
| điều kiện | Điều kiện lưu vào bộ nhớ đệm, có thể để trống, viết bằng SpEL, trả về true hoặc false, chỉ lưu vào bộ nhớ đệm khi là true | @CacheEvict(value=”testcache”,condition=”#userName.length()>2”) |
| allEntries | Có xóa toàn bộ nội dung bộ nhớ đệm hay không, mặc định là false, nếu chỉ định true thì toàn bộ bộ nhớ đệm sẽ được xóa ngay sau khi gọi phương thức | @CachEvict(value=”testcache”,allEntries=true) |
| beforeInvocation | Có xóa bộ nhớ đệm trước khi thực thi phương thức hay không, mặc định là false, nếu chỉ định là true, bộ nhớ đệm sẽ được xóa khi phương thức chưa thực thi, theo mặc định, nếu quá trình thực thi phương thức ném ngoại lệ, bộ nhớ đệm sẽ không bị xóa | @CachEvict(value=”testcache”,beforeInvocation=true) |
@CacheEvict(value="accountCache",key="#account.getName()")// 清空accountCache 缓存 public void updateAccount(Account account) { updateDB(account); } @CacheEvict(value="accountCache",allEntries=true)// 清空accountCache 缓存 public void reload() { reloadAll() } @Cacheable(value="accountCache",condition="#userName.length() <=4")// 缓存名叫 accountCache public Account getAccountByName(String userName) { // 方法内部实现不考虑缓存逻辑,直接实现业务 return getFromDB(userName); }
Annotation @CachePut
@CachePut:Khi cần cập nhật bộ nhớ đệm mà không làm ảnh hưởng đến việc chạy phương thức, có thể dùng chú thích này. Nghĩa là luôn thực thi phương thức đó và đưa kết quả vào bộ nhớ đệm;
| Tham số | Giải thích | ví dụ |
| value | Tên bộ nhớ đệm, được định nghĩa trong tệp cấu hình spring , phải chỉ định ít nhất một tên | @CachePut(value=”my cache”) |
| key | Khóa của bộ nhớ đệm, có thể để trống; nếu chỉ định thì phải viết theo biểu thức SpEL, nếu không chỉ định thì mặc định kết hợp tất cả tham số của phương thức | @CachePut(value=”testcache”,key=”#userName”) |
| điều kiện | Điều kiện lưu vào bộ nhớ đệm, có thể để trống, viết bằng SpEL, trả về true hoặc false, chỉ lưu vào bộ nhớ đệm khi là true | @CachePut(value=”testcache”,condition=”#userName.length()>2”) |
Annotation @CachePut, annotation này có thể bảo đảm phương thức được thực thi, đồng thời giá trị trả về của phương thức cũng được ghi vào bộ nhớ đệm, qua đó cập nhật đồng bộ bộ nhớ đệm và cơ sở dữ liệu.
@CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存 public Account updateAccount(Account account) { return updateDB(account); }
Annotation @Caching
Khi sử dụng bộ nhớ đệm, có thể cần đồng thời cập nhật và xóa, dẫn đến trường hợp dùng nhiều chú thích cùng lúc.Còn @Caching có thể thực hiện
//添加user缓存的同时,移除userPage的缓存 @Caching( put =@CachePut(value = "user",key ="#userVo.id"), evict = @CacheEvict(value = "userPage",allEntries = true) )
@Caching(put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") }) public User save(User user) {
@CacheConfig
Trong mọi @Cacheable()đều có thuộc tính value=“xxx”, rõ ràng nếu có nhiều phương thức thì viết như vậy cũng khá mệt. Nếu có thể khai báo hết một lần thì sẽ tiện hơn, vì thế mới có cấu hình @CacheConfig. @CacheConfig là một annotation ở cấp lớp cho phép dùng chung các tên bộ nhớ đệm, nếu bạn viết tên khác trong phương thức thì tên tại phương thức vẫn được ưu tiên.
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
Lưu bộ nhớ đệm có điều kiện
Dưới đây là một số cách lưu đệm có điều kiện thường dùng
//@Cacheable将在执行方法之前( #result还拿不到返回值)判断condition,如果返回true,则查缓存; @Cacheable(value = "user", key = "#id", condition = "#id lt 10") public User conditionFindById(final Long id) //@CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,如果返回true,则放入缓存; @CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'") public User conditionSave(final User user) //@CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,如果返回false,则放入缓存;(即跟condition相反) @CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'") public User conditionSave2(final User user) //@CacheEvict, beforeInvocation=false表示在方法执行之后调用(#result能拿到返回值了);且判断condition,如果返回true,则移除缓存; @CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'") public User conditionDelete(final User user)
Annotation bộ nhớ đệm tùy chỉnh
Chẳng hạn, tổ hợp @Caching trước đó khiến các annotation trên phương thức làm toàn bộ đoạn mã trông khá rối, lúc này có thể dùng annotation tùy chỉnh để kết hợp các annotation này thành một annotation, ví dụ:
@Caching(put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") }) @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface UserSaveCache { }
Như vậy, chúng ta chỉ cần sử dụng đoạn mã sau trên phương thức, toàn bộ mã sẽ trông khá gọn gàng.
@UserSaveCache public User save(User user)
Mở rộng
Ví dụ khi gọi findByUsername, không nên chỉ đưa username–>user vào bộ nhớ đệm, mà nên đưa cả id—>user và email—>user vào cùng;như vậy, lần sau nếu tìm theo id thì có thể tìm thấy ngay trong bộ nhớ đệm
@Caching( cacheable = { @Cacheable(value = "user", key = "#username") }, put = { @CachePut(value = "user", key = "#result.id", condition = "#result != null"), @CachePut(value = "user", key = "#result.email", condition = "#result != null") } ) public User findByUsername(final String username) { System.out.println("cache miss, invoke find by username, username:" + username); for (User user : users) { if (user.getUsername().equals(username)) { return user; } } return null; }
Thực ra với:id—>user;username—->user;email—>user;cách tốt hơn có thể là:id—>user;username—>id;email—>id;bảo đảm chỉ lưu một bản user;ví dụ:
@CachePut(value="cacheName", key="#user.username", cacheValue="#user.username") public void save(User user) @Cacheable(value="cacheName", key="#user.username", cacheValue="#caches[0].get(#caches[0].get(#username).get())") public User findByUsername(String username)
Dữ liệu ngữ cảnh SpEL
Spring Cache cung cấp một số dữ liệu ngữ cảnh SpEL để chúng ta sử dụng, bảng dưới đây được trích trực tiếp từ tài liệu chính thức của Spring:
| Tên | Vị trí | Mô tả | Ví dụ |
| methodName | đối tượng root | Tên phương thức hiện đang được gọi | root.methodName |
| method | đối tượng root | Phương thức hiện đang được gọi | root.method.name |
| target | đối tượng root | Đối tượng đích hiện đang được gọi | root.target |
| targetClass | đối tượng root | Lớp của đối tượng đích hiện đang được gọi | root.targetClass |
| args | đối tượng root | Danh sách tham số của phương thức hiện đang được gọi | root.args[0] |
| các bộ nhớ đệm | đối tượng root | Danh sách bộ nhớ đệm được sử dụng trong lần gọi phương thức hiện tại (ví dụ @Cacheable(value={“cache1”, “cache2”})), tức là có hai bộ nhớ đệm | root.caches[0].name |
| tên đối số | Ngữ cảnh thực thi | Tham số của phương thức hiện đang được gọi, chẳng hạn findById(Long id), chúng ta có thể lấy tham số qua #id | user.id |
| result | Ngữ cảnh thực thi | Giá trị trả về sau khi phương thức thực thi (chỉ có hiệu lực với điều kiện được xét sau khi phương thức thực thi, như ‘unless', 'cache evict' có beforeInvocation=false) | result |
@CacheEvict(value = "user", key = "#user.id", condition = "#root.target.canCache() and #root.caches[0].get(#user.id).get().username ne #user.username", beforeInvocation = true) public void conditionUpdate(User user)
Tóm lược
Đối với khai báo bộ nhớ đệm, cơ chế bộ nhớ đệm của Spring cung cấp một nhóm annotation Java:
· @Cacheable
o
Chức năng: kích hoạt ghi bộ nhớ đệm, nếu chưa có trong bộ nhớ đệm thì truy vấn cơ sở dữ liệu, lưu vào bộ nhớ đệm rồi trả về kết quả, nếu đã có trong bộ nhớ đệm thì trả về kết quả trực tiếp
o Ứng dụng:Truy vấn cơ sở dữ liệu phương thức
· @CacheEvict
o
Chức năng: kích hoạt xóa bộ nhớ đệm
o Ứng dụng:Xóa hoặc sửa đổi cơ sở dữ liệu phương thức
· @CachePut
o
Chức năng:Ghi vào bộ nhớ đệm(không ảnh hưởng đến việc thực thi phương thức).
o Ứng dụng:Thêm mới vào cơ sở dữ liệu phương thức
· @Lưu bộ nhớ đệm
o
Chức năng: kết hợp lại nhiều thao tác bộ nhớ đệm cần áp dụng cho phương thức
o Ứng dụng:Sử dụng kết hợp các annotation ở trên