انتقل إلى المحتوى

ابحث في CooLWJ

选择搜索范围,输入关键词后按回车。

اللغة

مقدمة في استخدام Spring Cache

أولًا حل للتخزين المؤقت قائم على SpringCache

1.1 مقدمة إلى SpringCache

springcache أُطلقت مع الإصدار spring3.1، وهي تغلّف استخدام ذاكرة التخزين المؤقت وتجرّده، وتتيح الحصول على النتائج المخزنة مؤقتًا بمجرد استخدام annotation على الدوال. وبفضل استخدامها annotation، عالجت مشكلة الترابط بين شيفرة منطق الأعمال وشيفرة التخزين المؤقت، أي إنها تمكّن الشيفرة الحالية من دعم التخزين المؤقت فورًا دون التدخل في شيفرة منطق الأعمال، وتتيح للمطورين استخدامه بشفافية.

1.2 نظرة عامة على SpringCache

توجد أطر كثيرة للتخزين المؤقت، ولكلّ منها مزاياه، مثل Redis وMemcached وGuava وCaffeine وغيرها.
إذا أراد برنامجنا استخدام التخزين المؤقت، فلا بد من ربطه بهذه الأطر. وقد أخذ المعماريون الأذكياء يستخدمون الواجهات لتقليل الترابط، مستفيدين من خاصيتي التجريد وتعدد الأشكال في البرمجة الكائنية لفصل شيفرة منطق الأعمال عن الأطر المحددة.
لكننا ما زلنا بحاجة إلى استدعاء الواجهات والطرائق المرتبطة بالتخزين المؤقت صراحةً في الشيفرة، وإدراج البيانات في ذاكرة التخزين المؤقت في الوقت المناسب، وقراءتها منها في الوقت المناسب. تأمّل 「AOP」من حالات الاستخدام، أليس هذا بطبيعته مما ينبغي أن تتولاه AOP؟
منذ Spring 3.1، أصبح هناك دعم للتخزين المؤقت Cache باستخدام التعليقات التوضيحية، على غرار المعاملات بالتعليق التوضيحي [@Transactional](/Transactional )، مع توفير طبقة تجريد لـ Cache، بينما كان يُنفَّذ عادةً عبر AOP قبل ذلك.
فوائد استخدام Spring Cache:

  • يوفر تجريدًا أساسيًا للتخزين المؤقت، مما يسهل التبديل بين تطبيقات التخزين المؤقت الأساسية المختلفة؛

  • باستخدام التعليق التوضيحي Cache، يمكن تطبيق منطق التخزين المؤقت بشفافية على شيفرة الأعمال، على غرار المعاملات، ولا يتطلّب إنجاز ذلك إلا قدرًا أقل من الشيفرة؛

  • توفير تراجع تلقائي عن تغييرات الذاكرة المؤقتة أيضًا عند التراجع عن المعاملة؛

  • يدعم منطقًا معقدًا نسبيًا للتخزين المؤقت؛

المعرّف التقني لـ Spring Cache

1.3 التصميم الأساسي لـ Spring Cache

بدءًا من Spring 3.1، أُدخل Spring Cache، أي طبقة التجريد للتخزين المؤقت في Spring. من خلال تعريف org.springframework.cache.Cache و org.springframework.cache.CacheManager لتوحيد تقنيات التخزين المؤقت المختلفة عبر واجهة، مع دعم استخدام تعليقات JCache التوضيحية لتبسيط عملية التطوير.
تحدد واجهة Cache مواصفات مكونات التخزين المؤقت، وتتضمن مجموعة العمليات المختلفة الخاصة به. ويوفر Spring لواجهة Cache أنواعًا مختلفة من xxxCache تنفيذ: RedisCache , EhCacheCache , ConcurrentMapCache وغيرها.
لنتعرف من خلال بعض الشيفرة المصدرية على واجهة Cache و CacheManager واجهة.

واجهة 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();     } }

يمكن ملاحظة أن واجهة Cache تجرّد عمليات التخزين المؤقت ذات الصلة، مثل get وput وevict.

الصنف المجرّد 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;     } }

