خلفية
لقد جعلها Springboot واحدة من أكثر أطر عمل تطوير الويب Java السائدة اليوم لأنها توفر مجموعة متنوعة من الإضافات خارج الصندوق. MyBatis هو إطار ORM خفيف الوزن للغاية وسهل الاستخدام. Redis هي قاعدة بيانات قيمة مفتاحية موزعة للغاية اليوم. في تطوير الويب ، غالبًا ما نستخدمه لنتائج استعلام قاعدة بيانات Cache.
ستقدم هذه المدونة كيفية إنشاء تطبيق ويب بسرعة باستخدام SpringBoot واستخدام MyBatis كإطار ORM الخاص بنا. لتحسين الأداء ، نستخدم Redis باعتباره ذاكرة التخزين المؤقت من المستوى الثاني لـ MyBatis. لاختبار الكود الخاص بنا ، كتبنا اختبارات الوحدة واستخدمنا قاعدة بيانات H2 داخل الذاكرة لإنشاء بيانات الاختبار الخاصة بنا. من خلال هذا المشروع ، نأمل أن يتمكن القراء من إتقان المهارات وأفضل ممارسات تطوير الويب الحديثة.
يمكن تنزيل نموذج رمز هذه المقالة في Github: https://github.com/lovelcp/spring-boot-mybatis-with-redis/tree/master
بيئة
بيئة التنمية: Mac 10.11
IDE: Intellij 2017.1
JDK: 1.8
الربيع-بوت: 1.5.3.release
ريديس: 3.2.9
MySQL: 5.7
الربيع
إنشاء مشروع جديد
أولاً ، نحتاج إلى تهيئة مشروع Boot Spring. من خلال Entlemizer من Intellij Spring ، من السهل جدًا إنشاء مشروع جديد للبوت الربيع. أولاً ، نختار مشروعًا جديدًا في Intellij:
ثم في الواجهة لتحديد التبعيات ، تحقق من الويب ، mybatis ، treeis ، mysql ، h2:
بعد نجاح المشروع الجديد ، يمكننا أن نرى الهيكل الأولي للمشروع كما هو موضح في الشكل أدناه:
ساعدنا مُهيئ Spring تلقائيًا في إنشاء فئة بدء التشغيل - SpringBootMyBatisWithRedIsApplication. رمز هذا الفصل بسيط للغاية:
springbootapplicationpublicpublicpublic springbootmybatiSwithRedisApplication {public static void main (string [] args) {springapplication.run (SpringBootMyBatiSwithRedIsapplication.class ، args) ؛ }}التعليق التوضيحي springbootapplication يعني تمكين ميزة التكوين التلقائي من SPRING BOOT. حسنًا ، تم بناء هيكلنا العظمي بنجاح حتى يتمكن القراء المهتمين من بدء النتائج من خلال Intellij.
إنشاء واجهة API جديدة
بعد ذلك ، سنكتب واجهة برمجة تطبيقات الويب. لنفترض أن هندسة الويب الخاصة بنا هي المسؤولة عن التعامل مع منتجات التاجر (المنتج). نحتاج إلى توفير واجهة Get التي تُرجع معلومات المنتج بناءً على معرف المنتج وواجهة PUT التي تقوم بتحديث معلومات المنتج. أولاً نحدد فئة المنتج ، والتي تتضمن معرف المنتج واسم المنتج والسعر:
يقوم منتج الطبقة العامة بتنفيذ Serializable {Private Static Final Long SerialVersionuid = 1435515995276255188L ؛ معرف طويل خاص ؛ اسم السلسلة الخاصة ؛ سعر طويل خاص ؛ // Getters Setters}ثم نحتاج إلى تحديد فئة وحدة التحكم. نظرًا لأن Spring Boot يستخدم Spring MVC كمكون الويب الخاص به داخليًا ، فيمكننا تطوير فئة الواجهة الخاصة بنا بسرعة من خلال التعليقات التوضيحية:
@restController @requestMapping ("/production) PROCORAL ProductController {getMapping ("/{id} ") المنتج العام getProductInfo (pathvariable (" id ") long productid) {// todo return null ؛ } putmapping ("/{id}") Public Product UpdateProductInfo (pathvariable ("id") long productid ، @REQUESTBODY NewProduct) {// todo return null ؛ }}دعونا نقدم بإيجاز وظائف التعليقات التوضيحية المستخدمة في الكود أعلاه:
RestController: يعني أن الفصل عبارة عن وحدة تحكم ويوفر واجهة راحة ، أي أن قيم جميع الواجهات يتم إرجاعها بتنسيق JSON. هذا التعليق التوضيحي هو في الواقع شرح توضيحي لـ controller و @ResponseBody ، مما يسهلنا إلى تطوير API REST.
requestmapping ، getMapping ، putMapping: يمثل عنوان URL للواجهة. يعني شرح requestmapping المشروح على الفصل أن عناوين URL لجميع الواجهات تحت الفصل تبدأ /المنتج. GetMapping يعني أن هذه واجهة GET HTTP ، يعني PUPPAPPING أن هذه واجهة HTTP PUT.
@pathvariable ، @requestbody: يمثل علاقة رسم الخرائط للمعلمات. على افتراض أن طلب الحصول على طلب /منتج /123 ، ستتم معالجة الطلب بواسطة طريقة getProductInfo ، حيث سيتم تعيين 123 في عنوان URL في المنتج. وبالمثل ، إذا كان طلبًا متوقعًا ، فسيتم تعيين الهيئة المطلوبة في كائن المنتجات الجديدة.
نحن هنا نحدد الواجهة فقط ، ولم يتم إكمال منطق المعالجة الفعلي بعد ، لأن معلومات المنتج موجودة في قاعدة البيانات. بعد ذلك سنقوم بدمج MyBatis في المشروع ونتفاعل مع قاعدة البيانات.
تكامل MyBatis
تكوين مصدر البيانات
أولاً ، نحتاج إلى تكوين مصدر البيانات لدينا في ملف التكوين. نستخدم MySQL كقاعدة بياناتنا. هنا نستخدم YAML كتنسيق ملف التكوين الخاص بنا. نقوم بإنشاء ملف application.yml جديد في دليل الموارد:
Spring:# Database Configuration DataSource: url: jdbc: mysql: // {your_host}/{your_db} اسم المستخدم: {your_username} كلمة المرور: {your_password} اسم برنامج التشغيلنظرًا لأن Spring Boot يحتوي على ميزة التكوين التلقائي ، لا نحتاج إلى إنشاء فئة تكوين بيانات بيانات جديدة. سيقوم SPRING BOOT تلقائيًا بتحميل ملف التكوين وإنشاء تجمع اتصال قاعدة بيانات بناءً على معلومات ملف التكوين ، وهو مريح للغاية.
يوصي المؤلف باستخدام YAML كتنسيق ملف التكوين. XML تبدو طويلة والخصائص ليس لها بنية هرمية. Yaml يعوض فقط أوجه القصور من كليهما. هذا هو السبب أيضًا في دعم SPRING Boot تنسيق YAML بشكل افتراضي.
تكوين MyBatis
لقد قدمنا مكتبة MyBatis-Spring-Boot-Starte في POM.xml من خلال مُهيئ الربيع ، والتي ستساعدنا تلقائيًا في تهيئة MyBatis. أولاً ، نملأ التكوين ذي الصلة لـ MyBatis في application.yml:
# myBatis تكوين myBatis: # قم بتكوين اسم الحزمة حيث توجد فئة التعيين من النوع الحزم: com.wooyoo.learning.dao.domain # تكوين المسار الذي يوجد فيه ملف Mapper XML ، وهنا هناك خريطة صافية:-mappers/productmapper.xml
ثم ، حدد فئة ProductMapper في الكود:
@mapperpublic interface productMapper {product select (param ("id") معرف طويل) ؛ تحديث باطل (منتج المنتج) ؛}هنا ، طالما أننا نضيف شرح @mapper ، سيقوم Spring Boot تلقائيًا بتحميل فئة Mapper عند تهيئة MyBatis.
السبب الأكبر الذي يجعل SPRING BOOT شائعًا جدًا هو ميزة التكوين التلقائي. يحتاج المطورون فقط إلى الانتباه إلى تكوين المكونات (مثل معلومات اتصال قاعدة البيانات) دون الاهتمام بكيفية تهيئة المكونات الفردية ، مما يسمح لنا بالتركيز على تنفيذ الأعمال وتبسيط عملية التطوير.
الوصول إلى قاعدة البيانات
بعد الانتهاء من تكوين MyBatis ، يمكننا الوصول إلى قاعدة البيانات في واجهتنا. نقدم فئة Mapper من خلال Autowired ضمن ProductController واتصل بالطريقة المقابلة لتنفيذ عمليات الاستعلام وتحديث المنتجات. هنا نأخذ واجهة الاستعلام كمثال:
@restController @requestMapping ("/production) PROMORT CLASSCONTROLLER {AUTOWIRED PRODUCTMAPPER PRODUCTMAPPER ؛ getMapping ("/{id}") المنتج العام getProductInfo (pathvariable ("id") long productId) {return productMapper.select (productId) ؛ } // تجنب فترة طويلة جدًا وحذف رمز UpdateProductInfo}ثم أدخل بعض معلومات المنتج في MySQL الخاص بك ويمكنك تشغيل المشروع لمعرفة ما إذا كان الاستعلام ناجحًا.
حتى الآن ، نجحنا في دمج MyBatis في مشروعنا ، مع إضافة القدرة على التفاعل مع قاعدة البيانات. لكن هذا لا يكفي. سيؤدي مشروع الويب الحديث بالتأكيد إلى تسريع استعلام قاعدة البيانات الخاص بنا على ذاكرة التخزين المؤقت. بعد ذلك ، سنقدم كيفية دمج Redis علمياً في ذاكرة التخزين المؤقت الثانوية لـ MyBatis لتحقيق ذاكرة التخزين المؤقت التلقائية لاستفسارات قاعدة البيانات.
redis متكاملة
تكوين redis
تمامًا مثل الوصول إلى قاعدة بيانات ، نحتاج إلى تكوين معلومات اتصال redis. أضف التكوين التالي إلى ملف application.yml:
الربيع: redis: # redis database index (الافتراضي هو 0). نحن نستخدم قاعدة بيانات مع الفهرس 3 لتجنب التعارض مع قاعدة بيانات قواعد البيانات الأخرى: 3 # عنوان خادم Redis (افتراضي هو مضيف محلي) المضيف: LocalHost # Port (الافتراضي هو 6379) المنفذ: 6379 # إعادة الوصول الأرقام تمثل الأرقام غير محدودة) Max-Active: 8 # الحد الأقصى لعدد اتصالات الخمول (الافتراضي هو 8 ، تمثل الأرقام السلبية غير محدودة) Max-Idle: 8 # الحد الأدنى لعدد اتصالات الخمول (الافتراضي هو 0 ، هذه القيمة فعالة فقط) min-idle: 0
جميعها المذكورة أعلاه هي تكوينات شائعة الاستخدام ، ويمكن للقراء فهم الدور المحدد لكل عنصر تكوين من خلال معلومات التعليق. نظرًا لأننا قدمنا مكتبة Dring-Boot-Starter-Data-Redis في pom.xml ، فإن SPRIN SPRIN
org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration. من خلال فئة التكوين هذه ، يمكننا أن نجد أن الطبقة الأساسية تستخدم مكتبة Jedis افتراضيًا ، وتوفر Redistemplate و StringTemplate خارج المربع.
استخدم Redis كذاكرة التخزين المؤقت من المستوى 2
لن يتم وصف مبدأ التخزين المؤقت الثانوي لـ MyBatis في هذه المقالة. يحتاج القراء فقط إلى معرفة أن التخزين المؤقت الثانوي لـ MyBatis يمكنه تلقائيًا لتخزين استفسارات قاعدة البيانات ، ويمكنه تحديث ذاكرة التخزين المؤقت تلقائيًا عند تحديث البيانات.
إن تنفيذ التخزين المؤقت الثانوي لـ MyBatis أمر بسيط للغاية. تحتاج فقط إلى إنشاء فئة جديدة لتنفيذ واجهة org.apache.ibatis.cache.cache.
هناك خمس طرق لهذه الواجهة:
String getId (): معرف كائن عملية ذاكرة التخزين المؤقت MyBatis. يتوافق Mapper مع كائن عملية ذاكرة التخزين المؤقت MyBatis.
void putObject (مفتاح الكائن ، قيمة الكائن): قم بإلغاء نتائج الاستعلام في ذاكرة التخزين المؤقت.
GetObject (مفتاح الكائن): احصل على نتيجة الاستعلام المخزنة مؤقتًا من ذاكرة التخزين المؤقت.
الكائن removeObject (مفتاح الكائن): قم بإزالة المفتاح المقابل والقيمة من ذاكرة التخزين المؤقت. أطلق النار فقط عند التدحرج. بشكل عام ، لا نحتاج إلى تنفيذها. لطرق الاستخدام المحددة ، يرجى الرجوع إلى: org.apache.ibatis.cache.decorators.transactionalcache.
void clear (): مسح ذاكرة التخزين المؤقت عند حدوث تحديث.
int getSize (): التنفيذ الاختياري. إرجاع عدد ذاكرة التخزين المؤقت.
ReadWritelock getReadWritelock (): التنفيذ الاختياري. تستخدم لتنفيذ عمليات ذاكرة التخزين المؤقت الذرية.
بعد ذلك ، نقوم بإنشاء فئة Rediscache جديدة لتنفيذ واجهة ذاكرة التخزين المؤقت:
يقوم Rediscache Public بتنفيذ ذاكرة التخزين المؤقت {private static final logger = loggerfactory.getLogger (rediscache.class) ؛ readWritelock readwritelock = جديد reentrantreadwritelock () ؛ معرف السلسلة النهائي الخاص ؛ // cache مثيل معرف redistemplate private redistemplate ؛ Private Static Final Expire_time_in_minutes = 30 ؛ . } this.id = id ؛ } Override public string getId () {return id ؛ } / ** * وضع نتيجة الاستعلام إلى redis * * param مفتاح * param value * / OverrideSuppressWarnings ("Unchected") public void putObject (مفتاح الكائن ، قيمة الكائن) {redistemplate redistemplate = getRedIstemplate () ؛ valuesOperations opsforvalue = redistemplate.opsforvalue () ؛ opsforvalue.set (المفتاح ، القيمة ، expire_time_in_minutes ، timeUnit.minutes) ؛ logger.debug ("وضع نتيجة الاستعلام إلى redis") ؛ } / ** * الحصول على نتيجة الاستعلام المخزنة من redis * * param مفتاح * return * / override الكائن العام getObject (مفتاح الكائن) {redistemplate redistemplate = getRedIstemplate () ؛ valuesOperations opsforvalue = redistemplate.opsforvalue () ؛ logger.debug ("الحصول على نتيجة الاستعلام المخزنة من redis") ؛ إرجاع opsforvalue.get (مفتاح) ؛ } / ** * إزالة نتيجة الاستعلام المخزنة من redis * * param مفتاح * @return * / OverrideSuppressWarnings ("Unchected") الكائن العام removeObject (مفتاح الكائن) {redistemplate redistemplate = getRedIstemplate () ؛ redistemplate.delete (مفتاح) ؛ logger.debug ("إزالة الاستعلام المخزنة من redis") ؛ العودة لاغية. } / ** * مسح مثيل ذاكرة التخزين المؤقت هذا * / Override public void clear () {redistemplate redistemplate = getRedIstemplate () ؛ redistemplate.execute ((rediscallback) connection -> {connection.flushdb () ؛ return null ؛}) ؛ logger.debug ("قم بمسح جميع نتائج الاستعلام المخزنة مؤقتًا من redis") ؛ } Override public int getSize () {return 0 ؛ } Override public readWritelock getReadWritelock () {return readWritelock ؛ } redistemplate private getRediStemplate () {if (redistemplate == null) {rediStemplate = applicationContextholder.getBean ("redistemplate") ؛ } إعادة redistemplate ؛ }}اسمحوا لي أن أشرح بعض النقاط الرئيسية في الكود أعلاه:
يجب أن يكون لدى ذاكرة التخزين المؤقت من المستوى الثاني الذي تنفذه مُنشئًا يحتوي على معرف ، وإلا سيتم الإبلاغ عن خطأ.
نستخدم Redistemplate المغلف الربيع لتشغيل redis. جميع المقالات عبر الإنترنت التي تقدم Redis إلى ذاكرة التخزين المؤقت الثانوية للمستوى 2 تستخدم مكتبة Jedis مباشرة ، لكن المؤلف يعتقد أن هذا لا يكفي أسلوب الربيع. علاوة على ذلك ، فإن redistemplate يلف التنفيذ الأساسي. إذا لم نستخدم Jedis في المستقبل ، فيمكننا استبدال المكتبة الأساسية مباشرة دون تعديل الكود العلوي. ما هو أكثر ملاءمة هو استخدام Redistemplate ، لا يتعين علينا الاهتمام بإصدار اتصالات redis ، وإلا سيكون من السهل على المبتدئين أن ينسى إصدار الاتصال والتسبب في تعثر التطبيق.
تجدر الإشارة إلى أنه لا يمكن الرجوع إلى Redistemplate من خلال AutoWire ، لأن Rediscache ليس حبة في حاوية الربيع. لذلك نحن بحاجة إلى استدعاء طريقة getBean يدويًا للحاوية للحصول على هذه الفول. لطرق التنفيذ المحددة ، يرجى الرجوع إلى الرمز في Github.
طريقة التسلسل Redis التي نستخدمها هي التسلسل الافتراضي JDK. لذلك ، يحتاج كائن استعلام قاعدة البيانات (مثل فئة المنتج) إلى تنفيذ الواجهة القابلة للتسلسل.
وبهذه الطريقة ، نقوم بتنفيذ فئة ذاكرة التخزين المؤقت الأنيقة والعلمية و Redis بأسلوب الربيع.
قم بتشغيل ذاكرة التخزين المؤقت من المستوى 2
بعد ذلك ، نحتاج إلى تمكين ذاكرة التخزين المؤقت من المستوى 2 في ProductMapper.xml:
<؟ مساحة الاسم = "com.wooyoo.learning.dao.mapper.productMapper"> <!-تمكين ذاكرة التخزين المؤقت الثانوية المستندة إلى إعادة التخصيص-> <cache type = "com.wooyoo.learning.util.util.rediscache"/> <select id = "select" resulttype = "product"> from products where {id id = parametertype = "product" flushcache = "true"> تحديث المنتجات تعيين name = #{name} ، السعر = #{price} حيث id = #{id} limit 1 </uptudle> </papper><cache type = "com.wooyoo.learning.util.rediscache"/> يعني تمكين ذاكرة التخزين المؤقت الثانوية المستندة إلى redis ، وفي عبارة التحديث ، قمنا بتعيين FlushCache على True ، بحيث يمكن إبطال ذاكرة التخزين المؤقت عند تحديث معلومات المنتج تلقائيًا (بشكل أساسي ، يتم استدعاء الطريقة الواضح).
امتحان
تكوين قاعدة بيانات ذاكرة H2
في هذه المرحلة ، أكملنا جميع تطوير التعليمات البرمجية ، وبعد ذلك نحتاج إلى كتابة رمز اختبار الوحدة لاختبار جودة الكود لدينا. في عملية التطوير ، استخدمنا قاعدة بيانات MySQL ، وغالبًا ما نستخدم قواعد البيانات داخل الذاكرة أثناء الاختبار. هنا نستخدم H2 كقاعدة البيانات المستخدمة في سيناريو الاختبار الخاص بنا.
من السهل جدًا استخدام H2 ، تحتاج فقط إلى تكوينه عند استخدام MySQL. في ملف Application.YML:
--- الربيع: ملفات التعريف: اختبار # اختبار بيانات تكوين قاعدة البيانات: URL: JDBC: H2: MEM: اختبار المستخدم: كلمة مرور الجذر: 123456 اسم برنامج التشغيل-اسم السائق: org.h2.driver المخطط: classpath: schema.sql البيانات: classpath: data.sql
لتجنب التعارض مع التكوين الافتراضي ، نستخدم --- لبدء فقرة جديدة واستخدام ملفات التعريف: اختبار للإشارة إلى أن هذا هو التكوين في بيئة الاختبار. ثم فقط أضف التعليق التوضيحي ActiveProfiles (Pressions = "Test") إلى فئة الاختبار الخاصة بنا لتمكين التكوين في بيئة الاختبار ، بحيث يمكنك التبديل من قاعدة بيانات MySQL إلى قاعدة بيانات H2 بنقرة واحدة.
في التكوين أعلاه ، يتم استخدام Schema.SQL لتخزين بيان إنشاء الجدول الخاص بنا ، ويتم استخدام Data.SQL لتخزين بيانات إدراج. وبهذه الطريقة ، عندما نختبر ، ستقرأ H2 هذين الملفين ، وتهيئة بنية الجدول والبيانات التي نحتاجها ، ثم تدميرها في نهاية الاختبار ، والتي لن يكون لها أي تأثير على قاعدة بيانات MySQL الخاصة بنا. هذا هو فائدة قواعد البيانات في الذاكرة. أيضًا ، لا تنسى ضبط نطاق تبعية H2 لاختبار pom.xml.
يعد استخدام SPRING BOOT بسيطًا ، يمكنك بسهولة تبديل قواعد البيانات في بيئات مختلفة دون تعديل أي رمز.
كتابة رمز الاختبار
نظرًا لأننا تمت تهيئتها من خلال مُهيئ الربيع ، لدينا بالفعل فئة اختبار - SPRINGBOOTMYBATISWITHREDISAPPLICATIONTESTS.
يوفر Spring Boot بعض فئات الأدوات التي تسهلنا لإجراء اختبار واجهة الويب ، مثل TestRestTemplate. ثم في ملف التكوين ، نقوم بضبط مستوى السجل للتصحيح لتسهيل مراقبة سجلات التصحيح. رمز الاختبار المحدد كما يلي:
Runwith (SpringRunner.Class) springBoottest (WebEnvironment = SpringBoottest.WebenVironment.Random_Port) ActiveProfiles (Prisتيان = "اختبار") من الدرجة العامة springbootmybatiSwithRedIsApplicationTests {localserverportport private ؛ @autowired private testrestTemplate restTemplate ؛ Test public void test () {long productId = 1 ؛ منتج المنتج = restTemplate.getForObject ("http: // localhost:" + port + "/product/" + productID ، product.class) ؛ AssertThat (Product.getPrice ()). Isequalto (200) ؛ منتج NewProduct = New Product () ؛ NewPrice Long = new Random (). nextlong () ؛ newProduct.setName ("الاسم الجديد") ؛ NewProduct.setPrice (NewPrice) ؛ RestTemplate.put ("http: // localhost:" + port + "/product/" + productID ، newProduct) ؛ Product TestProduct = restTemplate.getForObject ("http: // localhost:" + port + "/productId/" + productID ، product.class) ؛ AssertThat (TestProduct.getPrice ()). isequalto (newPrice) ؛ }}في رمز الاختبار أعلاه:
ندعو أولاً واجهة الحصول على واجهة ونستخدم بيان التأكيد لتحديد ما إذا كان قد تم الحصول على الكائن المتوقع. في هذا الوقت ، سيتم تخزين كائن المنتج في redis.
ثم ندعو واجهة PUT لتحديث كائن المنتج ، وسيتم إبطال ذاكرة التخزين المؤقت Redis.
أخيرًا ، ندعو واجهة GET مرة أخرى لتحديد ما إذا كنا قد حصلنا على كائن منتج جديد. إذا تم الحصول على كائن قديم ، فهذا يعني أن عملية التخزين المؤقت غير الصالحة فشل في التنفيذ وهناك خطأ في الكود ، وإلا فهذا يعني أن رمزنا على ما يرام.
اختبار وحدة الاختبار هي عادة برمجة جيدة. على الرغم من أن الأمر سيستغرق وقتًا معينًا بالنسبة لك ، عندما تحتاج إلى القيام ببعض أعمال إعادة الطعن في المستقبل ، إلا أنك ستكون ممتنًا لنفسك الذي كتب اختبارات الوحدة في الماضي.
عرض نتائج الاختبار
ننقر لتنفيذ حالة الاختبار في Intellij ، ونتائج الاختبار هي كما يلي:
يتم عرض اللون الأخضر ، مما يشير إلى أن حالة الاختبار قد تم تنفيذها بنجاح.
لخص
تقدم هذه المقالة كيفية إنشاء مشروع ويب حديثًا مع SPRING BOOT و MYBATIS و REDIS ، كما يقدم كيفية كتابة اختبارات الوحدة بأمان تحت SPRING BOOT لضمان جودة الكود لدينا. بالطبع ، هناك مشكلة أخرى في هذا المشروع ، أي أن ذاكرة التخزين المؤقت لـ MyBatis من المستوى 2 لا يمكن تخزينها إلا عن طريق مسح DB بأكمله. في هذا الوقت ، قد يتم أيضًا إبطال بعض ذاكرة التخزين المؤقت التي لا تحتاج إلى إبطال ، لذلك لها قيود معينة.