Spring Cache: من توصيل التخزين المؤقت في دقيقة واحدة إلى التكوين المرن لمدير التخزين المؤقت

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



في هذا المنشور ، سأحاول شرح التخزين المؤقت بهذه البساطة كما أفهمها الآن. ستتعرف على كيفية تنفيذ التخزين المؤقت في دقيقة واحدة ، وكيفية التخزين المؤقت حسب المفتاح ، وتعيين عمر ذاكرة التخزين المؤقت ، والعديد من الأشياء الأخرى التي تحتاج إلى معرفتها إذا تم إرشادك بتخزين شيء ما في مشروع عملك ، ولا ترغب في التشويش الوجه.

لماذا أقول "عهد"؟ نظرًا لأن التخزين المؤقت ، كقاعدة عامة ، من المنطقي التقديم في المشاريع الكبيرة المحملة بدرجة كبيرة ، مع عشرات الآلاف من الطلبات في الدقيقة. في مثل هذه المشروعات ، حتى لا يتم تحميل قاعدة البيانات بشكل زائد ، فإنهم عادة ما يقومون بتخزين مكالمات مستودع التخزين المؤقت. خاصة إذا كان من المعروف أنه يتم تحديث البيانات من بعض الأنظمة الرئيسية بتردد معين. نحن أنفسنا لا نكتب مثل هذه المشاريع ، نحن نعمل عليها. إذا كان المشروع صغيرًا ولا يهدد الأحمال الزائدة ، فمن الأفضل بالطبع عدم تخزين أي شيء - دائمًا ما تكون البيانات الجديدة دائمًا أفضل من البيانات التي يتم تحديثها بشكل دوري.

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

سننشئ مشروعًا سنحلل فيه جميع جوانب التخزين المؤقت التي وعدت بها. في النهاية ، كالعادة ، سيكون هناك رابط للمشروع نفسه.

0. إنشاء مشروع


سننشئ مشروعًا بسيطًا جدًا يمكننا من خلاله أخذ الكيان من قاعدة البيانات. أضفت لومبوك و Spring Cache و Spring Data JPA و H2 إلى المشروع. على الرغم من أنه لا يمكن الاستغناء عن ذاكرة التخزين المؤقت لـ Spring فقط.