فئة مجرّدة AbstractValueAdaptingCache تُنفذ واجهة Cache، وتجرد أساسًا منطق التعامل مع قيم NULL.

  • allowNullValues تبيّن الخاصية ما إذا كان مسموحًا بالتعامل مع تخزين قيم NULL مؤقتًا

  • fromStoreValue الخاصة بمعالجة قيم NULL باستخدام الدالة get عملية، في الخاصية allowNullValues عندما تكون قيمته true، تُعالَج NullValue بوصفها NULL

  • toStoreValue  طريقة لمعالجة قيم NULL  put عملية، في الخاصية allowNullValues عندما تكون القيمة true، تُعالَج NULL بوصفها NullValue، وإلا فيُطرح استثناء

  • toValueWrapper توفّر الطريقة Cache ضمن الواجهة get التنفيذ الافتراضي للطريقة، الذي يقرأ القيمة من ذاكرة التخزين المؤقت، ثم عبر fromStoreValue التحويل، ثم تغليفه في النهاية على هيئة SimpleValueWrapper رجوع

  • ValueWrapper get(Object key) و T get(Object key, @Nullable Classtype) تُنفَّذ الطريقة بالاستناد إلى الطريقة المذكورة أعلاه

  • ValueWrapper get(Object key) و @Nullable Classtype) تُنفَّذ الطريقة بالاستناد إلى الطريقة المذكورة أعلاه

  • lookup طريقة مجردة تتيح للفئات الفرعية الحصول على قيمة التخزين المؤقت الفعلية

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 يرث الصنف المجرّد AbstractValueAdaptingCache ، وهو التنفيذ الافتراضي للتخزين المؤقت في Spring. ويدعم تخزين نسخ من الكائنات مؤقتًا، ويتولى SerializationDelegate serialization معالجة التسلسل، وقيمته الافتراضية null، أي التخزين المؤقت القائم على المراجع. وتستند عمليات التخزين المؤقت ذات الصلة إلى الفئة الأساسية AbstractValueAdaptingCache معالجة قيم null الخاصة به ، ويُسمح افتراضيًا بأن تكون القيمة null.

CacheManager

