ข้ามไปยังเนื้อหาหลัก

ค้นหาใน 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 เพื่อให้สลับใช้ 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

หากไม่มีในแคช:ค้นฐานข้อมูล เก็บลงแคช ส่งคืนผลลัพธ์,
หากมีอยู่ในแคช:คืนผลลัพธ์ทันที

พารามิเตอร์ คำอธิบาย ตัวอย่าง
value ชื่อของแคช กำหนดไว้ในไฟล์การตั้งค่า spring ต้องระบุอย่างน้อยหนึ่งชื่อ ตัวอย่างเช่น:
@Cacheable(value=”mycache”)
@Cacheable(value={”cache1”,”cache2”}
คีย์ key ของแคช เว้นว่างได้ หากระบุต้องเขียนตาม นิพจน์ SpEL หากไม่ระบุ โดยปริยายจะนำพารามิเตอร์ทั้งหมดของเมธอดมาประกอบกัน @Cacheable(value=”testcache”,key=”#userName”)
เงื่อนไข เงื่อนไขในการแคช สามารถเว้นว่างได้ เขียนด้วย 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 จะลบรายการทั้งหมดเป็นชุดได้

พารามิเตอร์ คำอธิบาย ตัวอย่าง
value ชื่อของแคช กำหนดไว้ในไฟล์การตั้งค่า spring ต้องระบุอย่างน้อยหนึ่งชื่อ @CacheEvict(value=”my cache”)
คีย์ key ของแคช เว้นว่างได้ หากระบุต้องเขียนตาม นิพจน์ SpEL หากไม่ระบุ โดยปริยายจะนำพารามิเตอร์ทั้งหมดของเมธอดมาประกอบกัน @CacheEvict(value=”testcache”,key=”#userName”)
เงื่อนไข เงื่อนไขในการแคช สามารถเว้นว่างได้ เขียนด้วย 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:เมื่อต้องการอัปเดตแคชโดยไม่รบกวนการทำงานของเมธอด สามารถใช้แอนโนเทชันนี้ได้ กล่าวคือ เมธอดจะทำงานเสมอ และนำผลลัพธ์ไปใส่ในแคช;

พารามิเตอร์ คำอธิบาย ตัวอย่าง
value ชื่อของแคช กำหนดไว้ในไฟล์การตั้งค่า spring ต้องระบุอย่างน้อยหนึ่งชื่อ @CachePut(value=”my cache”)
คีย์ key ของแคช เว้นว่างได้ หากระบุต้องเขียนตาม นิพจน์ SpEL หากไม่ระบุ โดยปริยายจะนำพารามิเตอร์ทั้งหมดของเมธอดมาประกอบกัน @CachePut(value=”testcache”,key=”#userName”)
เงื่อนไข เงื่อนไขในการแคช สามารถเว้นว่างได้ เขียนด้วย 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 เป็น แอนโนเทชันระดับคลาสที่ช่วยให้ใช้ชื่อแคชร่วมกันได้ หากคุณเขียนชื่ออื่นไว้ในเมธอด ก็จะยังยึดชื่อที่ระบุในเมธอดเป็นหลัก。

@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
เมธอด ออบเจ็กต์ 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
result บริบทการทำงาน ค่าที่คืนหลังเมธอดทำงาน(มีผลเฉพาะเงื่อนไขที่ประเมินหลังเมธอดทำงาน เช่น ‘unless' และ beforeInvocation=false ของ 'cache evict') 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)

สรุปย่อ

สำหรับการประกาศแคช ระบบแคชของ spring มีแอนโนเทชัน java ชุดหนึ่งให้ใช้:
· @Cacheable
o หน้าที่:กระตุ้นการเขียนแคช หากไม่มีข้อมูลในแคช ให้ค้นฐานข้อมูล บันทึกลงแคช แล้วส่งคืนผลลัพธ์ หากมีข้อมูลในแคช ให้ส่งคืนผลลัพธ์โดยตรง
o การประยุกต์ใช้:สอบถามฐานข้อมูลเมธอด
· @CacheEvict
o หน้าที่:กระตุ้นการล้างแคช
o การประยุกต์ใช้:ลบหรือแก้ไขฐานข้อมูลเมธอด
· @CachePut
o หน้าที่:เขียนข้อมูลลงแคช(ไม่ส่งผลต่อการทำงานของเมธอด)。
o การประยุกต์ใช้:เพิ่มลงในฐานข้อมูลเมธอด
· @การแคช
o หน้าที่:จัดรวมการดำเนินการกับแคชหลายอย่างที่จะนำไปใช้กับเมธอดใหม่
o การประยุกต์ใช้:การใช้แอนโนเทชันข้างต้นร่วมกัน


ความคิดเห็น

กำลังส่ง

บทความยอดนิยม

เลือกธีมที่ชอบ

ธีมที่เลือกจะใช้ทั่วทั้งเว็บไซต์