plugins { id 'org.springframework.boot' version '2.1.7.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } group = 'ru.xpendence' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } 

سيكون لدينا كيان واحد فقط ، دعنا نسميها المستخدم.

 @Entity @Table(name = "users") @Data @NoArgsConstructor @ToString public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") private String name; @Column(name = "email") private String email; public User(String name, String email) { this.name = name; this.email = email; } } 

أضف المستودع والخدمة:

 public interface UserRepository extends JpaRepository<User, Long> { } @Slf4j @Service public class UserServiceImpl implements UserService { private final UserRepository repository; public UserServiceImpl(UserRepository repository) { this.repository = repository; } @Override public User create(User user) { return repository.save(user); } @Override public User get(Long id) { log.info("getting user by id: {}", id); return repository.findById(id) .orElseThrow(() -> new EntityNotFoundException("User not found by id " + id)); } } 

عندما ندخل طريقة خدمة get () ، نكتب عنها في السجل.

الاتصال بمشروع Spring Cache.

 @SpringBootApplication @EnableCaching // Spring Cache public class CacheApplication { public static void main(String[] args) { SpringApplication.run(CacheApplication.class, args); } } 

المشروع جاهز

1. التخزين المؤقت نتيجة العودة


ماذا الربيع مخبأ تفعل؟ Spring Cache يقوم ببساطة بتخزين نتيجة الإرجاع لمعلمات إدخال محددة. دعونا التحقق من ذلك. سنضع التعليق التوضيحيCacheable على طريقة خدمة get () للتخزين المؤقت للبيانات التي تم إرجاعها. نعطي هذا التعليق التوضيحي اسم "المستخدمين" (سنقوم بتحليل سبب ذلك بشكل منفصل).

  @Override @Cacheable("users") public User get(Long id) { log.info("getting user by id: {}", id); return repository.findById(id) .orElseThrow(() -> new EntityNotFoundException("User not found by id " + id)); } 

من أجل التحقق من كيفية عمل ذلك ، سنكتب اختبارًا بسيطًا.

 @RunWith(SpringRunner.class) @SpringBootTest public abstract class AbstractTest { } 

 @Slf4j public class UserServiceTest extends AbstractTest { @Autowired private UserService service; @Test public void get() { User user1 = service.create(new User("Vasya", "vasya@mail.ru")); User user2 = service.create(new User("Kolya", "kolya@mail.ru")); getAndPrint(user1.getId()); getAndPrint(user2.getId()); getAndPrint(user1.getId()); getAndPrint(user2.getId()); } private void getAndPrint(Long id) { log.info("user found: {}", service.get(id)); } } 

استطرادا صغيرا ، لماذا أكتب عادة AbstractTest وارث جميع الاختبارات منه.
إذا كان للفصل تعليق توضيحي خاص به علىSpringBootTest ، فسيتم إعادة رفع السياق لمثل هذه الفئة في كل مرة. نظرًا لأن السياق يمكن أن يرتفع لمدة 5 ثوانٍ ، أو ربما 40 ثانية ، فهذا في أي حال يمنع عملية الاختبار إلى حد كبير. في الوقت نفسه ، لا يوجد عادة اختلاف في السياق ، وعندما تقوم بتشغيل كل مجموعة من الاختبارات داخل نفس الفصل الدراسي ، ليست هناك حاجة لإعادة تشغيل السياق. إذا قمنا بوضع تعليق توضيحي واحد فقط ، على سبيل المثال ، على فصل تجريدي ، كما هو الحال في حالتنا ، فهذا يسمح لنا برفع السياق مرة واحدة فقط.

لذلك ، أفضل تقليل عدد السياقات التي أثيرت أثناء الاختبار / التجميع ، إذا أمكن ذلك.

ماذا اختبارنا تفعل؟ يقوم بإنشاء اثنين من المستخدمين ثم يسحبهم من قاعدة البيانات 2 مرات. كما نتذكر ، وضعنا التعليق التوضيحيCacheable ، والذي سيخزن مؤقتًا القيم المرتجعة. بعد تلقي الكائن من طريقة get () ، نخرج الكائن إلى السجل. أيضًا ، نقوم بتسجيل معلومات حول كل زيارة بواسطة التطبيق إلى طريقة get ().

قم بإجراء الاختبار. هذا هو ما نحصل عليه في وحدة التحكم.

 getting user by id: 1 user found: User(id=1, name=Vasya, email=vasya@mail.ru) getting user by id: 2 user found: User(id=2, name=Kolya, email=kolya@mail.ru) user found: User(id=1, name=Vasya, email=vasya@mail.ru) user found: User(id=2, name=Kolya, email=kolya@mail.ru) 

كما نرى ، أول مرتين ذهبنا فعلاً إلى طريقة get () وحصلنا فعليًا على المستخدم من قاعدة البيانات. في جميع الحالات الأخرى ، لم يكن هناك استدعاء حقيقي لهذه الطريقة ، أخذ التطبيق بيانات مخزنة مؤقتًا حسب المفتاح (في هذه الحالة ، هذا معرف).

2. التخزين المؤقت البيان الرئيسي


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

  @Override @Cacheable(value = "users", key = "#name") public User create(String name, String email) { log.info("creating user with parameters: {}, {}", name, email); return repository.save(new User(name, email)); } 

دعنا نكتب الاختبار المقابل:

  @Test public void create() { createAndPrint("Ivan", "ivan@mail.ru"); createAndPrint("Ivan", "ivan1122@mail.ru"); createAndPrint("Sergey", "ivan@mail.ru"); log.info("all entries are below:"); service.getAll().forEach(u -> log.info("{}", u.toString())); } private void createAndPrint(String name, String email) { log.info("created user: {}", service.create(name, email)); } 

سنحاول إنشاء ثلاثة مستخدمين ، يكون الاسمان متماثلين لهما

  createAndPrint("Ivan", "ivan@mail.ru"); createAndPrint("Ivan", "ivan1122@mail.ru"); 

واثنان منها سوف تتطابق مع البريد الإلكتروني

  createAndPrint("Ivan", "ivan@mail.ru"); createAndPrint("Sergey", "ivan@mail.ru"); 

في طريقة الإنشاء ، نسجل كل حقيقة تسمى الطريقة ، وأيضًا ، سنقوم بتسجيل جميع الكيانات التي أرجعت هذه الطريقة إلينا. ستكون النتيجة مثل هذا:

 creating user with parameters: Ivan, ivan@mail.ru created user: User(id=1, name=Ivan, email=ivan@mail.ru) created user: User(id=1, name=Ivan, email=ivan@mail.ru) creating user with parameters: Sergey, ivan@mail.ru created user: User(id=2, name=Sergey, email=ivan@mail.ru) all entries are below: User(id=1, name=Ivan, email=ivan@mail.ru) User(id=2, name=Sergey, email=ivan@mail.ru) 

نرى أنه في الواقع ، أطلق التطبيق على الطريقة 3 مرات ، ودخلها مرتين فقط. بمجرد أن يطابق المفتاح طريقة ما ، ويعيد ببساطة قيمة مخزنة مؤقتًا.

3. التخزين المؤقت القسري. CachePut


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

أضف طريقتين نحفظ بهما المستخدم. سنقوم بتمييز أحدهم بالتعليقات التوضيحية المعتادة @ Cacheable ، والثاني باستخدام CachePut.

  @Override @Cacheable(value = "users", key = "#user.name") public User createOrReturnCached(User user) { log.info("creating user: {}", user); return repository.save(user); } @Override @CachePut(value = "users", key = "#user.name") public User createAndRefreshCache(User user) { log.info("creating user: {}", user); return repository.save(user); } 

الطريقة الأولى ستُرجع ببساطة القيم المخزنة مؤقتًا ، بينما ستفرض الطريقة الثانية تحديث ذاكرة التخزين المؤقت. سيتم تنفيذ التخزين المؤقت باستخدام اسم المستخدم #. سنكتب الاختبار المقابل.

  @Test public void createAndRefresh() { User user1 = service.createOrReturnCached(new User("Vasya", "vasya@mail.ru")); log.info("created user1: {}", user1); User user2 = service.createOrReturnCached(new User("Vasya", "misha@mail.ru")); log.info("created user2: {}", user2); User user3 = service.createAndRefreshCache(new User("Vasya", "kolya@mail.ru")); log.info("created user3: {}", user3); User user4 = service.createOrReturnCached(new User("Vasya", "petya@mail.ru")); log.info("created user4: {}", user4); } 

وفقًا للمنطق الذي تم وصفه بالفعل ، في المرة الأولى التي يتم فيها حفظ مستخدم باسم "Vasya" من خلال طريقة createOrReturnCached () ، سنتلقى بعد ذلك كيانًا مخبأ ، ولن يدخل التطبيق في الطريقة نفسها. إذا اتصلنا بالطريقة createAndRefreshCache () ، فسيتم الكتابة فوق الكيان المخزن مؤقتًا للمفتاح المسمى "Vasya" في ذاكرة التخزين المؤقت. لنقم بإجراء الاختبار ونرى ما سيتم عرضه في وحدة التحكم.

 creating user: User(id=null, name=Vasya, email=vasya@mail.ru) created user1: User(id=1, name=Vasya, email=vasya@mail.ru) created user2: User(id=1, name=Vasya, email=vasya@mail.ru) creating user: User(id=null, name=Vasya, email=kolya@mail.ru) created user3: User(id=2, name=Vasya, email=kolya@mail.ru) created user4: User(id=2, name=Vasya, email=kolya@mail.ru) 

نرى أن user1 قد كتب بنجاح إلى قاعدة البيانات وذاكرة التخزين المؤقت. عندما نحاول تسجيل المستخدم بنفس الاسم مرة أخرى ، نحصل على النتيجة المخبأة للنداء الأول (user2 ، والذي يكون المعرف هو نفسه user1 ، والذي يخبرنا أن المستخدم لم يكتب ، وهذا مجرد ذاكرة تخزين مؤقت). بعد ذلك ، نكتب المستخدم الثالث من خلال الطريقة الثانية ، والتي ، حتى مع النتيجة المخبأة ، ما زالت تسمى الطريقة وكتبت نتيجة جديدة إلى ذاكرة التخزين المؤقت. هذا هو user3. كما يمكننا أن نرى ، لديه بالفعل معرف جديد. بعد ذلك ، نسمي الطريقة الأولى ، والتي تأخذ ذاكرة التخزين المؤقت الجديدة المضافة بواسطة user3.

4. إزالة من ذاكرة التخزين المؤقت. CacheEvict


في بعض الأحيان يصبح من الضروري تحديث بعض البيانات في ذاكرة التخزين المؤقت. على سبيل المثال ، تم بالفعل حذف كيان من قاعدة البيانات ، لكن لا يزال من الممكن الوصول إليه من ذاكرة التخزين المؤقت. للحفاظ على تناسق البيانات ، نحتاج على الأقل إلى عدم تخزين البيانات المحذوفة في ذاكرة التخزين المؤقت.

أضف عدة طرق للخدمة.

  @Override public void delete(Long id) { log.info("deleting user by id: {}", id); repository.deleteById(id); } @Override @CacheEvict("users") public void deleteAndEvict(Long id) { log.info("deleting user by id: {}", id); repository.deleteById(id); } 

الأول سيؤدي ببساطة إلى حذف المستخدم ، والثاني سيحذفه أيضًا ، لكننا سنضع علامة عليه مع التعليق التوضيحيCacheEvict. أضف اختبارًا سيؤدي إلى إنشاء مستخدمين ، وبعد ذلك سيتم حذف واحد من خلال طريقة بسيطة ، والثاني من خلال طريقة مشروحة. بعد ذلك ، سوف نحصل على هؤلاء المستخدمين من خلال طريقة get ().

  @Test public void delete() { User user1 = service.create(new User("Vasya", "vasya@mail.ru")); log.info("{}", service.get(user1.getId())); User user2 = service.create(new User("Vasya", "vasya@mail.ru")); log.info("{}", service.get(user2.getId())); service.delete(user1.getId()); service.deleteAndEvict(user2.getId()); log.info("{}", service.get(user1.getId())); log.info("{}", service.get(user2.getId())); } 

من المنطقي أنه نظرًا لأن المستخدم قد تم تخزينه مؤقتًا بالفعل ، فإن الإزالة لن تمنعنا من الحصول عليها ، لأنها مخبأة. دعنا نرى السجلات.

 getting user by id: 1 User(id=1, name=Vasya, email=vasya@mail.ru) getting user by id: 2 User(id=2, name=Vasya, email=vasya@mail.ru) deleting user by id: 1 deleting user by id: 2 User(id=1, name=Vasya, email=vasya@mail.ru) getting user by id: 2 javax.persistence.EntityNotFoundException: User not found by id 2 

نرى أن التطبيق ذهب بأمان في كلتا الحالتين إلى طريقة get () و Spring مؤقتًا لهذه الكيانات. بعد ذلك ، قمنا بحذفها من خلال طرق مختلفة. لقد حذفنا أول واحد بالطريقة المعتادة ، وظلت القيمة المخزنة مؤقتًا ، لذلك عندما نجحنا في جعل المستخدم تحت المعرف 1 ، نجحنا. عندما حاولنا الحصول على مستخدم 2 ، أرجعت الطريقة EntityNotFoundException - لم يكن هناك مستخدم في ذاكرة التخزين المؤقت.

5. إعدادات التجميع. Caching


في بعض الأحيان يتطلب أسلوب واحد عدة إعدادات التخزين المؤقت. يستخدم التعليق التوضيحيCaching لهذه الأغراض. قد يبدو شيء مثل هذا:

  @Caching( cacheable = { @Cacheable("users"), @Cacheable("contacts") }, put = { @CachePut("tables"), @CachePut("chairs"), @CachePut(value = "meals", key = "#user.email") }, evict = { @CacheEvict(value = "services", key = "#user.name") } ) void cacheExample(User user) { } 

هذه هي الطريقة الوحيدة لتجميع التعليقات التوضيحية. إذا حاولت تراكم شيء من هذا القبيل

  @CacheEvict("users") @CacheEvict("meals") @CacheEvict("contacts") @CacheEvict("tables") void cacheExample(User user) { } 

ثم سوف تخبرك IDEA أن هذا ليس هو الحال.

6. التكوين مرنة. CacheManager


أخيرًا ، اكتشفنا ذاكرة التخزين المؤقت ، ولم يعد أمرًا غير مفهوم ومخيف بالنسبة لنا. الآن دعونا ننظر تحت الغطاء ونرى كيف يمكننا تكوين التخزين المؤقت بشكل عام.

لمثل هذه المهام ، هناك CacheManager. إنه موجود أينما كان Spring Cache. عندما أضفنا تعليقًا توضيحيًاEnableCache ، سيتم إنشاء مدير ذاكرة التخزين المؤقت تلقائيًا بحلول Spring. يمكننا التحقق من ذلك إذا قمنا بالالتفاف التلقائي على ApplicationContext وفتحه عند نقطة الإيقاف. بين صناديق أخرى ، سيكون هناك cacheManager الفول.



أوقفت التطبيق في المرحلة عندما تم بالفعل إنشاء مستخدمين ووضعهما في ذاكرة التخزين المؤقت. إذا اتصلنا بالفاصوليا التي نحتاجها من خلال تقييم التعبير ، فسنرى أن هناك بالفعل مثل هذه الحبة ، فهي تحتوي على ConcurentMapCache مع مفتاح "المستخدمين" وقيمة ConcurrentHashMap ، التي تحتوي بالفعل على مستخدمين تم تخزينهم مؤقتًا.



يمكننا ، بدوره ، إنشاء مدير ذاكرة التخزين المؤقت لدينا ، مع Habr والمبرمجين ، ثم صقله حسب ذوقنا.

  @Bean("habrCacheManager") public CacheManager cacheManager() { return null; } 

يبقى فقط اختيار مدير ذاكرة التخزين المؤقت الذي سنستخدمه ، لأن هناك الكثير منهم. لن أدرج جميع مديري ذاكرة التخزين المؤقت ، سيكون كافياً أن نعرف أن هناك مثل:

  • SimpleCacheManager هو أبسط مدير ذاكرة التخزين المؤقت ، مريحة للتعلم والاختبار.
  • ConcurrentMapCacheManager - Lazily تهيئة المثيلات التي تم إرجاعها لكل طلب. يوصى أيضًا باختبار وتعلم كيفية العمل مع ذاكرة التخزين المؤقت ، وكذلك لبعض الإجراءات البسيطة مثل إجراءاتنا. للعمل الجاد مع ذاكرة التخزين المؤقت ، يوصى بالتطبيق أدناه.
  • JCacheCacheManager و EhCacheCacheManager و CaffeineCacheManager هم مديرو ذاكرة التخزين المؤقت الجادون القائمون على الشركاء والتي يمكن تخصيصها بشكل مرن وتؤدي مهام مجموعة واسعة جدًا من الإجراءات.

كجزء من منشوري المتواضع ، لن أصف مديري ذاكرة التخزين المؤقت للثلاثة الأخيرة. بدلاً من ذلك ، سننظر في العديد من جوانب إعداد مدير ذاكرة التخزين المؤقت باستخدام ConcurrentMapCacheManager كمثال.

لذلك ، دعونا إعادة إنشاء مدير التخزين المؤقت لدينا.

  @Bean("habrCacheManager") public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); } 

