บางครั้งโครงการเดียวกันเกี่ยวข้องกับหลายฐานข้อมูลนั่นคือแหล่งข้อมูลหลายแหล่ง แหล่งข้อมูลหลายแหล่งสามารถแบ่งออกเป็นสองสถานการณ์:
1) ฐานข้อมูลสองฐานขึ้นไปไม่มีความสัมพันธ์และเป็นอิสระจากกัน ในความเป็นจริงสิ่งนี้สามารถพัฒนาเป็นสองโครงการ ตัวอย่างเช่นในการพัฒนาเกมฐานข้อมูลหนึ่งเป็นฐานข้อมูลแพลตฟอร์มและฐานข้อมูลอื่น ๆ ที่สอดคล้องกับเกมภายใต้แพลตฟอร์ม
2) ฐานข้อมูลสองฐานขึ้นไปคือความสัมพันธ์หลัก-ทาสเช่น MySQL สร้างมาสเตอร์มาสเตอร์ตามด้วยทาสหลายคน หรือสำเนาหลักที่สร้างขึ้นด้วย MHA;
ปัจจุบันมีสองวิธีในการสร้างแหล่งข้อมูลหลายแหล่งสปริงและคุณสามารถเลือกได้ตามสถานการณ์ของแหล่งข้อมูลหลายข้อมูล
1. ใช้ไฟล์การกำหนดค่าสปริงเพื่อกำหนดค่าแหล่งข้อมูลหลายแหล่งโดยตรง
ตัวอย่างเช่นในกรณีที่ไม่มีความสัมพันธ์ระหว่างฐานข้อมูลสองฐาน
<บริบท: Component-Scan base-package = "net.aazj.service, net.aazj.aop" /> <บริบท: คอมโพเนนต์-สแกนฐานแพคเกจ = "net.aazj.aop" /> <! init-method = "init" destroy-method = "close"> <property name = "url" value = "$ {jdbc_url}" /> <property name = "ชื่อผู้ใช้" value = "$ {jdbc_username}" /> <property name = "รหัสผ่าน" value = "0" /> <!-จำนวนสูงสุดของการเชื่อมต่อที่ใช้โดยพูลการเชื่อมต่อ-> <ชื่อคุณสมบัติ = "maxactive" value = "20" /> <!-จำนวนสูงสุดของการเชื่อมต่อที่มีอยู่-> <property name = "maxidle" value = "20" /> <! name = "maxwait" value = "60000" /> </ebean> <bean id = "sqlsessionfactory"> <property name = "dataSource" ref = "dataSource" /> <property name = "configlocation" value = "classpath: config /mybatis-config.xml" value = "classpath*: config/mappers/**/*. xml"/> </ebean> <!-ผู้จัดการธุรกรรมสำหรับ DataSource JDBC เดียว-> <bean id = "transactionManager"> <property name = "DataSource" ref = "DataSource" Transaction-manager = "TransactionManager"/> <Bean> <property name = "basepackage" value = "net.aazj.mapper"/> <property name = "sqlsessionfactoryBeanName" value = "sqlsessionfactory"/> </epop:>การกำหนดค่าของแหล่งข้อมูลที่สอง
<bean name = "dataSource_2" init-method = "init" destroy-method = "close"> <property name = "url" value = "$ {jdbc_url_2}" /> <property name = "username" value = "$ {jdbc_username_2}" /> < เริ่มต้นขนาดการเชื่อมต่อ-> <property name = "ค่าเริ่มต้น" value = "0" /> <!-จำนวนสูงสุดของการเชื่อมต่อที่ใช้ในพูลการเชื่อมต่อ-> <ชื่อคุณสมบัติ = "maxactive" value = "20" /> <! เวลา-> <property name = "maxwait" value = "60000" /> </ebean> <bean id = "sqlsessionfactory_slave"> <property name = "dataSource" ref = "dataSource_2" /> <property name = "การกำหนดค่า value = "classpath*: config/mappers2/**/*. xml"/> </ebean> <!-ตัวจัดการธุรกรรมสำหรับ DataSource JDBC เดี่ยว-> <bean id = "transactionManager_2"> <property name = "dataSource" ref = "dataSource_2"/> Transaction-Manager = "TransactionManager_2" /> <Bean> <property name = "basepackage" value = "net.aazj.mapper2" /> <property name = "SQLSessionFactoryBeanName" value = "SQLSessionFactory_2" /> </ebean> ดังที่แสดงไว้ด้านบนเรากำหนดค่าสองแหล่งข้อมูลสอง SQLSessionFactory สองรายการ TransactionManagers และกุญแจสำคัญอยู่ในการกำหนดค่าของ MappersCannerConfigurer - การใช้คุณสมบัติ SQLSessionFactoryBeanName เพื่อฉีดชื่อ SQLSessionFactory ที่แตกต่างกัน ด้วยวิธีนี้เราฉีด SQLSessionFactory ที่สอดคล้องกันลงในอินเทอร์เฟซ MAPPER ที่สอดคล้องกับฐานข้อมูลที่แตกต่างกัน
ควรสังเกตว่าการกำหนดค่าฐานข้อมูลหลายฐานข้อมูลนี้ไม่รองรับธุรกรรมแบบกระจายนั่นคือฐานข้อมูลหลายฐานข้อมูลไม่สามารถดำเนินการในธุรกรรมเดียวกันได้ ข้อดีของการกำหนดค่านี้คือมันง่ายมาก แต่ก็ไม่ยืดหยุ่น มันไม่เหมาะสำหรับการกำหนดค่าของแหล่งข้อมูลหลายแผ่นของชนิดหลัก การกำหนดค่าของแหล่งข้อมูลหลายข้อมูลของชนิดหลักต้องมีความยืดหยุ่นโดยเฉพาะและต้องมีการกำหนดค่าโดยละเอียดตามประเภทของธุรกิจ ตัวอย่างเช่นสำหรับคำสั่ง Select ที่ใช้เวลานานเราหวังว่าจะนำพวกเขาไปไว้ในทาสและสำหรับการอัปเดตการลบและการดำเนินการอื่น ๆ เราสามารถดำเนินการกับอาจารย์ได้เท่านั้น นอกจากนี้สำหรับคำสั่งที่เลือกบางอย่างที่มีข้อกำหนดแบบเรียลไทม์สูงเราอาจต้องวางไว้ในอาจารย์ - ตัวอย่างเช่นในสถานการณ์ฉันไปที่ห้างสรรพสินค้าเพื่อซื้ออาวุธและการดำเนินการซื้อเป็นหลักอย่างแน่นอน หลังจากการซื้อเสร็จสมบูรณ์เราต้องถามอาวุธและเหรียญทองที่ฉันเป็นเจ้าของอีกครั้ง จากนั้นแบบสอบถามนี้อาจจำเป็นต้องป้องกันไม่ให้พวกเขาถูกประหารชีวิตกับอาจารย์และไม่สามารถดำเนินการกับทาสได้เนื่องจากอาจมีความล่าช้าในทาส เราไม่ต้องการให้ผู้เล่นพบว่าหลังจากการซื้อประสบความสำเร็จพวกเขาไม่สามารถหาอาวุธในกระเป๋าเป้สะพายหลังได้
ดังนั้นสำหรับการกำหนดค่าของแหล่งข้อมูลหลายแผ่นหลักสลาฟจำเป็นต้องมีการกำหนดค่าที่ยืดหยุ่นตามธุรกิจซึ่งสามารถเลือกได้บนทาสและไม่สามารถเลือกได้บนทาส ดังนั้นการกำหนดค่าข้างต้นของแหล่งข้อมูลจึงไม่เหมาะสมมาก
2. การกำหนดค่าของแหล่งข้อมูลหลายข้อมูลตาม abstractroutingDataSource และ AOP
หลักการพื้นฐานคือเรากำหนดคลาส DataSource ThreadLocalRountingDataSource เพื่อสืบทอด AbstractroutingDataSource จากนั้นฉีดแหล่งข้อมูลต้นแบบและทาสลงใน ThreadLocalRountingDataSource ในไฟล์กำหนดค่า มาดูการใช้งานรหัสด้านล่าง:
1) ก่อนกำหนด enum เพื่อแสดงแหล่งข้อมูลที่แตกต่างกัน:
Package Net.aazj.enums; /** * หมวดหมู่ของแหล่งข้อมูล: Master/Slave */Public Enum DataSources {Master, Slave} 2) ใช้ headlocal เพื่อบันทึกคีย์ของแหล่งข้อมูลที่จะเลือกสำหรับแต่ละเธรด:
แพ็คเกจ net.aazj.util; นำเข้า net.aazj.enums.datasources; DataSourCetYpEnager {Private Static Final ThreadLocal <TataCources> DataSourCetypes = ใหม่ ThreadLocal <TataCources> () {@Override Protected DataSources InitialValue () {ส่งคืน DataSources.master; - แหล่งข้อมูลสาธารณะคงที่ get () {ส่งคืน dataSourcetypes.get (); } ชุดโมฆะสาธารณะคงที่ (DataSources DataSourCetype) {dataSourcetypes.set (DataSourCetype); } โมฆะคงที่สาธารณะรีเซ็ต () {dataSourcetypes.set (dataSources.master0); - 3) กำหนด ThreadLocalRountingDataSource และสืบทอด abstractroutingDataSource:
แพ็คเกจ net.aazj.util; นำเข้า org.springframework.jdbc.datasource.lookup.abstractroutingDatasource; ชั้นเรียนสาธารณะ ThreadLocalRountingDataSource ขยาย AbsTractroutingDataSource {@Override วัตถุที่ได้รับการป้องกันการกำหนดค่าใช้จ่ายในการกำหนดค่าใช้จ่าย () {ส่งคืน DataSourCetyPemanager.get (); -4) การฉีดแหล่งข้อมูลต้นแบบและทาสลงใน ThreadLocalRountingDataSource ในไฟล์กำหนดค่า:
<บริบท: Component-Scan base-package = "net.aazj.service, net.aazj.aop" /> <บริบท: คอมโพเนนต์-สแกนฐานแพคเกจ = "net.aazj.aop" /> <! init-method = "init" destroy-method = "close"> <property name = "url" value = "$ {jdbc_url}" /> <property name = "ชื่อผู้ใช้" value = "$ {jdbc_username}" /> <property name = "รหัสผ่าน" value = "0" /> <!-จำนวนสูงสุดของการเชื่อมต่อที่ใช้โดยพูลการเชื่อมต่อ-> <ชื่อคุณสมบัติ = "maxactive" value = "20" /> <!-จำนวนสูงสุดของการเชื่อมต่อที่มีอยู่-> <property name = "maxidle" value = "20" /> <! name = "maxwait" value = "60000" /> </ebean> <!-กำหนดค่า SOUT SLAVE แหล่งข้อมูล-> <bean name = "DataSourcesLave" init-method = "init" destroy-method = "close"> <property name = "url" url " value = "$ {jdbc_username_slave}" /> <property name = "รหัสผ่าน" value = "$ {jdbc_password_slave}" /> <! Pool-> <property name = "MaxIdle" value = "20" /> <!-การเชื่อมต่อที่ไม่ได้ใช้งานขั้นต่ำ-> <ชื่อคุณสมบัติ = "minidle" value = "0" /> <! ref = "dataSourceMaster"/> <property name = "targetDataSources"> <แผนที่ key-type = "net.aazj.enums.datasources"> <entry key = "master" value-ref = "DataSourceMaster"/> </คุณสมบัติ> </ebean> <bean id = "sqlsessionfactory"> <property name = "dataSource" ref = "dataSource"/> <property name = "การกำหนดค่า" value = "classPath: config/mybatis-config.xml"/> <property name = "mapperlocations" <!-ตัวจัดการธุรกรรมสำหรับ DataSource JDBC เดียว-> <bean id = "TransactionManager"> <ชื่อคุณสมบัติ = "DataSource" ref = "DataSource" /> </ebean> <! value = "net.aazj.mapper"/> <!-<property name = "SQLSessionFactoryBeanName" value = "SQLSessionFactory"/>-> </ebean> ในไฟล์การกำหนดค่าสปริงด้านบนเรากำหนดแหล่งข้อมูลและแหล่งข้อมูลสำหรับฐานข้อมูลหลักและฐานข้อมูลทาสตามลำดับจากนั้นฉีดเข้าไปใน <bean id = "แหล่งข้อมูล"> เพื่อให้แหล่งข้อมูลของเราสามารถเลือก DataSourceMaster และ DataSourcesLave ตามคีย์ที่แตกต่างกัน
5) ใช้ Spring AOP เพื่อระบุคีย์ของ DataSource ดังนั้น DataSource จะเลือก DataSourceMaster และ DataSourcesLave ตามคีย์:
Package Net.aazj.aop; นำเข้า net.aazj.enums.datasources; นำเข้า net.aazj.util.datasourcetypemanager; นำเข้า org.aspectj.lang.joinpoint; นำเข้า org.aspectj.lang.annotation.aspect; นำเข้า org.aspectj.lang.annotation.before; นำเข้า org.aspectj.lang.annotation.pointcut; นำเข้า org.springframework.stereotype.component; @Aspect // สำหรับ AOP @Component // สำหรับ DataSourceInterceptor Auto Scanpublic คลาส {@pointcut ("การดำเนินการ (สาธารณะ * net.aazj.service .. *. getuser (.. ))") โมฆะสาธารณะ DataSourceslave () {}; @Before ("DataSourceslave ()") โมฆะสาธารณะก่อน (JoinPoint JP) {DataSourCetYpEnager.Set (DataSources.slave); - ที่นี่เรากำหนดคลาสต่างๆ เราใช้ @Before เพื่อเรียก DataSourCetyPeManager.Set (DataSources.slave) ก่อนที่วิธีการปฏิบัติตาม @pointcut ("การดำเนินการ (สาธารณะ * net.aazj.service .. *. getuser (.. ))" ดังนั้นคำสั่ง SQL สำหรับวิธีนี้จะถูกดำเนินการในฐานข้อมูลทาส
เราสามารถขยายแง่มุม DataSourceInterceptor อย่างต่อเนื่องและสร้างคำจำกัดความต่าง ๆ ในนั้นเพื่อระบุแหล่งข้อมูลที่สอดคล้องกับแหล่งข้อมูลที่เหมาะสมสำหรับวิธีการบริการที่แน่นอน
ด้วยวิธีนี้เราสามารถใช้ฟังก์ชั่นที่ทรงพลังของ Spring AOP เพื่อกำหนดค่าได้อย่างยืดหยุ่น
6) การวิเคราะห์หลักการของ abstractroutingDataSource
ThreadlocalrountingDatasource สืบทอด abstractroutingDataSource การใช้วิธีนามธรรมป้องกันวัตถุนามธรรม dectracturrentlookupkey (); ดังนั้นการใช้ฟังก์ชันการกำหนดเส้นทางสำหรับแหล่งข้อมูลที่แตกต่างกัน เริ่มต้นด้วยซอร์สโค้ดเพื่อวิเคราะห์หลักการ:
บทคัดย่อระดับสาธารณะ AbstractroutingDataSource ขยาย AbstractDataSource ดำเนินการเริ่มต้นการเริ่มต้น BeanabstractroutingDataSource ดำเนินการเริ่มต้นเบียน จากนั้นเมื่อฤดูใบไม้ผลิเริ่มต้นถั่วมันจะเรียกอินเทอร์เฟซของช่องว่างการเริ่มต้นเบียน Afterpropertiesset () โยนข้อยกเว้น; มาดูกันว่า abstractroutingDataSource ใช้อินเทอร์เฟซนี้อย่างไร: @Override โมฆะสาธารณะ AfterPropertIesset () {ถ้า (this.targetDataSources == null) {โยน unlegalargumentException ใหม่ ( } this.resolvedDataSources = new hashmap <object, dataSource> (this.targetDatasources.size ()); สำหรับ (map.entry <วัตถุ, วัตถุ> รายการ: this.targetDataSources.entrySet ()) {Object Lookupkey = ResolEvespecifiedLookUpkey (entry.getKey ()); DataSource DataSource = ResolVespecifiedDataSource (entry.getValue ()); this.resolvedDataSources.put (Lookupkey, DataSource); } if (this.defaultTargetDataSource! = null) {this.resolvedDefaultDataSource = ResolVespecifiedDataSource (this.defaultTargetDataSource); - TargetDataSources เป็น DataSourceMaster และ DataSourcesLave ที่เราฉีดเข้าไปในไฟล์การกำหนดค่า XML วิธีการ Afterpropertiesset ถูกฉีด
DataSourceMaster และ DataSourceslave เพื่อสร้าง HASHMAP - ResolvedDataSources มันสะดวกที่จะได้รับแหล่งข้อมูลที่เกี่ยวข้องจากแผนที่ตามคีย์ในภายหลัง
ลองมาดูกันว่าการเชื่อมต่อ getConnection () โยน sqlexception อย่างไร ในส่วนต่อประสาน AbstractDatasource ถูกนำมาใช้:
@Override การเชื่อมต่อสาธารณะ getConnection () พ่น sqlexception {return deMinetArgetDataSource (). getConnection (); -กุญแจสำคัญคือการกำหนดค่าธรรมเนียมการจัดสรรข้อมูล () ซึ่งสามารถมองเห็นได้ตามชื่อวิธีการและเราควรตัดสินใจว่าแหล่งข้อมูลใดที่จะใช้ที่นี่:
DataSource Protection DeCinetArgetDataSource () {assert.notnull (this.resolvedDataSources, "เราเตอร์ DataSource ไม่ได้เริ่มต้น"); Object Lookupkey = deconinecurrentLookupkey (); DataSource DataSource = this.resolvedDataSources.get (lookupkey); if (dataSource == null && (this.lenientfallback || lookupkey == null)) {dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) {โยน unlueLstateException ใหม่ ("ไม่สามารถกำหนดแหล่งข้อมูลเป้าหมายสำหรับคีย์การค้นหา [" + lookupkey + "]"); } ส่งคืน DataSource;} Object Lookupkey = deconinecurrentLookupkey (); วิธีนี้ถูกนำไปใช้โดยเราซึ่งเราได้รับค่าคีย์ที่บันทึกไว้ใน ThreadLocal หลังจากได้รับคีย์ให้ได้แหล่งข้อมูลที่สอดคล้องกับคีย์ในแผนที่ที่เริ่มต้น ResolvedDataSources จาก AfterPropertIesset () ค่าคีย์ที่บันทึกไว้ใน ThreadLocal ถูกตั้งค่าก่อนเรียกใช้วิธีการที่เกี่ยวข้องในบริการผ่าน AOP ตกลงที่นี่เสร็จแล้ว!
3. สรุป
จากบทความนี้เราสามารถสัมผัสกับพลังและความยืดหยุ่นของ AOP
ข้างต้นคือการเรียงลำดับข้อมูลของการประมวลผลแหล่งข้อมูลหลายข้อมูล Mybatis ฉันหวังว่ามันจะช่วยเพื่อนที่ต้องการได้