ذاكرة التخزين المؤقت من المستوى 1 وذاكرة التخزين المؤقت المستوى 2
يقوم MyBatis بتصميم ذاكرة التخزين المؤقت للبيانات في بنية من مستويين ، مقسمة إلى ذاكرة التخزين المؤقت من المستوى الأول وذاكرة التخزين المؤقت من المستوى الثاني:
ذاكرة التخزين المؤقت من المستوى 1 هي ذاكرة التخزين المؤقت على مستوى الجلسة ، تقع في كائن SQLSession الذي يمثل جلسة قاعدة بيانات ، ويسمى أيضًا ذاكرة التخزين المؤقت المحلية. ذاكرة التخزين المؤقت من المستوى 1 هي ميزة تنفذها داخليًا بواسطة MyBatis. لا يمكن للمستخدمين تكوينه ودعمه تلقائيًا افتراضيًا. لا يحق للمستخدمين تخصيصه (لكن هذا ليس مطلقًا ، ويمكن تعديلهم من خلال مكونات التطوير) ؛
ذاكرة التخزين المؤقت من المستوى الثاني هي ذاكرة التخزين المؤقت على مستوى التطبيق ، والتي لها دورة حياة طويلة ، وهي نفس دورة الإعلان للتطبيق ، مما يعني أن نطاق الوظيفة هو تطبيق التطبيق بالكامل.
يظهر في الشكل أدناه: تنظيم ذاكرة التخزين المؤقت من المستوى الأول وذاكرة التخزين المؤقت من المستوى الثاني في MyBatis:
آلية العمل في التخزين المؤقت من المستوى الأول:
المستوى 1 ذاكرة التخزين المؤقت هو مستوى الجلسة. بشكل عام ، سيستخدم كائن SQLSession كائنًا منفذًا لإكمال عمليات الجلسة. سيحافظ كائن المنفذ على ذاكرة التخزين المؤقت لذاكرة التخزين المؤقت لتحسين أداء الاستعلام.
آلية العمل للتخزين المؤقت الثانوي:
كما ذكر أعلاه ، سيستخدم كائن SQLSession كائنًا تنفيذيًا لإكمال عملية الجلسة. مفتاح آلية التخزين المؤقت الثانوية لـ MyBatis هو إثارة ضجة حول هذا الكائن المنفذ. إذا قام المستخدم بتكوين "Cacheenabled = true" ، عندما يقوم MyBatis بإنشاء كائن منفذي لكائن SQLSession ، فسيضيف ديكور إلى كائن Executor: CachingExecutor. في هذا الوقت ، يستخدم SQLSession كائن CachingExecutor لإكمال طلب التشغيل. لطلبات الاستعلام ، سيحدد CachingExecutor أولاً ما إذا كان طلب الاستعلام قد أدى إلى تخزين النتائج في ذاكرة التخزين المؤقت الثانوية على مستوى التطبيق. إذا كانت هناك نتيجة استعلام ، فسوف يعيد مباشرة النتائج المخزنة مؤقتًا ؛ إذا لم يكن هناك ذاكرة التخزين المؤقت ، فسيتم تسليمها إلى كائن المنفذ الحقيقي لإكمال عملية الاستعلام. بعد ذلك ، سيضع CachingExecutor نتيجة الاستعلام التي يتم إرجاعها من قبل المنفذ الحقيقي في ذاكرة التخزين المؤقت ثم إعادتها إلى المستخدم.
تم تصميم ذاكرة التخزين المؤقت الثانوية لـ MyBatis لتكون أكثر مرونة. يمكنك استخدام تنفيذ ذاكرة التخزين المؤقت الثانوية المحددة بواسطة MyBatis ؛ يمكنك أيضًا تخصيص ذاكرة التخزين المؤقت عن طريق تطبيق واجهة org.apache.ibatis.cache.cache ؛ يمكنك أيضًا استخدام مكتبات ذاكرة التخزين المؤقت للذاكرة التابعة لجهة خارجية ، مثل Memcached ، إلخ.
تحويل ذاكرة التخزين المؤقت
سؤال:
المشكلة الأكثر شيوعًا هي أنه بعد فتح ذاكرة التخزين المؤقت ، سيتم إرجاع البيانات الواردة في الصفحة الأولى إلى الصفحة عند الاستعلام عن الترحيل. بالإضافة إلى ذلك ، عند استخدام المكون الإضافي SQL Automatic Generation لإنشاء SQL لطريقة GET ، لا تعمل المعلمات التي تم تمريرها. بغض النظر عن المعلمات التي تم تمريرها ، يتم إرجاع نتيجة الاستعلام للمعلمة الأولى.
لماذا تحدث هذه المشاكل:
عند شرح عملية تنفيذ MyBatis من قبل ، تم الإشارة إلى أنه في ظل فرضية تمكين ذاكرة التخزين المؤقت ، سيقوم منفذ MyBatis أولاً بقراءة البيانات من ذاكرة التخزين المؤقت ، وينتقل فقط إلى قاعدة البيانات للاستعلام إذا لم يكن من الممكن قراءتها. المشكلة تكمن هنا. يوجد وقت تنفيذ المكون الإضافي للتوليد التلقائي SQL و PAGER في IttialHandler ، ويتم تنفيذ itshandler بعد المنفذ. ما إذا كان يتم تنفيذ المكونات الإضافية لـ SQL Automatic Generation و Paging Plag من خلال إعادة كتابة SQL ، يستخدم المنفذ SQL الأصلي عند إنشاء وقراءة مفتاح ذاكرة التخزين المؤقت (يتكون المفتاح من قيم SQL وقيم المعلمة المقابلة) ، لذلك بالطبع هناك مشكلة.
حل المشكلة:
بمجرد العثور على سبب المشكلة ، سيكون من المناسب حلها. ما عليك سوى تجاوز طريقة إنشاء المفاتيح في المنفذ من خلال التقاطع ، واستخدم SQL الذي تم إنشاؤه تلقائيًا (المقابل لمكون الإضافات التلقائية SQL) أو إضافة معلومات الترحيل (المقابلة لمكونات الترحيل) عند إنشاءها.
توقيع التقاطع:
intercepts ({@signature (type = executor.class ، method = "query" ، args = {mappedStatement.class ، object.class ، rowbounds.class ، resulthandler.class})}) تبرز مستقبلات الفئة العامة اعتراض {...}كما يتضح من التوقيع ، فإن نوع الهدف المراد اعتراضه هو المنفذ (ملاحظة: لا يمكن تكوين النوع إلا كنوع واجهة) ، وطريقة اعتراض هي طريقة مسماة الاستعلام.
تنفيذ التقاطع:
اعتراض الكائن العام (استدعاء الاستدعاء) يلقي رمي {executor executorproxy = (Executor) invocation.getTarget () ؛ metaObject metaexecutor = metaObject.foroBject (ExecutorProxy ، default_object_factory ، default_object_wrapper_factory) ؛ // افصل سلسلة كائن الوكيل بينما (metaexecutor.hasgetter ("H")) {object = metaexecutor.getValue ("H") ؛ metaexecutor = metaObject.foroBject (كائن ، default_object_factory ، default_object_wrapper_factory) ؛ } // الفئة الهدف التي تفصل كائن الوكيل الأخير بينما (metaexecutor.hasgetter ("target")) {object = metaexecutor.getValue ("target") ؛ metaexecutor = metaObject.foroBject (كائن ، default_object_factory ، default_object_wrapper_factory) ؛ } object [] args = invocation.getargs () ؛ إرجاع this.query (metaexecutor ، args) ؛ } public <e> list <e> query (metaObject metaexecutor ، object [] args) يرمي sqlexception {medstatement ms = (mappedStatement) args [0] ؛ parameterObject = args [1] ؛ ROWBOUNDS ROWBOUNDS = (ROWBOUNDS) args [2] ؛ Resulthandler Resulthandler = (resulthandler) args [3] ؛ BODEDSQL BOODSQL = MS.GetBoundSQL (parameterObject) ؛ // أعد كتابة توليد مفتاح cachekey cachekey = createCacheKey (ms ، parameterObject ، rowbounds ، boundsql) ؛ Executor Executor = (Executor) metaexecutor.getoriginalObject () ؛ Return Executor. } cachekey private createCachekey (mappedStatement MS ، parameterObject ، rowbounds rowbounds ، boundsql boundsql) {configuration configuration = ms.getConfiguration () ؛ pagesqlid = configuration.getVariables (). getProperty ("pagesqlid") ؛ if (null == pageqlid || "" .equals (pagesqlid)) {logger.warn ("pagesqlid لم يتم تعيينه ، استخدم الافتراضي".*الصفحة $ '") ؛ pagesqlid = defaultpagesqlid ؛ } cachekey cachekey = new cachekey () ؛ cachekey.update (Ms.GetId ()) ؛ cachekey.update (rowbounds.getoffset ()) ؛ cachekey.update (rowbounds.getLimit ()) ؛ قائمة <ParmeterMapping> paramEterMappings = boundsql.getParamEterMappings () ؛ . id = id.subString (id.lastindexof (".") + 1) ؛ سلسلة newsql = null ؛ حاول {if ("SELECT" .equals (id)) {newsql = sqlbuilder.buildselectsql (parameterObject) ؛ } sqlsource sqlsource = buildSqlSource (التكوين ، newsql ، parameterObject.getClass ()) ؛ ParamEterMappings = sqlsource.getBoundSql (parameterObject) .getParamEterMappings () ؛ cachekey.update (newsql) ؛ } catch (استثناء e) {logger.error ("تحديث خطأ cachekey." ، e) ؛ }} آخر {cachekey.update (boundsql.getsql ()) ؛ } metaObject metaObject = metaObject.foroBject (parameterObject ، default_object_factory ، default_object_wrapper_factory) ؛ if (parameTerMappings.size ()> 0 && parameterObject! = null) {typeHandlerRegistry typeHandlerRegistry = ms.getConfiguration (). gettypeHandlerRegistry () ؛ if (typeHandLergistry.hastypeHandler (parameterObject.getClass ())) {cachekey.update (parameterObject) ؛ } else {for (parameterMapping parameterMapping: parameterMappings) {String propertyName = parameterMapping.getProperty () ؛ if (metaObject.hasgetter (propertyName)) {cachekey.update (metaObject.getValue (propertyName)) ؛ } آخر إذا (boundsql.hasadditionalparameter (propertyName)) {cachekey.update (boundsql.getAdditionalParameter (propertyName)) ؛ }}}} // عند الحاجة إلى استعلام ترحيل ، أضف الصفحة الحالية وعدد الصفحات لكل صفحة في معلمة الصفحة إلى cachekey if (ms.getId (). matches (pagesqlid) && metaobject.hasgetter ("page")) if (null! = page) {cachekey.update (page.getCurrentPage ()) ؛ cachekey.update (page.getPagesize ()) ؛ }} إرجاع cachekey ؛ } تنفيذ البرنامج المساعد:
المكون الإضافي للكائن العام (هدف الكائن) {// عندما تكون الفئة الهدف من نوع CachingExecutor ، يتم لف الفئة المستهدفة ، وإلا فإنه سيعود مباشرة إلى الهدف نفسه ، مما يقلل من عدد المرات التي يتم فيها كتبانة الهدف إذا (Target extorly of CachingExecutor) {return plugin.wrap (الهدف ،) ؛ } آخر {return target ؛ }}