مدير التخزين المؤقت جاهز.

7. إعداد ذاكرة التخزين المؤقت. وقت الحياة ، الحد الأقصى لحجم وهلم جرا.


للقيام بذلك ، نحتاج إلى مكتبة Google Guava ذات شعبية كبيرة. أخذت آخر واحد.

 compile group: 'com.google.guava', name: 'guava', version: '28.1-jre' 

عند إنشاء مدير ذاكرة التخزين المؤقت ، نعيد تعريف طريقة createConcurrentMapCache ، والتي سوف ندعو فيها CacheBuilder من جوافة. في هذه العملية ، سيُطلب منا تكوين مدير ذاكرة التخزين المؤقت عن طريق تهيئة الطرق التالية:

  • maximumSize - الحد الأقصى لحجم القيم التي يمكن أن تحتويها ذاكرة التخزين المؤقت. باستخدام هذه المعلمة ، يمكنك العثور على محاولة للعثور على حل وسط بين التحميل على قاعدة البيانات وذاكرة الوصول العشوائي JVM.
  • refreshAfterWrite - الوقت بعد كتابة القيمة إلى ذاكرة التخزين المؤقت ، وبعد ذلك سيتم تحديثها تلقائيًا.
  • expireAfterAccess - عمر القيمة بعد آخر مكالمة لها.
  • expireAfterWrite - عمر القيمة بعد الكتابة إلى ذاكرة التخزين المؤقت. هذه هي المعلمة التي سنحددها.