public interface CacheManager {     @Nullable     //获取指定name的Cache,可能延迟创建     Cache getCache(String name);      //获取当前CacheManager下的Cache name集合     Collection<String> getCacheNames(); }

CacheManager يدير مجموعة من كائنات Cache استنادًا إلى name. وبالطبع، CacheManager له أيضًا أصناف تنفيذ عديدة، مثل ConcurrentMapCacheManager 、 AbstractCacheManager و SimpleCacheManager ، هذه xxxCacheManager تهدف جميع هذه الفئات إلى وضع قواعد إدارة Cache، ولن نتعمق في مناقشتها هنا.

ثانيًا استخدام SpringCache

2.1 كيفية الاستخدام

Spring Cache هو إطار عمل من هذا النوع. فهو يستعين بالبرمجة جانبية التوجّه (AOP) لتوفير التخزين المؤقت القائم على التعليقات التوضيحية، ويقدّم تجريدًا مناسبًا بحيث لا تحتاج شيفرة الأعمال إلى الاهتمام بإطار التخزين المؤقت المستخدم في الطبقة الأساسية؛ فمجرد إضافة تعليق توضيحي بسيط يكفي لتفعيل التخزين المؤقت.

إضافة الاعتماديات

<!--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

أضف التعليمة التوضيحية @EnableCaching إلى فئة بدء التشغيل Application

@SpringBootApplication     @MapperScan("com.xxxx.dao")     @EnableCaching     public class AxxxxApplication {          public static void main(String[] args) {             SpringApplication.run(AxxxxApplication.class, args);         }      }

إعداد نوع التخزين المؤقت لـ cache، مع التركيز هنا على التخزين المؤقت المحلي

//方式一: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以及其他的方式,这里主要介绍以上两种方式

تنفيذ التخزين المؤقت للواجهات

//控制器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 التوضيحية

التعليق التوضيحي @Cacheable

إذا لم يكن موجودًا في الذاكرة المؤقتة: استعلم من قاعدة البيانات، وخزّنه في الذاكرة المؤقتة، وأعد النتيجة،
إذا كانت موجودة في ذاكرة التخزين المؤقت: تُعاد النتيجة مباشرةً

المعاملات التفسير مثال
القيمة اسم ذاكرة التخزين المؤقت، ويُعرّف في ملف إعدادات spring ، ويجب تحديد اسم واحد على الأقل مثلًا:
@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}
key مفتاح ذاكرة التخزين المؤقت، ويمكن أن يكون فارغًا؛ وإذا حُدد، فيجب كتابته وفق تعبير SpEL، وإذا لم يُحدد، فتُدمج جميع معاملات الدالة افتراضيًا @Cacheable(value=”testcache”,key=”#userName”)
condition شرط التخزين المؤقت، ويمكن تركه فارغًا، ويُكتب باستخدام SpEL ويُرجع true أو false، ولا يُنفّذ التخزين المؤقت إلا إذا كانت القيمة true @Cacheable(value=”testcache”,condition=”#userName.length()>2”)

الوظيفة:يمكن استخدامه للكتابة في الذاكرة المؤقتة، وحفظ النتائج فيها، بحيث يمكن عند الاستدعاءات اللاحقة إرجاع القيمة المخزنة مباشرة، دون الحاجة إلى تنفيذ الطريقة الفعلية مرة أخرى. أبسط طريقة للاستخدام,اسم التعليمة التوضيحية=اسم الذاكرة المؤقتة,ومثال الاستخدام كما يلي:

@Cacheable(value="accountCache")// 使用了一个缓存名叫 accountCache  public Account getAccountByName(String userName) {    // 方法内部实现不考虑缓存逻辑,直接实现业务    System.out.println("real query account."+userName);     return getFromDB(userName);  }

التعليمة التوضيحية @CacheEvict

@CacheEvict: تعليق توضيحي لحذف ذاكرة التخزين المؤقت، وهو مفيد جدًا لحذف البيانات القديمة وغير المفيدة. ويوجد هنا أيضًا معامل إضافي هو (allEntries)، وعند ضبط allEntries=true يمكن حذف جميع المدخلات دفعة واحدة

المعاملات التفسير مثال
القيمة اسم ذاكرة التخزين المؤقت، ويُعرّف في ملف إعدادات spring ، ويجب تحديد اسم واحد على الأقل @CacheEvict(value=”my cache”)
key مفتاح ذاكرة التخزين المؤقت، ويمكن أن يكون فارغًا؛ وإذا حُدد، فيجب كتابته وفق تعبير SpEL، وإذا لم يُحدد، فتُدمج جميع معاملات الدالة افتراضيًا @CacheEvict(value=”testcache”,key=”#userName”)
condition شرط التخزين المؤقت، ويمكن تركه فارغًا، ويُكتب باستخدام SpEL ويُرجع true أو false، ولا يُنفّذ التخزين المؤقت إلا إذا كانت القيمة true @CacheEvict(value=”testcache”,condition=”#userName.length()>2”)
allEntries ما إذا كان سيُمسح كل محتوى ذاكرة التخزين المؤقت، والقيمة الافتراضية هي false، وإذا حُدّدت true، فستُمسح جميع محتويات ذاكرة التخزين المؤقت فورًا بعد استدعاء الدالة @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation ما إذا كان المسح يتم قبل تنفيذ الدالة، والقيمة الافتراضية هي false، وإذا ضُبطت على true، فتُمسح ذاكرة التخزين المؤقت قبل تنفيذ الدالة، أما افتراضيًا، فإذا طرح تنفيذ الدالة استثناءً، فلن تُمسح ذاكرة التخزين المؤقت @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

@CachePut:يمكن استخدام هذا التعليق التوضيحي عند الحاجة إلى تحديث ذاكرة التخزين المؤقت دون التأثير في تنفيذ الدالة. أي إن الدالة تُنفذ دائمًا، وتُوضع النتيجة في ذاكرة التخزين المؤقت؛

المعاملات التفسير مثال
القيمة اسم ذاكرة التخزين المؤقت، ويُعرّف في ملف إعدادات spring ، ويجب تحديد اسم واحد على الأقل @CachePut(value=”my cache”)
key مفتاح ذاكرة التخزين المؤقت، ويمكن أن يكون فارغًا؛ وإذا حُدد، فيجب كتابته وفق تعبير SpEL، وإذا لم يُحدد، فتُدمج جميع معاملات الدالة افتراضيًا @CachePut(value=”testcache”,key=”#userName”)
condition شرط التخزين المؤقت، ويمكن تركه فارغًا، ويُكتب باستخدام SpEL ويُرجع true أو false، ولا يُنفّذ التخزين المؤقت إلا إذا كانت القيمة true @CachePut(value=”testcache”,condition=”#userName.length()>2”)

التعليمة التوضيحية @CachePut، وهي تضمن تنفيذ الدالة، مع تخزين القيمة التي تعيدها في الذاكرة المخبئية أيضًا، لتحقيق تحديث متزامن للذاكرة المخبئية وقاعدة البيانات.

@CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存 public Account updateAccount(Account account) {    return updateDB(account);  }

التعليمة التوضيحية @Caching

عند استخدام التخزين المؤقت، قد تجري عمليتا التحديث والحذف في الوقت نفسه، مما يستدعي استخدام عدة تعليقات توضيحية معًا. ويمكن لـ @Caching تحقيق

//添加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

تحتوي جميع تعليقات @Cacheable()على خاصية value=“xxx”، ومن الواضح أن كتابتها تصبح مرهقة إذا كثرت الدوال. ولو أمكن التصريح بها دفعة واحدة لكان ذلك أسهل، ولذلك وُجد إعداد @CacheConfig؛ فهو تعليق توضيحي على مستوى الفئة يتيح مشاركة أسماء ذاكرة التخزين المؤقت. وإذا كتبت اسمًا آخر في دالتك، فسيظل الاسم المحدد على مستوى الدالة هو المعتمد.

@CacheConfig("books") public class BookRepositoryImpl implements BookRepository {    @Cacheable   public Book findBook(ISBN isbn) {...} }

التخزين المؤقت المشروط
فيما يلي بعض حالات التخزين المؤقت المشروط الشائعة

//@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)

تعليقات توضيحية مخصصة للتخزين المؤقت

مثلًا، يجعل تركيب @Caching السابق التعليقات التوضيحية على الدالة تبدو مبعثرة في الشيفرة؛ وعندئذ يمكن استخدام تعليق توضيحي مخصص لجمع هذه التعليقات في تعليق واحد، مثل:

@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 { }

بهذا يكفينا استخدام الشيفرة التالية على الدالة، فتبدو الشيفرة كلها مرتبة وواضحة.

@UserSaveCache public User save(User user)

توسعة
مثلًا، عند استدعاء findByUsername، لا ينبغي تخزين username–>user وحده، بل ينبغي تخزين id—>user وemail—>user معه أيضًا؛ وهكذا، إذا جرى البحث بالمعرّف id في المرة التالية، فسيُعثر على النتيجة مباشرة في ذاكرة التخزين المؤقت

@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; }

في الواقع، بالنسبة إلى:id—>user;username—->user;email—>user;قد يكون الأسلوب الأفضل:id—>user;username—>id;email—>id;لضمان تخزين نسخة واحدة فقط من user؛ مثل:

@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
يوفر Spring Cache بعض بيانات سياق SpEL لاستخدامها، والجدول التالي مقتبس مباشرة من وثائق Spring الرسمية:

الاسم الموقع الوصف مثال
methodName كائن root اسم الطريقة التي يجري استدعاؤها حاليًا root.methodName
method كائن root الطريقة الجاري استدعاؤها root.method.name
target كائن root الكائن المستهدف الذي يجري استدعاؤه حاليًا root.target
targetClass كائن root فئة الكائن الهدف الذي يُستدعى حاليًا root.targetClass
args كائن root قائمة معاملات الدالة المستدعاة حاليًا root.args[0]
caches كائن root قائمة ذاكرات التخزين المؤقت المستخدمة في استدعاء الطريقة الحالي (مثل @Cacheable(value={“cache1”, “cache2”}))، وفي هذه الحالة توجد ذاكرتا تخزين مؤقت root.caches[0].name
اسم الوسيط سياق التنفيذ معاملات الدالة التي يجري استدعاؤها حاليًا، مثل findById(Long id)، ويمكننا الحصول على المعامل عبر #id user.id
النتيجة سياق التنفيذ القيمة المُعادة بعد تنفيذ الدالة(لا تكون فعّالة إلا في الشروط التي تُقيّم بعد تنفيذ الدالة، مثل ‘unless'، وbeforeInvocation=false في 'cache evict') النتيجة
@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)

خلاصة

للتصريح بالتخزين المؤقت، توفر آلية التخزين المؤقت في spring مجموعة من التعليقات التوضيحية في java:
· @Cacheable
o الوظيفة:تفعيل الكتابة في ذاكرة التخزين المؤقت، فإذا لم تكن البيانات فيها، تُستعلَم قاعدة البيانات، وتُخزَّن البيانات في الذاكرة المؤقتة، وتُعاد النتيجة، وإذا كانت البيانات موجودة فيها، تُعاد النتيجة مباشرة
o التطبيق:الاستعلام من قاعدة البيانات الطريقة
· @CacheEvict
o الوظيفة:تشغيل مسح ذاكرة التخزين المؤقت
o التطبيق:حذف قاعدة البيانات أو تعديلها الطريقة
· @CachePut
o الوظيفة:الكتابة في ذاكرة التخزين المؤقت(دون التأثير على تنفيذ الدالة).
o التطبيق:إضافة إلى قاعدة البيانات الطريقة
· @التخزين المؤقت
o الوظيفة: إعادة تجميع عمليات التخزين المؤقت المتعددة التي ستُطبَّق على الدالة
o التطبيق:استخدام التعليقات التوضيحية المذكورة أعلاه معًا


التعليقات

جارٍ الإرسال

اختر المظهر المفضل

يُطبّق المظهر على الموقع كله.