يتضمن المشروع نفسه أحيانًا قواعد بيانات متعددة ، أي مصادر بيانات متعددة. يمكن تقسيم مصادر بيانات متعددة إلى حالتين:
1) لا توجد قواعد بيانات أو أكثر ارتباطًا ومستقلًا عن بعضهما البعض. في الواقع ، يمكن تطوير هذا كمشروعين. على سبيل المثال ، في تطوير اللعبة ، قاعدة بيانات واحدة هي قاعدة بيانات منصة ، وتتوفر أيضًا قواعد البيانات الأخرى المقابلة للألعاب الموجودة تحت النظام الأساسي ؛
2) قواعد بيانات أو أكثر هما علاقات رئيسية ، مثل MySQL تقوم بإنشاء مسيرة رئيسية ، تليها عبيد متعددين ؛ أو نسخة رئيسية ماجستير تم إنشاؤها مع MHA ؛
حاليًا ، هناك طريقتان تقريبًا لبناء مصادر متعددة البيانات الربيعية ، ويمكنك اختيارها وفقًا لحالة مصادر البيانات المتعددة.
1. استخدم ملفات تكوين الربيع لتكوين مصادر بيانات متعددة مباشرة
على سبيل المثال ، في حالة عدم وجود ارتباط بين قواعد البيانات ، يمكنك تكوين مصادر بيانات متعددة مباشرة في ملف تكوين الربيع ثم تنفيذ تكوينات المعاملات ، كما هو موضح أدناه:
<السياق: مكون المسح الضوئي package = "net.aazj.service ، net.aazj.aop" /> <context: component-scan base-package = "net.aazj.aop" /> <!-ملفات الخصائص-> <contex init-method = "init" تدمير method = "close"> <property name = "url" value = "$ {jdbc_url}" /> <property name = "username" value = "$ {jdbc_username}" /> <property name = "password" value = $ {jdbc_password} value = "0" /> <!-الحد الأقصى لعدد الاتصالات المستخدمة من خلال تجمع الاتصال-> <property name = "maxactive" value = "20" /> <!-الحد الأقصى لعدد الاتصالات المتاح-> <property name = "maxidle" value = "20" /> <! name = "maxwait" value = "60000" /> </bean> <bean id = "sqlsessionfactory"> <property name = "dataSource" ref = "dataSource" /> <property name = "configlocation" value = "classpath: config /mybatis-config.xml" /> <property name = "mapperlocations" value = "classpath*: config/mappers/**/*. المعاملات-manager = "TransactionManager"/> <Bean> <property name = "basePackage" value = "net.aazj.mapper"/> <property name = "sqlsessionfactorybeannameتكوين مصدر البيانات الثاني
<bean name = "dataSource_2" init-method = "init" تدمير method = "close"> <property name = "url" value = "$ {jdbc_url_2}" /> <property name = "username" value = "$ {jdbc_username_2}" /> تهيئة حجم الاتصال-> <property name = "initialsize" value = "0" /> <!-الحد الأقصى لعدد الاتصالات المستخدمة في تجمع الاتصال-> <property name = "maxactive" value = "20" /> <! Time-> <property name = "maxwait" value = "60000" /> </bean> <bean id = "sqlsessionfactory_slave"> <property name = "dataSource" ref = "dataSource_2" /> <property name = "configlocation" value = value = "classpath*: config/mappers2/**/*. المعاملات-manager = "TransactionManager_2" /> <Bean> <property name = "basePackage" value = "net.aazj.mapper2" /> <property name = "sqlsessionfactorybeanname" value = "sqlsessionfactory_2" كما هو موضح أعلاه ، نقوم بتكوين اثنين من البيانات ، وهما SQLSessionFactory ، واثنين من المعاملات ، والمفتاح يكمن في تكوين MappersCannerConfigurer - باستخدام خاصية SQLSessionFactoryBeanName لضخ مختلف أسماء SQLSessionFactory. وبهذه الطريقة ، حقن SQLSessionFactory المقابلة في واجهات Mapper المقابلة لقواعد البيانات المختلفة.
تجدر الإشارة إلى أن هذا التكوين لقواعد البيانات المتعددة لا يدعم المعاملات الموزعة ، أي أنه لا يمكن تشغيل قواعد البيانات المتعددة في نفس المعاملة. ميزة هذا التكوين هي أنها بسيطة للغاية ، لكنها ليست مرنة. إنه ليس مناسبًا جدًا لتكوين مصدر البيانات المتعددة من نوع الرقيق. يجب أن يكون تكوين مصدر البيانات المتعددة لنوع العبد الرئيسي مرنًا بشكل خاص ويتطلب تكوينًا مفصلاً وفقًا لنوع الأعمال. على سبيل المثال ، بالنسبة لبعض عبارات SELECT التي تستغرق وقتًا طويلاً ، نأمل أن نضعها على العبد ، وللتحديث ، الحذف والعمليات الأخرى ، يمكننا تنفيذها فقط على السيد. بالإضافة إلى ذلك ، بالنسبة لبعض العبارات المختارة ذات المتطلبات العالية في الوقت الفعلي ، قد نحتاج أيضًا إلى وضعها على السيد - على سبيل المثال ، في سيناريو ، أذهب إلى المركز التجاري لشراء سلاح ، وعملية الشراء هي بالتأكيد الرئيسية. بعد الانتهاء من عملية الشراء ، نحتاج إلى إعادة استعادة الأسلحة والذهبات الذهبية التي أملكها. ثم قد يحتاج هذا الاستعلام أيضًا إلى منعهم من تنفيذها على السيد ، ولا يمكن تنفيذها على العبد ، لأنه قد يكون هناك تأخير على العبد. لا نريد أن يجد اللاعبون أنه بعد النجاح ، لا يمكنهم العثور على أسلحة في حقيبة الظهر.
لذلك ، من أجل تكوين مصدر Master-Slave Multi-Data ، يلزم تكوين مرن وفقًا للشركة ، والتي يمكن وضعها على العبد ، والتي لا يمكن وضعها على العبد. لذلك ، فإن التكوين أعلاه لمصدر البيانات غير مناسب للغاية.
2. تكوين مصدر بيانات متعددة على أساس AbstractRoutingDataSource و AOP
المبدأ الأساسي هو أننا نحدد مرأس DataSource Class ThreadLocalRountingDataSource أنفسنا لروث ArgtRactractRoutingDataSource ، ثم حقن مصادر بيانات Master و Slave في مصادر بيانات ThreadLocalRountingDataSource في ملف التكوين ، ثم تكوينها بمرونة من خلال AOP ، وحيث لاختيار مصدر بيانات Slave وحيث لاختيار. لنرى تطبيق الكود أدناه:
1) حدد أولاً التعداد لتمثيل مصادر البيانات المختلفة:
حزمة net.aazj.enums ؛ /** * فئة البيانات المصدر: Master/Slave */public enum datasources {master ، slave} 2) استخدم headlocal لحفظ مفتاح مصدر البيانات لاختيار كل مؤشر ترابط:
حزمة net.aazj.util ؛ استيراد net.aazj.enums.datasources ؛ الفئة العامة dataSourCeTypEmanager {private static final threadlocal <DataSources> dataSourCeTypes = new threadlocal <DataSources> () {Override DataSources initialValue () {return datasources.master ؛ }} ؛ get () get () {return datasourcetypes.get () ؛ } مجموعة الفراغ الثابتة العامة (DataSources DataSourCeType) {datasourcetypes.set (datasourcetype) ؛ } reset static static void () {dataSourCeTypes.set (datasources.master0) ؛ }} 3) تعريف threadlocalRountingDataSource وراثة abrtractractroutingdataSource:
حزمة net.aazj.util ؛ استيراد org.springframework.jdbc.datasource.lookup.abstractroutingdataSource ؛ الفئة العامة threadlocalRountingDataSource يمتد ArgtRactRoutingDataSource {Override كائن محمي DETERNECURRENTOFKUPKEY () }}4) حقن مصادر البيانات الرئيسية والعبد في ThreadLocalRountingDataSource في ملف التكوين:
<السياق: مكون-المسح الضوئي package = "net.aazj.service ، net.aazj.aop" /> <context: component-scan base-package = "net.aazj.aop" /> <!-conferuce property-> <contex name = "dataSourCemaster" init-method = "init" تدمير method = "close"> <property name = "url" value = "$ {jdbc_url}" /> <property name = "username" value = "$ {jdbc_username}" /> <property name = "value" name = "initialsize" value = "0" /> <!-الحد الأقصى لعدد الاتصالات المستخدمة من خلال تجمع الاتصال-> <property name = "maxactive" value = "20" /> <! <property name = "maxwait" value = "60000" /> </bean> <!-تكوين Slave Source Source-> <bean name = "dataSourceSlave" init-method = "init" DRIDE-Method = "close"> <property name = "url" value = "$ {jdbc_url_slave} /> <propert value = "$ {jdbc_username_slave}" /> <property name = "password" value = "$ {jdbc_password_slave}" /> <!-تهيئة حجم الاتصال-> <property name = "initialsize" value = "0" /> <! pool-> <property name = "maxidle" value = "20" /> <! /> <property name = "targetDataSources"> <map key-type = "net.aazj.enums.datasources"> <interpt key = "master" value-ref = "dataSourCemaster"/> <intring key = "slave" value-ref = "datasourceslave"/> <!-يمكنك إضافة dataSources هنا- id = "sqlsessionfactory"> <property name = "dataSource" ref = "datasource"/> <property name = "configlocation" value = "classpath: config/mybatis-config.xml"/> <property name = "mapperlocations" value = DataSource-> <bean id = "transactionManager"> <property name = "datasource" ref = "datasource" /> </bean> <!-تعريف المعاملات باستخدام التعليق التوضيحي-> <tx: step-driven revalive = armapper = "keformanager" name = "sqlsessionfactorybeanname" value = "sqlsessionfactory"/> -> </bean> في ملف تكوين الربيع أعلاه ، نقوم بتحديد DataSourCeMaster و DataSourceSlave لقاعدة البيانات الرئيسية وقاعدة بيانات الرقيق على التوالي ، ثم حقنها في <bean id = "datasource"> حتى يتمكن مصدر البيانات الخاص بنا
5) استخدم Spring AOP لتحديد مفتاح مصدر البيانات ، لذلك سيقوم DataSource بتحديد DataSourCemaster و DataSourceSlave وفقًا للمفتاح:
حزمة net.aazj.aop ؛ استيراد net.aazj.enums.datasources ؛ استيراد net.aazj.util.datasourcetypemanager ؛ استيراد org.aspectj.lang.joinpoint ؛ import org.aspectj.lang.annotation.Aspect ؛ import org.aspectj.lang.annotation.beore ؛ import org.aspectj.lang.annotation.pointcut ؛ import org.springframework.stereotypre.component.component ؛ Aspect // for aop @component // for auto scanpublic class dataSourceInterceptor {pointCut ("التنفيذ (public * net.aazj.service .. *. before ("datasourceLave ()") void public قبل (JoinPoint jp) {datasourCeTypEmanager.set (datasources.slave) ؛ } // ...} هنا نحدد فئة الجانب. نستخدم @قبل استدعاء datasourcetypemanager.set (datasources.slave) قبل الطريقة في الامتثال لـ @pointcut ("التنفيذ (public * net.aazj.service .. *. لذلك ، سيتم تنفيذ عبارات SQL لهذه الطريقة على قاعدة بيانات الرقيق.
يمكننا بشكل مستمر توسيع جانب DataSourceInterceptor ، وتقديم تعريفات مختلفة فيه لتحديد مصدر البيانات المقابل لمصدر البيانات المناسب لطريقة خدمة معينة.
وبهذه الطريقة ، يمكننا استخدام الوظائف القوية لـ Spring AOP لتكوينها بمرونة شديدة.
6) تحليل مبدأ AbstRactRoutingDataSource
ThreadLocalRountingDataSource ويرث AbstRactRactRoutingDataSource ، وتنفيذ طريقة التجريد المحمية الكائن التجريدي المحمي DETERNECURRENTOLKUPKEY () ؛ وبالتالي تنفيذ وظيفة التوجيه لمصادر البيانات المختلفة. لنبدأ بالرمز المصدري لتحليل المبادئ:
الطبقة التجريدية العامة AbstractRoutingDataSource يمتد AbstractDataSource الأدوات التهيئة initializingbeanabstractractroutingdatasource الأدوات التهيئة. ثم عندما يقوم Spring بتهيئة الفاصوليا ، فإنه سيطلق على واجهة intivalizingbean void بعد propertiesset () استثناء ؛ دعونا نرى كيف أن AbstractRoutingDataSource تنفذ هذه الواجهة: Override public void بعد propertiesset () {if (this.targetDataSources == null) {رمي new alficalArgumentException ("property 'targetdatasources' مطلوب") ؛ } this.resolvedDatasources = new hashmap <object ، datasource> (this.targetDatasources.size ()) ؛ لـ (map.entry <object ، Object> inter: this.targetDatasources.entrySet ()) {Object lookupkey = releDspecifiedLookupKey (intrad.getKey ()) ؛ DataSource DataSource = solvespecifiedDataSource (Entpl.getValue ()) ؛ this.resolvedDataSources.put (lookupkey ، dataSource) ؛ } if (this.defaultTargetDataSource! = null) {this.resolvedDefaultDataSource = desolvespecifiedDataSource (this.defaultTargetDataSource) ؛ }} TargetDataSources هو DataSourCeMaster و DataSourceSlave الذي حقنه في ملف تكوين XML. يتم حقن طريقة ما بعد بروبتيست.
DataSourCemaster و DataSourceSlave لبناء hashmap - ResolvedDataSources. من المريح الحصول على بيانات البيانات المقابلة من الخريطة وفقًا للمفتاح لاحقًا.
دعونا نلقي نظرة على كيفية رمي connection getConnection () sqlexception ؛ في واجهة AbstractDataSource يتم تنفيذها:
Override Public Connection getConnection () يلقي sqlexception {return decondArgetDatasource (). getConnection () ؛ }المفتاح هو تحديد areetargetDataSource () ، والذي يمكن رؤيته بناءً على اسم الطريقة ، ويجب أن نقرر أي بيانات يجب استخدامها هنا:
DataSource DETERNETARGETDATASOURCE () {Assert.notnull (this.resolvedDataSources ، "DataSource Router غير تهيئة") ؛ Object lookupkey = decondInecurrentLookupKey () ؛ datasource dataSource = this.resolvedDatasources.get (lookupkey) ؛ if (datasource == null && (this.lenientfallback || lookupkey == null)) {datasource = this.resolvedDefaultDataSource ؛ } if (dataSource == null) {رمي جديد alficalstateException ("لا يمكن تحديد مصدر البيانات المستهدف لمفتاح البحث [" + lookupkey + "]") ؛ } إرجاع مصدر البيانات ؛} Object lookupkey = decondInecurrentLookupKey () ؛ يتم تنفيذ هذه الطريقة من قبلنا ، حيث نحصل على القيمة الرئيسية المحفوظة في ThreadLocal. بعد الحصول على المفتاح ، احصل على مصدر البيانات المقابل للمفتاح في الخريطة المهيئة ResolvedDataSources من بعد propertiesset (). يتم تعيين القيمة الرئيسية المحفوظة في ThreadLocal قبل استدعاء الأساليب ذات الصلة في الخدمة من خلال AOP. حسنًا ، هنا يتم!
3. ملخص
من هذه المقالة ، يمكننا تجربة قوة ومرونة AOP.
ما سبق هو فرز المعلومات لمعالجة مصدر MyBatis Multi-Data. آمل أن تساعد الأصدقاء المحتاجين