وغيرها.

نحدد في مدير عمر السجل. حتى لا تنتظر طويلا ، تعيين 1 ثانية.

  @Bean("habrCacheManager") public CacheManager cacheManager() { return new ConcurrentMapCacheManager() { @Override protected Cache createConcurrentMapCache(String name) { return new ConcurrentMapCache( name, CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .build().asMap(), false); } }; } 

نكتب اختبار المقابلة لهذه الحالة.

  @Test public void checkSettings() throws InterruptedException { User user1 = service.createOrReturnCached(new User("Vasya", "vasya@mail.ru")); log.info("{}", service.get(user1.getId())); User user2 = service.createOrReturnCached(new User("Vasya", "vasya@mail.ru")); log.info("{}", service.get(user2.getId())); Thread.sleep(1000L); User user3 = service.createOrReturnCached(new User("Vasya", "vasya@mail.ru")); log.info("{}", service.get(user3.getId())); } 

نحفظ عدة قيم في قاعدة البيانات ، وإذا كانت البيانات مخزنة مؤقتًا ، فإننا لا نحفظ أي شيء. أولاً ، نحفظ قيمتين ، ثم ننتظر ثانية واحدة حتى نفقد ذاكرة التخزين المؤقت ، وبعد ذلك نحفظ قيمة أخرى.

 creating user: User(id=null, name=Vasya, email=vasya@mail.ru) getting user by id: 1 User(id=1, name=Vasya, email=vasya@mail.ru) User(id=1, name=Vasya, email=vasya@mail.ru) creating user: User(id=null, name=Vasya, email=vasya@mail.ru) getting user by id: 2 User(id=2, name=Vasya, email=vasya@mail.ru) 

توضح السجلات أننا أنشأنا مستخدمًا لأول مرة ، ثم جربنا مستخدمًا آخر ، ولكن نظرًا لأن البيانات تم تخزينها مؤقتًا ، فقد حصلنا عليها من ذاكرة التخزين المؤقت (في كلتا الحالتين ، عند الحفظ وعند الحصول على قاعدة البيانات). ثم حدث سوء في ذاكرة التخزين المؤقت ، حيث يخبرنا السجل عن الحفظ الفعلي والاستلام الفعلي للمستخدم.

8. لتلخيص


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

جيثب من المشروع هنا: https://github.com/promoscow/cache

Source: https://habr.com/ru/post/ar465667/


All Articles