1. Giriş
Okuma-yazma ayrımı için yapmamız gereken şey, bir SQL parçası için hangi veritabanının yürütüleceğini seçmektir Veritabanını kimin seçeceğine gelince, ikiden fazlası yoktur.Ya ara yazılım bunu bizim için yapar veya program kendi başına yapar. Bu nedenle, genel olarak konuşmak gerekirse, okuma ve yazmanın birbirinden ayrılmasını sağlamanın iki yolu vardır. Birincisi ara katman yazılımına güvenmektir (örneğin: MyCat), bu da uygulamanın ara katman yazılımına bağlı olduğu ve ara yazılımın SQL ayırma yapmamıza yardımcı olduğu anlamına gelir; ikincisi ise ayırmayı kendi kendine yapan uygulamadır. Burada, esas olarak Spring ve AOP tarafından sağlanan yönlendirme veri kaynağını kullanarak, bunu kendimiz yapacak programı seçiyoruz.
Bununla birlikte, uygulama düzeyinde okuma ve yazma ayrımının en büyük zayıflığı (eksikliği), veri kaynağı yapılandırmasının yapılandırmada yazılması nedeniyle veritabanı düğümünün dinamik olarak eklenememesi ve yeni veritabanı, değiştirilmesi gereken yeni bir veri kaynağının eklenmesi anlamına gelmesidir. Uygulamayı yapılandırın ve yeniden başlatın. Elbette avantajı, nispeten basit olmasıdır.
2. AbstractRoutingDataSource
Belirli bir arama anahtarına göre belirli bir veri kaynağına yönlendirin. Bir dizi hedef veri kaynağını dahili olarak tutar ve yönlendirme anahtarları ile hedef veri kaynakları arasında bir eşleme yapar ve anahtarlara dayalı veri kaynaklarını bulmak için yöntemler sağlar.
3. Pratik yapın
Yapılandırma için lütfen şunlara bakın:
https://www.cnblogs.com/cjsblog/p/9706370.html
3.1. Maven bağımlılığı
< ? xml version = "1.0" encoding = "UTF-8"? > < proje xmlns = " xmlns: xsi = " xsi: schemaLocation = " > < modelVersion > 4.0.0 < / modelVersion > < Grup kimliği > com.cjs.example < /Grup kimliği > < artifactId > cjs-datasource-demo < / artifactId > < versiyon > 0.0.1-SNAPSHOT < / version > < ambalaj > kavanoz < / paketleme > < isim > cjs-datasource-demo < / isim > < açıklama > < /açıklama > < ebeveyn > < Grup kimliği > org.springframework.boot < /Grup kimliği > < artifactId > Spring-boot-starter-ebeveyn < / artifactId > < versiyon > 2.0.5. YAYIN < / version > < göreceli yol/ > < ! - depodan ebeveyn araması - > < / ebeveyn > < özellikleri > < project.build.sourceEncoding > UTF-8 < /project.build.sourceEncoding > < project.reporting.outputEncoding > UTF-8 < /project.reporting.outputEncoding > < java.version > 1.8 < /java.version > < /özellikleri > < bağımlılıklar > < bağımlılık > < Grup kimliği > org.springframework.boot < /Grup kimliği > < artifactId > Spring-boot-starter-aop < / artifactId > < /bağımlılık > < bağımlılık > < Grup kimliği > org.springframework.boot < /Grup kimliği > < artifactId > Spring-boot-starter-jdbc < / artifactId > < /bağımlılık > < bağımlılık > < Grup kimliği > org.springframework.boot < /Grup kimliği > < artifactId > Spring-boot-starter-web < / artifactId > < /bağımlılık > < bağımlılık > < Grup kimliği > org.mybatis.spring.boot < /Grup kimliği > < artifactId > mybatis-spring-boot-starter < / artifactId > < versiyon > 1.3.2 < / version > < /bağımlılık > < bağımlılık > < Grup kimliği > org.apache.commons < /Grup kimliği > < artifactId > commons-lang3 < / artifactId > < versiyon > 3.8 < / version > < /bağımlılık > < bağımlılık > < Grup kimliği > mysql < /Grup kimliği > < artifactId > mysql-bağlayıcı-java < / artifactId > < dürbün > Çalışma süresi < /dürbün > < /bağımlılık > < bağımlılık > < Grup kimliği > org.springframework.boot < /Grup kimliği > < artifactId > yay önyükleme başlangıç testi < / artifactId > < dürbün > Ölçek < /dürbün > < /bağımlılık > < / bağımlılıklar > < inşa etmek > < eklentiler > < Eklenti > < Grup kimliği > org.springframework.boot < /Grup kimliği > < artifactId > Spring-boot-maven-eklentisi < / artifactId > < /Eklenti > < ! - < Eklenti > < Grup kimliği > org.mybatis.generator < /Grup kimliği > < artifactId > mybatis-generator-maven-eklentisi < / artifactId > < versiyon > 1.3.5 < / version > < bağımlılıklar > < bağımlılık > < Grup kimliği > mysql < /Grup kimliği > < artifactId > mysql-bağlayıcı-java < / artifactId > < versiyon > 5.1.46 < / version > < /bağımlılık > < / bağımlılıklar > < konfigürasyon > < configurationFile > $ {basedir} /src/main/resources/myBatisGeneratorConfig.xml < / configurationFile > < üzerine yazmak > doğru < / üzerine yaz > < / configuration > < infazlar > < icra > < İD > MyBatis Yapıları Oluşturun < /İD > < hedefler > < hedef > oluşturmak < /hedef > < /hedefler > < / yürütme > < / infazlar > < /Eklenti > - > < / plugins > < /inşa etmek > < / proje >3.2. Veri kaynağı yapılandırması
application.yml
bahar: veri kaynağı: usta: jdbc-url: jdbc: mysql: //192.168.102.31: 3306 / test kullanıcı adı: kök şifre: 123456 sürücü sınıfı adı: com.mysql.jdbc.Driver köle1: jdbc-url: jdbc: mysql: //192.168.102.56: 3306 / test username: pig # Salt okunur hesap şifre: 123456 sürücü sınıfı adı: com.mysql.jdbc.Driver köle2: jdbc-url: jdbc: mysql: //192.168.102.36: 3306 / test username: pig # Salt okunur hesap şifre: 123456 sürücü sınıfı adı: com.mysql.jdbc.DriverÇoklu veri kaynağı yapılandırması
paket com.cjs.example.config; import com.cjs.example.bean.MyRoutingDataSource; import com.cjs.example.enums.DBTypeEnum; import org.springframework.beans.factory.annotation.Qualifier; org.springframework.boot.context.properties.ConfigurationProperties dosyasını içe aktar; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; org.springframework.context.annotation.Configuration dosyasını içe aktarın; javax.sql.DataSource'u içe aktar; java.util.HashMap'i içe aktar; java.util.Map içe aktarın; / ** * Veri kaynağı yapılandırması için, SpringBoot resmi belgesinin Bölüm 79 "Veri Erişimi" ne bakın * 79. Veri Erişimi * 79.1 Özel Veri Kaynağını Yapılandırma * 79.2 İki Veri Kaynağını Yapılandırın * / @Yapılandırma public class DataSourceConfig { @Fasulye @ConfigurationProperties ("spring.datasource.master") public DataSource masterDataSource () { return DataSourceBuilder.create (). build (); } @Fasulye @ConfigurationProperties ("spring.datasource.slave1") public DataSource slave1DataSource () { return DataSourceBuilder.create (). build (); } @Fasulye @ConfigurationProperties ("spring.datasource.slave2") public DataSource slave2DataSource () { return DataSourceBuilder.create (). build (); } @Fasulye public DataSource myRoutingDataSource (@Qualifier ("masterDataSource") DataSource masterDataSource, @Qualifier ("slave1DataSource") DataSource slave1DataSource, @Qualifier ("slave2DataSource") DataSource slave2DataSource) { Harita < Nesne, Nesne > targetDataSources = yeni HashMap < > (); targetDataSources.put (DBTypeEnum.MASTER, masterDataSource); targetDataSources.put (DBTypeEnum.SLAVE1, slave1DataSource); targetDataSources.put (DBTypeEnum.SLAVE2, slave2DataSource); MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource (); myRoutingDataSource.setDefaultTargetDataSource (masterDataSource); myRoutingDataSource.setTargetDataSources (targetDataSources); myRoutingDataSource döndür; } }Burada 4 veri kaynağı, 1 ana, 2 bağımlı ve 1 yönlendirme veri kaynağı yapılandırdık. İlk 3 veri kaynağı, dördüncü veri kaynağını oluşturmak için kullanılır ve gelecekte yalnızca son yönlendirme veri kaynağını kullanacağız.
MyBatis yapılandırması
paket com.cjs.example.config; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; org.springframework.context.annotation.Configuration dosyasını içe aktarın; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.annotation.Resource; javax.sql.DataSource'u içe aktar; @EnableTransactionManagement @Yapılandırma public class MyBatisConfig { @Resource (name = "myRoutingDataSource") özel DataSource myRoutingDataSource; @Fasulye public SqlSessionFactory sqlSessionFactory (), Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean (); sqlSessionFactoryBean.setDataSource (myRoutingDataSource); sqlSessionFactoryBean.setMapperLocations (yeni PathMatchingResourcePatternResolver (). getResources ("classpath: mapper / *. xml")); dönüş sqlSessionFactoryBean.getObject (); } @Fasulye public PlatformTransactionManager platformTransactionManager () { yeni DataSourceTransactionManager (myRoutingDataSource) döndür; } }Spring konteynerinde artık 4 veri kaynağı olduğundan, işlem yöneticisi ve MyBatis için manuel olarak net bir veri kaynağı belirlememiz gerekiyor.
3.3. Yönlendirme anahtarını ayarlayın / veri kaynağını bulun
Hedef veri kaynağı bildiğimiz ilk 3 kaynaktır, ancak onu kullanırken veri kaynağı nasıl bulunur?
İlk olarak, bu üç veri kaynağını temsil edecek bir numaralandırma tanımlıyoruz
paket com.cjs.example.enums; public enum DBTypeEnum { MASTER, SLAVE1, SLAVE2; }Ardından, veri kaynağını ThreadLocal aracılığıyla her iş parçacığı bağlamına ayarlayın
paket com.cjs.example.bean; import com.cjs.example.enums.DBTypeEnum; import java.util.concurrent.atomic.AtomicInteger; public class DBContextHolder { özel statik final ThreadLocal < DBTypeEnum > contextHolder = yeni ThreadLocal < > (); özel statik son AtomicInteger sayacı = new AtomicInteger (-1); public static void set (DBTypeEnum dbType) { contextHolder.set (dbType); } public static DBTypeEnum get () { return contextHolder.get (); } public static void master () { set (DBTypeEnum.MASTER); System.out.println ("Ana sayfaya geç"); } public static void slave () { // anket int index = counter.getAndIncrement ()% 2; eğer (counter.get () > 9999) { counter.set (-1); } eğer (indeks == 0) { set (DBTypeEnum.SLAVE1); System.out.println ("Slave1'e geç"); }Başka { set (DBTypeEnum.SLAVE2); System.out.println ("Slave2'ye geç"); } } }Yönlendirme anahtarını alın
paket com.cjs.example.bean; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.lang.Nullable; public class MyRoutingDataSource, AbstractRoutingDataSource { @Nullable @Override korumalı Nesne determCurrentLookupKey () { DBContextHolder.get () döndür; } }Yönlendirme anahtarını ayarlayın
Varsayılan olarak, tüm sorgular bağımlı veritabanına gider ve ekle / değiştir / sil ana veritabanına gider. İşlem türünü yöntem adına (CRUD) göre ayırıyoruz
paket com.cjs.example.aop; import com.cjs.example.bean.DBContextHolder; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Görünüş @Bileşen public class DataSourceAop { @Pointcut ("! @ Annotation (com.cjs.example.annotation.Master)" + "(yürütme (* com.cjs.example.service .. *. seçin * (..))" + "|| yürütme (* com.cjs.example.service .. *. get * (..)))") public void readPointcut () { } @Pointcut ("@ annotation (com.cjs.example.annotation.Master)" + "|| yürütme (* com.cjs.example.service .. *. insert * (..))" + "|| yürütme (* com.cjs.example.service .. *. ekle * (..))" + "|| yürütme (* com.cjs.example.service .. *. güncelleme * (..))" + "|| yürütme (* com.cjs.example.service .. *. düzenle * (..))" + "|| yürütme (* com.cjs.example.service .. *. sil * (..))" + "|| yürütme (* com.cjs.example.service .. *. remove * (..))") public void writePointcut () { } @Before ("readPointcut ()") public void read () { DBContextHolder.slave (); } @Before ("writePointcut ()") public void write () { DBContextHolder.master (); } / ** * Başka bir yazma yolu: if ... else ... hangilerinin veritabanından okunması gerektiğini belirlemek ve geri kalanı ana veritabanına gitmek için * / // @Before ("yürütme (* com.cjs.example.service.impl. *. * (..))") // öncesinde public void (JoinPoint jp) { // String methodName = jp.getSignature (). GetName (); // // if (StringUtils.startsWithAny (methodName, "get", "select", "find")) { // DBContextHolder.slave (); // }Başka { // DBContextHolder.master (); //} //} }Genel durumlar ve özel durumlar vardır.Bazı durumlarda, ana kitaplığın okunmasını zorlamamız gerekir.Bu durumda, bir birincil anahtar tanımlıyoruz ve ana kitaplığı işaretlemek için açıklamayı kullanıyoruz.
paket com.cjs.example.annotation; public @interface Master { }Örneğin, bir masa üyemiz olduğunu varsayalım
paket com.cjs.example.service.impl; ithal com.cjs.example.annotation.Master; import com.cjs.example.entity.Member; import com.cjs.example.entity.MemberExample; import com.cjs.example.mapper.MemberMapper; import com.cjs.example.service.MemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Hizmet public class MemberServiceImpl MemberService'i uygular { @Autowired özel MemberMapper memberMapper; @Transactional @Override public int insert (Üye üye) { üyeMapper.insert (üye) döndür; } @Usta @Override public int save (Üye üye) { üyeMapper.insert (üye) döndür; } @Override genel Liste < Üye > hepsini seç() { return memberMapper.selectByExample (new MemberExample ()); } @Usta @Override public String getToken (String appId) { // Bazı okuma işlemleri ana veritabanını okumalıdır // Örneğin, WeChat erişim belirtecini alın, çünkü ana-bağımlı senkronizasyonu yoğun dönemlerde gecikebilir // Bu durumda ana veriden okunmaya zorlanmalıdır boş döndür; } }4. Test Etme
paket com.cjs.example; import com.cjs.example.entity.Member; import com.cjs.example.service.MemberService; org.junit.Test'i içe aktar; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith (SpringRunner.class) @Hayalhanemersin public class CjsDatasourceDemoApplicationTests { @Autowired özel MemberService memberService; @Ölçek public void testWrite () { Üye üye = yeni Üye (); üye.setName ("zhangsan"); memberService.insert (üye); } @Ölçek public void testRead () { for (int i = 0; i < 4; i ++) { memberService.selectAll (); } } @Ölçek public void testSave () { Üye üye = yeni Üye (); üye.setName ("wangwu"); memberService.save (üye); } @Ölçek public void testReadFromMaster () { memberService.getToken ("1234"); }Konsolu görüntüle
5. Mühendislik yapısı