Cache level 1 dan cache level 2
Mybatis Desain cache data menjadi struktur dua tingkat, dibagi menjadi cache tingkat pertama dan cache tingkat kedua:
Cache Level 1 adalah cache tingkat sesi, yang terletak di objek SQLSession yang mewakili sesi basis data, dan juga disebut cache lokal. Cache Level 1 adalah fitur yang diimplementasikan secara internal oleh MyBatis. Pengguna tidak dapat mengonfigurasinya dan secara otomatis mendukungnya secara default. Pengguna tidak memiliki hak untuk menyesuaikannya (tetapi ini tidak mutlak, dan mereka dapat dimodifikasi melalui plug-in pengembangan);
Cache tingkat kedua adalah cache tingkat aplikasi, yang memiliki siklus hidup yang panjang, sama seperti siklus deklarasi aplikasi, yang berarti bahwa ruang lingkup fungsinya adalah seluruh aplikasi aplikasi.
Organisasi cache tingkat pertama dan cache tingkat kedua di Mybatis ditunjukkan pada gambar di bawah ini:
Mekanisme kerja level satu caching:
Cache Level 1 adalah Sesi Sesi Level. Secara umum, objek SQLSession akan menggunakan objek pelaksana untuk menyelesaikan operasi sesi. Objek pelaksana akan mempertahankan cache cache untuk meningkatkan kinerja kueri.
Mekanisme kerja caching sekunder:
Seperti disebutkan di atas, objek SQLSession akan menggunakan objek pelaksana untuk menyelesaikan operasi sesi. Kunci dari mekanisme caching sekunder Mybatis adalah membuat keributan tentang objek pelaksana ini. Jika pengguna telah mengonfigurasi "Cacheenabled = True", ketika MyBatis membuat objek pelaksana untuk objek SQLSession, itu akan menambah dekorator ke objek pelaksana: CachingExecutor. Pada saat ini, SQLSession menggunakan objek CachingExecutor untuk menyelesaikan permintaan operasi. Untuk permintaan kueri, CachingExecutor pertama-tama akan menentukan apakah permintaan kueri telah di-cache hasil dalam cache sekunder tingkat aplikasi. Jika ada hasil kueri, itu akan secara langsung mengembalikan hasil yang di -cache; Jika tidak ada cache, itu akan diserahkan kepada objek pelaksana nyata untuk menyelesaikan operasi kueri. Setelah itu, CachingExecutor akan menempatkan hasil kueri yang dikembalikan oleh pelaksana nyata ke dalam cache dan kemudian mengembalikannya ke pengguna.
Cache sekunder Mybatis dirancang agar lebih fleksibel. Anda dapat menggunakan implementasi cache sekunder yang ditentukan oleh mybatis; Anda juga dapat menyesuaikan cache dengan mengimplementasikan antarmuka org.apache.ibatis.cache.cache; Anda juga dapat menggunakan pustaka cache memori pihak ketiga, seperti memcached, dll.
Transformasi cache
pertanyaan:
Masalah yang paling umum adalah bahwa setelah membuka cache, data pada halaman pertama akan dikembalikan ke halaman saat menanyakan paging. Selain itu, saat menggunakan plug-in Generasi Otomatis SQL untuk menghasilkan SQL untuk metode GET, parameter yang diteruskan tidak berfungsi. Terlepas dari parameter yang diteruskan, hasil kueri dari parameter pertama dikembalikan.
Mengapa masalah ini terjadi:
Ketika menjelaskan proses eksekusi mybatis sebelumnya, disebutkan bahwa di bawah premis mengaktifkan cache, pelaksana MyBatis pertama akan membaca data dari cache, dan hanya pergi ke database untuk meminta jika tidak dapat dibaca. Masalahnya ada di sini. Waktu eksekusi plug-in generasi otomatis SQL dan plug-in paging ada di Pernyataan Penjual, dan Pernyataan Tulisan dieksekusi setelah pelaksana. Apakah plug-in generasi otomatis SQL dan plug-in paging diimplementasikan dengan menulis ulang SQL, pelaksana menggunakan SQL asli saat menghasilkan dan membaca kunci cache (kunci terdiri dari SQL dan nilai parameter yang sesuai), jadi tentu saja ada masalah.
Selesaikan masalahnya:
Setelah penyebab masalah ditemukan, akan lebih mudah untuk menyelesaikannya. Jadikan override metode pembuatan kunci di pelaksana melalui interseptor, dan gunakan SQL yang dihasilkan secara otomatis (sesuai dengan plug-in generasi otomatis SQL) atau tambahkan informasi paging (sesuai dengan plug-in paging) saat menghasilkannya.
Tanda Tangan Interceptor:
@Intercepts ({@Signature (type = executor.class, method = "query", args = {mappedstatement.class, object.class, rowbounds.class, resulthandler.class})}) Kelas publik Cacheinterceptor mengimplementasikan Interceptor {...}Seperti yang dapat dilihat dari tanda tangan, tipe target yang akan dicegat adalah pelaksana (Catatan: Jenis hanya dapat dikonfigurasi sebagai tipe antarmuka), dan metode intersepsi adalah metode bernama kueri.
Implementasi Intercept:
Public Object Intercept (Invocation Invocation) melempar Throwable {Executor ExecutorProxy = (Executor) Invocation.getTarget (); MetaObject metaExecutor = metaObject.forObject (executorproxy, default_object_factory, default_object_wrapper_factory); // Pisahkan rantai objek proxy sementara (metaExecutor.hasgetter ("h")) {objek objek = metaExecutor.getValue ("h"); metaExecutor = metaObject.forObject (objek, default_object_factory, default_object_wrapper_factory); } // kelas target yang memisahkan objek proxy terakhir sementara (metaExecutor.hasgetter ("target")) {objek objek = metaExecutor.getValue ("target"); metaExecutor = metaObject.forObject (objek, default_object_factory, default_object_wrapper_factory); } Objek [] args = Invocation.getArgs (); kembalikan ini.Query (MetaExecutor, Args); } PUBLIK <E> DAFTAR <E> kueri (metaObject metaExecutor, objek [] args) melempar sqlexception {mappedstatement ms = (mappedstatement) args [0]; Objek parameterObject = args [1]; Rowbounds rowbounds = (rowbounds) args [2]; Resulthandler resulthandler = (resulthandler) args [3]; BoundSQL BoundSQL = ms.getBoundSQL (ParameterObject); // Tulis ulang generasi CacheKey CacheKey Kunci = CreateCacheKey (MS, ParameterObject, Rowbounds, BoundSQL); Executor Executor = (Executor) MetaExecutor.GetoriginalObject (); return executor.Query (MS, ParameterObject, Rowbounds, Resulthandler, CacheKey, BoundSQL); } private CacheKey CreateCacheKey (MappedStatement MS, Object ParameterObject, Rowbounds Rowbounds, BoundSQL BoundSQL) {Configuration Configuration = ms.getConfiguration (); pagesqlid = configuration.getVariables (). getProperty ("pagesqlid"); if (null == pagesqlid || "" .Equals (pagesqlid)) {logger.warn ("Properti Pagesqlid tidak diselesaikan, gunakan default '.*Halaman $'"); PAGESQLID = defaultPagesQLID; } CacheKey CacheKey = CacheKey baru (); cacheKey.update (ms.getid ()); cacheKey.update (rowbounds.getoffset ()); cacheKey.update (rowbounds.getlimit ()); Daftar <Parametermapping> parameterMappings = BoundSQL.GetParametermappings (); // Pecahkan bug yang secara otomatis menghasilkan SQL, dan pernyataan SQL kosong, menyebabkan kunci untuk menghasilkan kesalahan jika (null == boundsql.getsql () || "" .Equals (boundsql.getsql ())) {string id = ms.getid (); id = id.substring (id.LastIndexOf (".") + 1); String newsql = null; coba {if ("pilih" .equals (id)) {newsql = sqlbuilder.buildselectsql (parameterObject); } Sqlsource sqlsource = buildingsqlSource (konfigurasi, newsql, parameterobject.getClass ()); parameterMappings = sqlsource.getboundsql (parameterObject) .getParametermappings (); CacheKey.update (newsql); } catch (Exception e) {logger.error ("Perbarui kesalahan cacheKey.", e); }} else {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 (typeHandlerregistry.hastypehandler (parameterObject.getClass ())) {cacheKey.update (parameterObject); } else {for (parametermapping parametermapping: parametermappings) {string propertyname = parametermapping.getProperty (); if (metaObject.hasgetter (propertieName)) {CacheKey.update (metaObject.getValue (propertieName)); } else if (boundsql.hasAdditionalparameter (propertieName)) {cacheKey.update (boundsql.getAdditionalparameter (propertieName)); }}}} // Saat kueri paging diperlukan, tambahkan halaman saat ini dan jumlah halaman per halaman dalam parameter halaman ke cacheKey if (ms.getid (). Pencocokan (pagesqlid) && metaObject.hasgetter ("halaman")) {pageparameter page = (pageParameter) Metaobet. if (null! = page) {cacheKey.update (page.getCurrentPage ()); cacheKey.update (page.getPagesize ()); }} return CacheKey; } Implementasi plugin:
Plugin Objek Publik (Target Objek) {// Ketika kelas target adalah tipe CachingExecutor, kelas target dibungkus, jika tidak, ia akan langsung kembali ke target itu sendiri, mengurangi berapa kali target ditroksi jika (target instance dari CachingExecutor) {return plugin.wrap (target, ini); } else {return target; }}