Bir SpringCache tabanlı önbellekleme çözümü
1.1 SpringCache'e giriş
springcache spring3.1 sürümüyle yayımlandı; önbellek kullanımını kapsüller ve soyutlar. Metotlarda annotation anotasyonları kullanılarak önbellekteki sonuçlar alınabilir. Tam da anotasyonları kullandığı için iş kodu ile önbellek kodu arasındaki bağımlılık sorununu çözer; yani iş koduna müdahale etmeden mevcut kodun hemen önbelleği desteklemesini sağlar ve geliştiricilerin önbelleği farkına bile varmadan kullanmasına olanak tanır.
1.2 SpringCache'e genel bakış
Redis, Memcached, Guava ve Caffeine gibi birçok önbellekleme çatısı vardır; her birinin kendine özgü avantajları bulunur.
Programımız önbellek kullanmak istiyorsa bu çerçevelere bağımlı hâle gelmesi gerekir。Akıllı mimarlar bağımlılığı azaltmak için arayüzlerden zaten yararlanıyor; nesne yönelimli yaklaşımın soyutlama ve çok biçimlilik özelliklerini kullanarak iş mantığı kodunu belirli çerçevelerden ayırıyorlar。
Ancak yine de kod içinde önbellekle ilgili arayüz ve yöntemleri açıkça çağırmamız, uygun zamanda verileri önbelleğe eklememiz ve uygun zamanda önbellekten okumamız gerekiyor. Bir düşünün「AOP」için kullanım senaryosu; bu, tam da AOP'nin yapması gereken bir iş değil mi?
Spring
3.1 sürümünden itibaren, [@Transactional](/Transactional ) ile sağlanan anotasyon tabanlı işlem desteğine benzer bir Cache anotasyon desteği ve Cache soyutlaması sunulmuştur; bundan önce bu işlev genellikle AOP ile gerçekleştirilirdi.
Spring Cache kullanmanın faydaları:
-
Temel bir Cache soyutlaması sunar, altta yatan farklı Cache uygulamaları arasında geçişi kolaylaştırır;
-
Cache anotasyonu aracılığıyla, işlem yönetiminde olduğu gibi önbellekleme mantığı iş kodumuza şeffaf biçimde uygulanabilir ve bunu tamamlamak için daha az kod yeterlidir;
-
İşlem geri alındığında önbelleğin de otomatik olarak geri alınmasını sağlar;
-
Oldukça karmaşık önbellekleme mantığını destekler;

