Proyek yang sama terkadang melibatkan banyak database, yaitu, banyak sumber data. Beberapa sumber data dapat dibagi menjadi dua situasi:
1) Dua atau lebih database tidak memiliki korelasi dan tidak tergantung satu sama lain. Bahkan, ini dapat dikembangkan sebagai dua proyek. Misalnya, dalam pengembangan game, satu database adalah database platform, dan database lain yang sesuai dengan game di bawah platform juga tersedia;
2) Dua atau lebih database adalah hubungan master-slave, seperti MySQL membangun master-master, diikuti oleh banyak budak; atau salinan master-slave yang dibangun dengan MHA;
Saat ini, ada sekitar dua cara untuk membangun sumber multi-data musim semi, dan Anda dapat memilih sesuai dengan situasi sumber multi-data.
1. Gunakan file konfigurasi pegas untuk mengonfigurasi beberapa sumber data secara langsung
Misalnya, jika tidak ada korelasi antara dua database, Anda dapat secara langsung mengonfigurasi beberapa sumber data dalam file konfigurasi pegas dan kemudian melakukan konfigurasi transaksi, seperti yang ditunjukkan di bawah ini:
<context:component-scan base-package="net.aazj.service,net.aazj.aop" /><context:component-scan base-package="net.aazj.aop" /><!-- Introduce property files-><context:property-placeholder location="classpath:config/db.properties" /> <!-- Configure data source-><bean name="dataSource" init-method = "init" destroy-method = "tutup"> <name properti = "url" value = "$ {jdbc_url}" /> <name properti = "nama pengguna" value = "$ {jdbc_username}" /> <nama properti = "kata sandi" = "$ {jdbc_password}" /<"Name =" Nilai /Kata Sandi = "$ {JDBC_PASSWORD}" value = "0" /> <!-Jumlah maksimum koneksi yang digunakan oleh kumpulan koneksi-> <nama properti = "maxactive" value = "20" /> <!-Jumlah maksimum koneksi yang tersedia-> <nama properti = "maxidle" value = "20" /> <!-Jumlah minimum koneksi yang tersedia-> <nama properti = "Minidle" value = "0" value = "60000"/> </ bean> <bean id = "sqlSessionfactory"> <name properti = "DataSource" ref = "DataSource"/> <name properti = "configLocation" value = "classpath: x config/mybatis-config.xml"/<nama properti = "mappath" mybatis-config.xml "/<property name =" mappath/. /></bean> <!-- Transaction manager for a single JDBC DataSource --><bean id="transactionManager"> <property name="dataSource" ref="dataSource" /></bean> <!-- Define transactions using annotation--><tx:annotation-driven transaction-manager="transactionManager" /> <bean> <property name="basePackage" value = "net.aazj.mapper"/> <name properti = "sqlSessionFactoryBeanName" value = "sqlSessionFactory"/> </tean> <!-memungkinkan penggunaan gaya @Aspectj dari AOP-> <AOP: AspectJ-autoproxy/>Konfigurasi sumber data kedua
<bean name = "DataSource_2" init-method = "init" dash-method = "tutup"> <name properti = "url" value = "$ {jdbc_url_2}" /> <name properti = "name"} {JDBC_USERNAME_2} " /> <word" {JDBC_USERNAME_2} " /> <word =" {JDBC_USERNAME_2} " /> <!-Inisialisasi ukuran koneksi-> <name properti = "inisialisasi" value = "0" /> <!-Jumlah koneksi maksimum yang digunakan dalam kumpulan koneksi-> <nama properti = "maxactive" value = "20" /> <!-Kolam koneksi Idle maksimum-> <nama properti = "MaxIdle" value = "20" /> <!-Maximum Idle Connection Pool-> <Properti Name = "Maxidle" Nilai = "20" /> <! connection waiting time--> <property name="maxWait" value="60000" /></bean> <bean id="sqlSessionFactory_slave"> <property name="dataSource" ref="dataSource_2" /> <property name="configLocation" value="classpath:config/mybatis-config-2.xml" /> <property name="mapperLocations" value = "classpath*: config/mappers2/**/*. xml"/> </ bean> <!-Transaction Manager untuk satu JDBC DataSource-> <bean id = "TransactionManager_2"> <nama properti = "DataSource" Ref = "DataSource_2"/</bean> <! transaction-manager = "transactionManager_2" /> <bean> <name properti = "basepackage" value = "net.aazj.mapper2" /> <name properti = "sqlSessionFactoryBeanName" value = "sqlSessionFactory_2" /> < /bean> Seperti yang ditunjukkan di atas, kami mengkonfigurasi dua sumber data, dua SQLSessionFactory, dua transactionmanagers, dan kuncinya terletak pada konfigurasi MappersCannerConfigurer - menggunakan properti SQLSessionFactoryBeanName untuk menyuntikkan nama SQLSessionFactory yang berbeda. Dengan cara ini, kami menyuntikkan SQLSessionFactory yang sesuai ke antarmuka mapper yang sesuai dengan database yang berbeda.
Perlu dicatat bahwa konfigurasi beberapa database ini tidak mendukung transaksi terdistribusi, yaitu, beberapa database tidak dapat dioperasikan dalam transaksi yang sama. Keuntungan dari konfigurasi ini adalah sangat sederhana, tetapi tidak fleksibel. Ini tidak terlalu cocok untuk konfigurasi sumber multi-data dari tipe master-slave. Konfigurasi sumber multi-data dari tipe master-slave harus sangat fleksibel dan membutuhkan konfigurasi terperinci sesuai dengan jenis bisnis. Misalnya, untuk beberapa pernyataan terpilih yang memakan waktu, kami berharap dapat menempatkannya di budak, dan untuk pembaruan, hapus dan operasi lainnya, kami hanya dapat menjalankannya di master. Selain itu, untuk beberapa pernyataan tertentu dengan persyaratan waktu nyata, kita mungkin juga perlu menempatkannya di master - misalnya, dalam skenario, saya pergi ke mal untuk membeli senjata, dan operasi pembelian jelas merupakan master. Setelah pembelian selesai, kita perlu menuntut kembali senjata dan koin emas yang saya miliki. Maka kueri ini mungkin juga perlu mencegah mereka dieksekusi pada tuan, dan tidak dapat dieksekusi pada budak, karena mungkin ada penundaan pada budak. Kami tidak ingin pemain menemukan bahwa setelah pembelian berhasil, mereka tidak dapat menemukan senjata di ransel.
Oleh karena itu, untuk konfigurasi sumber multi-data tipe master-slave, konfigurasi fleksibel diperlukan sesuai dengan bisnis, yang dipilih dapat ditempatkan pada budak, dan yang dipilih tidak dapat ditempatkan pada budak. Oleh karena itu, konfigurasi sumber data di atas tidak terlalu cocok.
2. Konfigurasi Sumber Multi-Data Berdasarkan AbstractroutingDataSource dan AOP
Prinsip dasarnya adalah bahwa kami mendefinisikan Thread Source ThreadlocalrountingDataSource kami untuk mewarisi abstractroutingDataSource, dan kemudian menyuntikkan sumber data master dan slave ke dalam threadlocalrountingDataSource dalam file konfigurasi, dan kemudian secara fleksibel mengkonfigurasi melalui AOP, di mana untuk memilih sumber data master dan di mana untuk menjadi Sumber Data dan ke mana yang menjadi Sumber. Mari kita lihat implementasi kode di bawah ini:
1) Pertama mendefinisikan enum untuk mewakili sumber data yang berbeda:
paket net.aazj.enums; /** * Kategori Sumber Data: Master/Slave */Public Enum DataSources {Master, Slave} 2) Gunakan headlocal untuk menyimpan kunci sumber data mana yang akan dipilih untuk setiap utas:
paket net.aazj.util; impor net.aazj.enums.datasources; Kelas Publik DataSourCetypeManager {private static final ThreadLocal <DateSources> DataSourCetypes = new ThreadLocal <DataSources> () {@Override Protected DataSources InitialValue () {return DataSources.master; }}; Public Static DataSources get () {return DataSourCetypes.get (); } public static void set (DataSources DataSourCetype) {DataSourCetypes.set (DataSourCetype); } public static void reset () {DataSourCetypes.set (DataSources.master0); }} 3) Tentukan ThreadLocalRountingDataSource dan mewarisi AbstractroutingDataSource:
paket net.aazj.util; impor org.springframework.jdbc.datasource.lookup.abstractroutingDataSource; Public Class ThreadLocalRountingDataSource memperluas AbstractroutingDataSource {@Override Protected Object DetectionEcurrentLookUpkey () {return DataSourCetypeManager.get (); }}4) Suntikan sumber data master dan slave ke ThreadLocalRountingDataSource di file konfigurasi:
<context:component-scan base-package="net.aazj.service,net.aazj.aop" /><context:component-scan base-package="net.aazj.aop" /><!-- Introduce property files-><context:property-placeholder location="classpath:config/db.properties" /> <!-- Configure data source Master --><bean name="dataSourceMaster" init-method = "init" destroy-method = "tutup"> <name properti = "url" value = "$ {jdbc_url}" /> <name properti = "nama pengguna" value = "$ {jdbc_username}" /> <nama properti = "kata sandi" = "$ {jdbc_password}" /<"Name =" Nilai /Kata Sandi = "$ {JDBC_PASSWORD}" value = "0" /> <!-Jumlah maksimum koneksi yang digunakan oleh kumpulan koneksi-> <nama properti = "maxactive" value = "20" /> <!-Jumlah maksimum koneksi yang tersedia-> <nama properti = "maxidle" value = "20" /> <!-Jumlah minimum koneksi yang tersedia-> <nama properti = "Minidle" value = "0" value = "60000" /> </ bean> <!-Mengkonfigurasi sumber data slave-> <bean name = "DataSourcesLave" init-method = "init" dash-method = "tutup"> <nama properti = "url" value = "$ {jdbc_url_slave}" /<name name = "$ {JDBC_URL_SLAVE {{{JDBC_URLANENE {JDBC_URLACE {{JDBC_URN {{JDBC_BC name = "kata sandi" value = "$ {jdbc_password_slave}" /> <!-inisialisasi ukuran koneksi-> <nama properti = "inisialisasi" value = "0" /> <!-Jumlah koneksi maksimum yang digunakan oleh kumpulan koneksi-> <nama properti = "MaxActive" value = "20" /<! IDLE Connection Pool-> <Properti Nama = "Minidle" Value = "0" /> <!-Dapatkan waktu tunggu koneksi maksimum-> <nama properti = "maxwait" value = "60000" /> < /bean> <bean id = "datasource"> <name properti = "DefaultTargeSource" Ref = "DataSourcemaster" <po properten /"DefaultTargeSource" REF = "DataSourCemaster" /"DefaultTargetDataSource" Ref = "DataSourCemaster" Key-Type = "net.aazj.enums.dataSources"> <entri key = "master" value-ref = "DataSourCemaster"/> <entri key = "slave" value-ref = "DataSourceslave"/> <!-Anda dapat menambahkan beberapa datasor di sini-> </peta> </properti> </bean> <"bean iden =" Ref = "DataSource"/> <properti name = "configLocation" value = "classpath: config/mybatis-config.xml"/> <properti name = "mapperlocations" value = "classpath*: config/mapper" property/*xml "/bean </bean> <!-Transaction Manager <! name = "DataSource" ref = "DataSource" /> </ bean> <!-Definisi transaksi menggunakan anotasi-> <tx: anotasi-digerakkan transaksi-manager = "transactionManager" /> <bean> <nama properti = "Basepackage" value = "net.aazj.mapper" /<!-<! " value = "sqlSessionFactory"/> -> </t bean> Dalam file konfigurasi Spring di atas, kami mendefinisikan DataSourCemaster dan DataSourceslave untuk masing -masing database master dan slave, dan kemudian menyuntikkannya ke <bean id = "DataSource"> sehingga sumber data kami dapat memilih DataSourCemaster dan DataSourcesLave sesuai dengan tombol yang berbeda.
5) Gunakan Spring AOP untuk menentukan kunci DataSource, jadi DataSource akan memilih DataSourCemaster dan DataSourceslave sesuai dengan kunci:
paket net.aazj.aop; impor net.aazj.enums.datasources; impor net.aazj.util.datasourCetypeManager; impor org.aspectj.lang.joinpoint; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; impor org.aspectj.lang.annotation.pointcut; import org.springframework.stereotipe.stereotipe.sconyponent; @Aspect // untuk AOP @Component // untuk Kelas ScanPublic Auto DataSourceinterCeptor {@pointcut ("Eksekusi (publik * net.aazj.service .. *. GetUser (..))") public void dataSourcesLave () {}; @Before ("DataSourcesLave ()") public void Sebelumnya (joinpoint jp) {DataSourCetypeManager.set (DataSources.slave); } // ...} Di sini kami mendefinisikan kelas aspek. Kami menggunakan @Before untuk memanggil DataSourCetypeManager.set (DataSources.slave) sebelum metode yang sesuai dengan @Pointcut ("Eksekusi (publik * net.aazj.service .. *. GetUser (..)") dipanggil, dan tipe kunci ditetapkan ke DataSources.slave, So DataSource. Oleh karena itu, pernyataan SQL untuk metode ini akan dieksekusi pada database Slave.
Kami dapat terus memperluas aspek DataSourceinterceptor, dan membuat berbagai definisi di dalamnya untuk menentukan sumber data yang sesuai dengan sumber data yang sesuai untuk metode layanan tertentu.
Dengan cara ini, kita dapat menggunakan fungsi yang kuat dari Spring AOP untuk mengonfigurasinya dengan sangat fleksibel.
6) Analisis prinsip abstractroutingDataSource
ThreadLocalRountingDataSource mewarisi AbstractroutingDataSource, mengimplementasikan metode abstrak abstrak yang dilindungi objek abstrak DetermineCurrentLookUpkey (); dengan demikian menerapkan fungsi perutean untuk sumber data yang berbeda. Mari kita mulai dengan kode sumber untuk menganalisis prinsip -prinsip:
Kelas Abstrak Publik AbstractroutingDataSource Memperluas AbstractDataSource mengimplementasikan inisialisasiBeanAbstractroutingDataSource mengimplementasikan inisialisasi. Kemudian ketika Spring menginisialisasi kacang, itu akan memanggil antarmuka inisialisasi batal afterpropertiesset () melempar pengecualian; Mari kita lihat bagaimana AbstractroutingDataSource mengimplementasikan antarmuka ini: @Override public void afterpropertiesset () {if (this.targetDataSources == null) {lempar baru ilegalargumentException ("properti 'targetDataSources' diperlukan"); } this.resolvedDataSources = new HashMap <object, DataSource> (this.targetDataSources.size ()); untuk (map.entry <objek, objek> entri: this.targetDataSources.entryset ()) {objek lookupkey = resolvespecifiedLookUnkKey (entry.getKey ()); DataSource DataSource = resolvespecifiedDataSource (entri.getValue ()); this.resolvedDataSources.put (lookupkey, dataSource); } if (this.defaultTargetDataSource! = null) {this.resolvedDefaultDataSource = resolvespecifiedDataSource (this.defaultTargetDataSource); }} TargetDataSources adalah DataSourCemaster dan DataSourcesLave yang kami suntikan ke dalam file konfigurasi XML. Metode afterpropertiesset disuntikkan.
DataSourCemaster dan DataSourceslave untuk membangun hashmap - resolvedDataSources. Lebih mudah untuk mendapatkan sumber data yang sesuai dari peta sesuai dengan kunci nanti.
Mari kita lihat bagaimana koneksi getConnection () melempar sqlexception; Dalam antarmuka AbstractDataSource diimplementasikan:
@Override Public Connection getConnection () melempar sqlexception {return detectionetargetDataSource (). GetConnection (); }Kuncinya adalah dengan deceTeTargetDataSource (), yang dapat dilihat berdasarkan nama metode, dan kita harus memutuskan sumber data mana yang akan digunakan di sini:
DataSource Detasource yang Dilindungi DeterminetGetDataSource () {assert.notnull (this.resolvedDataSources, "router DataSource tidak diinisialisasi"); Objek lookupkey = determinecurrentlookupkey (); DataSource DataSource = this.resolvedDataSources.get (lookUpkey); if (DataSource == null && (this.lenientfallback || lookupkey == null)) {DataSource = this.resolvedDefaultDataSource; } if (DataSource == null) {lempar baru ilegalstateException ("tidak dapat menentukan sumber data target untuk kunci pencarian [" + lookupkey + "]"); } return DataSource;} Objek lookupkey = determinecurrentlookupkey (); Metode ini diimplementasikan oleh kami, di mana kami mendapatkan nilai kunci yang disimpan di ThreadLocal. Setelah mendapatkan kunci, dapatkan sumber data yang sesuai dengan kunci dalam peta yang diinisialisasi disurvei disumbangkan dari afterpropertiesset (). Nilai kunci yang disimpan dalam threadlocal diatur sebelum memanggil metode yang relevan dalam layanan melalui AOP. Oke, ini dia sudah selesai!
3. Ringkasan
Dari artikel ini, kita dapat mengalami kekuatan dan fleksibilitas AOP.
Di atas adalah penyortiran informasi pemrosesan sumber multi-data Mybatis. Saya berharap ini dapat membantu teman yang membutuhkan