1.3 Spring Cache'in temel tasarımı
Spring 3.1 sürümünden itibaren Spring Cache, yani Spring önbellek soyutlaması kullanıma sunuldu. Şunu tanımlayarak org.springframework.cache.Cache ve org.springframework.cache.CacheManager arayüzüyle farklı önbellekleme teknolojilerini birleştirir ve geliştirme sürecini kolaylaştırmak için JCache ek açıklamalarının kullanımını destekler.
Cache arayüzü, önbellek bileşenlerinin sözleşmesini tanımlar ve çeşitli önbellek işlemlerini kapsar. Spring, Cache arayüzü için çeşitli xxxCache uygulaması: RedisCache , EhCacheCache , ConcurrentMapCache vb.
Kaynak kodun bir bölümünü inceleyerek Cache arayüzünü ve CacheManager arayüzü.
Cache arayüzü
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(); } }
Görüldüğü gibi Cache arayüzü, önbellekle ilgili get put evict gibi işlemleri soyutlar.
AbstractValueAdaptingCache soyut sınıfı
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; } }
Soyut sınıf AbstractValueAdaptingCache Cache arayüzünü uygular ve temel olarak NULL değerlerinin işlenmesine ilişkin mantığı soyutlar.
-
allowNullValuesözelliği, NULL değerlerini işleyen önbelleğe izin verilip verilmediğini belirtir -
fromStoreValuemetodunun NULL değerlerini işlemesine ilişkingetişlemi, özellikteallowNullValuestrue olduğunda, NullValue değerini NULL olarak işler -
toStoreValueNULL değerlerini işleyen yöntemputişlemi, özellikteallowNullValuestrue olduğunda,NULL, NullValue olarak işlenir,aksi hâlde istisna fırlatılır -
toValueWrappermetodu şunu sağlarCachearayüzündegetmetodunun varsayılan uygulaması, değeri önbellekten okur, ardındanfromStoreValuedönüştürülür,sonunda şu biçimde sarmalanırSimpleValueWrapperGeri dön -
ValueWrapper get(Object key)veT get(Object key, @Nullable Classtype)yöntemi, yukarıdaki yöntem temel alınarak uygulanır -
ValueWrapper get(Object key)ve@Nullable Classtype)yöntemi, yukarıdaki yöntem temel alınarak uygulanır -
lookupSoyut yöntem, alt sınıfların gerçek önbellek değerini almasını sağlamak için kullanılır
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 şu soyut sınıftan türemiştir: AbstractValueAdaptingCache ,Spring'in varsayılan önbellek uygulamasıdır。Önbelleğe alınan nesnelerin kopyalarını saklamayı destekler,serileştirme SerializationDelegate
serialization tarafından gerçekleştirilir,varsayılan değer null'dır, yani referansa dayalı önbellekleme yapılır。Önbellekle ilgili işlemler şu temel sınıfa dayanır AbstractValueAdaptingCache öğesinin
null değerlerini işleme biçimi, varsayılan olarak null değerine izin verilir。
CacheManager
public interface CacheManager { @Nullable //获取指定name的Cache,可能延迟创建 Cache getCache(String name); //获取当前CacheManager下的Cache name集合 Collection<String> getCacheNames(); }
CacheManager name temelinde
bir Cache grubunu yönetir. Elbette, CacheManager için de çok sayıda uygulama sınıfı vardır, örneğin ConcurrentMapCacheManager 、 AbstractCacheManager ve SimpleCacheManager ,bunlar xxxCacheManager sınıflarının tümü Cache yönetim kurallarını belirlemek içindir; burada daha fazla ayrıntıya girilmeyecektir。
İki SpringCache kullanımı
2.1 Nasıl kullanılır
Spring Cache tam da böyle bir çerçevedir. AOP kullanarak anotasyon tabanlı önbellekleme sağlar ve uygun bir soyutlama sunar. İş mantığı kodunun altyapıda hangi önbellek çerçevesinin kullanıldığını bilmesi gerekmez, yalnızca bir anotasyon ekleyerek önbellekleme işlevini sağlayabilir.
Bağımlılık ekleme
<!--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>
Cache yapılandırması
Application başlangıç sınıfına @EnableCaching ek açıklamasını ekleyin
@SpringBootApplication @MapperScan("com.xxxx.dao") @EnableCaching public class AxxxxApplication { public static void main(String[] args) { SpringApplication.run(AxxxxApplication.class, args); } }
cache’in önbellek türünü yapılandırma; burada ağırlıklı olarak yerel önbellek ele alınmaktadır
//方式一: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以及其他的方式,这里主要介绍以上两种方式
Arayüz önbelleklemesini uygulama
//控制器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 SpringCache anotasyonlarının ayrıntılı açıklaması
@Cacheable ek açıklaması
Önbellekte yoksa: veritabanını sorgula, önbelleğe kaydet, sonucu döndür,
Önbellekte varsa: sonucu doğrudan döndür
| Parametreler | Açıklama | example |
| value | Önbelleğin adı, spring yapılandırma dosyasında tanımlanır; en az bir tane belirtilmelidir |
Örneğin: @Cacheable(value=”mycache”) @Cacheable(value={”cache1”,”cache2”} |
| anahtar | Önbellek key değeri; boş bırakılabilir, belirtilirse SpEL ifadesi olarak yazılmalıdır; belirtilmezse varsayılan olarak yöntemin tüm parametreleri birleştirilir | @Cacheable(value=”testcache”,key=”#userName”) |
| condition | Önbelleğe alma koşulu; boş bırakılabilir, SpEL ile yazılır ve true veya false döndürür; yalnızca true olduğunda önbelleğe alınır | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
İşlevi:Önbelleğe yazmak için kullanılabilir; sonuçları önbellekte saklar, böylece sonraki çağrılarda gerçek metodu yeniden çalıştırmadan önbellekteki değer doğrudan döndürülebilir. En basit kullanım biçimi, anotasyon adı=önbellek adı şeklindedir, kullanım örneği aşağıdadır:
@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache public Account getAccountByName(String userName) { // 方法内部实现不考虑缓存逻辑,直接实现业务 System.out.println("real query account."+userName); return getFromDB(userName); }
@CacheEvict anotasyonu
@CacheEvict:Önbelleği silmeye yarayan bir anotasyondur, eski ve gereksiz verileri silmek için oldukça kullanışlıdır. Ayrıca (allEntries) adlı bir parametresi vardır, allEntries=true olarak ayarlandığında tüm girdiler topluca silinebilir
| Parametreler | Açıklama | example |
| value | Önbelleğin adı, spring yapılandırma dosyasında tanımlanır; en az bir tane belirtilmelidir | @CacheEvict(value=”my cache”) |
| anahtar | Önbellek key değeri; boş bırakılabilir, belirtilirse SpEL ifadesi olarak yazılmalıdır; belirtilmezse varsayılan olarak yöntemin tüm parametreleri birleştirilir | @CacheEvict(value=”testcache”,key=”#userName”) |
| condition | Önbelleğe alma koşulu; boş bırakılabilir, SpEL ile yazılır ve true veya false döndürür; yalnızca true olduğunda önbelleğe alınır | @CacheEvict(value=”testcache”,condition=”#userName.length()>2”) |
| allEntries | Önbellekteki tüm içeriğin temizlenip temizlenmeyeceği,varsayılan değer false'dur,true olarak belirtilirse,metot çağrısından hemen sonra tüm önbellek temizlenir | @CachEvict(value=”testcache”,allEntries=true) |
| beforeInvocation | Metot çalıştırılmadan önce temizlenip temizlenmeyeceğini belirtir; varsayılan değer false'tur. true olarak belirtilirse önbellek, metot henüz çalıştırılmadan temizlenir. Varsayılan durumda, metodun çalışması sırasında istisna fırlatılırsa önbellek temizlenmez | @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); }
@CachePut anotasyonu
@CachePut: Metodun çalışmasına müdahale etmeden önbelleği güncellemek gerektiğinde bu anotasyon kullanılabilir. Başka bir deyişle, metot her zaman çalıştırılır ve sonuç önbelleğe konur;
| Parametreler | Açıklama | example |
| value | Önbelleğin adı, spring yapılandırma dosyasında tanımlanır; en az bir tane belirtilmelidir | @CachePut(value=”my cache”) |
| anahtar | Önbellek key değeri; boş bırakılabilir, belirtilirse SpEL ifadesi olarak yazılmalıdır; belirtilmezse varsayılan olarak yöntemin tüm parametreleri birleştirilir | @CachePut(value=”testcache”,key=”#userName”) |
| condition | Önbelleğe alma koşulu; boş bırakılabilir, SpEL ile yazılır ve true veya false döndürür; yalnızca true olduğunda önbelleğe alınır | @CachePut(value=”testcache”,condition=”#userName.length()>2”) |
@CachePut anotasyonu, metodun çalıştırılmasını ve aynı zamanda dönüş değerinin önbelleğe kaydedilmesini sağlayarak önbellek ile veritabanının eşzamanlı güncellenmesini gerçekleştirir.
@CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存 public Account updateAccount(Account account) { return updateDB(account); }
@Caching ek açıklaması
Önbellek kullanılırken güncelleme ve silme işlemleri aynı anda yapılabilir; bu da birden fazla ek açıklamanın birlikte kullanılmasını gerektirebilir. @Caching ise
//添加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
Tüm @Cacheable()ek açıklamalarında value=“xxx” özelliği bulunur; yöntem sayısı arttığında bunu yazmak elbette epey yorucu olur. Hepsini tek seferde bildirmek mümkün olsa iş kolaylaşırdı. Bu yüzden @CacheConfig yapılandırması vardır; @CacheConfig, önbellek adlarının paylaşılmasını sağlayan sınıf düzeyinde bir ek açıklamadır. Yönteminizde başka bir ad yazarsanız yine yöntem düzeyinde belirtilen ad esas alınır.
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
Koşullu önbellekleme
Aşağıda sık kullanılan bazı koşullu önbellekleme örnekleri verilmiştir
//@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)
Özel önbellek anotasyonları
Örneğin, daha önceki @Caching birleşimi, metot üzerindeki anotasyonlar nedeniyle kodun genel olarak dağınık görünmesine yol açabilir. Bu durumda, bu anotasyonları tek bir anotasyonda birleştirmek için özel bir anotasyon kullanılabilir, örneğin:
@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 { }
Böylece yöntemde aşağıdaki kodu kullanmamız yeterlidir; kodun tamamı oldukça sade görünür.
@UserSaveCache public User save(User user)
Genişletme
Örneğin findByUsername kullanılırken yalnızca username–>user eklenmemeli, id—>user ve email—>user da birlikte eklenmelidir;böylece bir sonraki sefer id ile arama yapıldığında sonuç doğrudan önbellekte bulunur
@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; }
Aslında şu yapı için: id—>user;username—->user;email—>user;daha iyi bir yaklaşım şöyle olabilir: id—>user;username—>id;email—>id;böylece user yalnızca bir kopya olarak saklanır;örneğin:
@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)
SpEL bağlam verileri
Spring Cache, kullanabileceğimiz bazı SpEL bağlam verileri sağlar,aşağıdaki tablo doğrudan Spring'in resmî belgelerinden alınmıştır:
| Ad | Konum | Açıklama | Örnek |
| methodName | root nesnesi | O anda çağrılan metodun adı | root.methodName |
| method | root nesnesi | O anda çağrılan metot | root.method.name |
| hedef | root nesnesi | O anda çağrılan hedef nesne | root.target |
| targetClass | root nesnesi | Şu anda çağrılan hedef nesnenin sınıfı | root.targetClass |
| args | root nesnesi | O anda çağrılan metodun parametre listesi | root.args[0] |
| caches | root nesnesi | Geçerli metot çağrısında kullanılan önbelleklerin listesi(örneğin @Cacheable(value={“cache1”, “cache2”}) durumunda iki önbellek vardır) | root.caches[0].name |
| argüman adı | Yürütme bağlamı | O anda çağrılan metodun parametreleri; örneğin findById(Long id) için #id aracılığıyla parametreye erişebiliriz | user.id |
| result | Yürütme bağlamı | Metot yürütüldükten sonraki dönüş değeri(yalnızca metot yürütüldükten sonra yapılan değerlendirmelerde geçerlidir; örneğin ‘unless' veya 'cache evict' için beforeInvocation=false olduğunda) | 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)
Kısa özet
Önbellek bildirimleri için spring'in önbellek desteği bir dizi java ek açıklaması sunar:
· @Cacheable
o
İşlev:Önbelleğe yazmayı tetikler, önbellekte veri yoksa veritabanını sorgular, önbelleğe kaydeder ve sonucu döndürür, önbellekte veri varsa sonucu doğrudan döndürür
o Uygulama:Veritabanını sorgulama metot
· @CacheEvict
o
İşlev: Önbelleğin temizlenmesini tetikler
o Uygulama:Veritabanını silme veya değiştirme metot
· @CachePut
o
İşlev:Önbelleğe yazma(metodun çalışmasını etkilemez)。
o Uygulama:Veritabanına ekle metot
· @Önbellekleme
o
İşlev: Bir metoda uygulanacak birden fazla önbellek işlemini yeniden birleştirmek
o Uygulama:yukarıdaki anotasyonların birlikte kullanılması