Kürzlich arbeiten wir an SaaS -Anwendungen. Die Datenbank übernimmt eine einzelne Instanz-Multi-Schema-Architektur (siehe Referenz 1 für Einzelheiten). Jeder Mieter verfügt über ein unabhängiges Schema, und die gesamte Datenquelle hat ein gemeinsames Schema. Daher ist es erforderlich, das Problem der dynamischen Addition und des Löschens und des Schaltens von Datenquellen zu lösen.
Nach der Suche nach vielen Artikeln online sprechen viele von ihnen über die Konfiguration der Master-Slave-Datenquelle oder haben die Datenquellenkonfiguration vor Beginn der Anwendung bestimmt und selten darüber sprechen, wie die Datenquelle dynamisch geladen werden kann, ohne zu schließen. Deshalb habe ich diesen Artikel als Referenz geschrieben.
Techniken verwendet
Ideen
Wenn eine Anfrage eingeht, bestimmen Sie den Mieter, zu dem der aktuelle Benutzer gehört, und wechseln Sie zu der entsprechenden Datenquelle basierend auf den Mieterinformationen und führen Sie dann nachfolgende Geschäftsabläufe durch.
Code -Implementierung
TenantConfigEntity (Mieterinformationen) @EqualsandhashCode (Calluper = False)@Data@fieldDefaults (Level = AccessLevel.Private) öffentliche Klasse Tenantconfigentity { / ***Mieter -ID ** / Integer Tenantid; / ***Mietername **/ String TenantName; / ***Mietername Key **/ String Tenantkey; / ***Datenbank -URL **/ String dburl; / ***Datenbank Benutzername **/ String DBUSER; / ***Datenbankkennwort **/ String dbpassword; / ***Database public_key **/ String dbpublickey;} dataSourceutil (ADAL-TOOL-Klasse, nicht wesentlich) öffentliche Klasse DataSourceutil {private statische endgültige String data_source_bean_key_suffix = "_data_source"; private statische endgültige Zeichenfolge jdbc_url_args = "? useUnicode = true & charakterencodierung = utf-8 & uswendaliasmetadaTabehavior = true & Zerodatetimebehavior = convertonull"; private statische endgültige String Connection_properties = "config.decrypt = true; config.decrypt.key ="; / ** * Spring Bean Key zum Spleißen von Datenquellen */ public static String getDataSourcebeankey (String Tenantkey) {if (! Stringutils.hastext (Tenantkey)) {return null; } return meenantkey + data_source_bean_key_suffix; } / ** * Spleißen komplett jdbc url * / public static String getJdbcurl (String BaseUrl) {if (! Stringutils.hastext (BaseUrl)) {return null; } return baseUrl + jdbc_url_args; } / *** Spleiß komplette Druidenverbindungseigenschaften* / public static String getConnectionProperties (String PublicKey) {if (! Stringutils.hastext (publicKey)) {return null; } return Connection_Properties + PublicKey; }}DataSourceContextHolder
Verwenden Sie ThreadLocal, um den Namen des Datenquellenschlüssels des aktuellen Threads zu speichern und die Methoden zu implementieren, abzahlen und zu klären.
public class DataSourceContextHolder {private statische endgültige ThreadLocal <string> dataSourceKey = new inheritabletHeadLocal <> (); public static void setDataSourceKey (String Tenantkey) {DataSourceKey.set (Tenantkey); } public static String getDataSourceKey () {return DataSourceKey.get (); } public static void clearDataSourceKey () {DataSourceKey.remove (); }}DynamicDataSource (Schlüsselpunkt)
Inherit AbstractroutingDataSource (es wird empfohlen, seinen Quellcode zu lesen, um den Prozess des dynamischen Schaltens von Datenquellen zu verstehen) und die dynamische Auswahl von Datenquellen zu realisieren.
Public Class DynamicDataSource erweitert AbstractroutingDataSource {@autowired private applicationContext applicationContext; @Lazy @autowired Private dynamicDataSourcesummoner SUPSUMER; @Lazy @autowired Private Mieterconfigdao Mieterconfigdao; @Override Protected String DeterInecurrentLookupkey () {String mesantkey = dataSourceContextHolder.getDataSourceKey (); return DataSourceutil.getDatasourcebeankey (Tenantkey); } @Override Protected DataSource DeterInetargetDataSource () {String mesantkey = datasourceContexTHolder.getDataSourceKey (); String beankey = dataSourceutil.getDataSourcebeankey (Tenantkey); if (! Stringutils.hastext (Tenantkey) || applicationContext.ContainsBean (Beankey)) {return Super.DeterminTargetDataSource (); } if (mandantconfigdao.EXIST (Menstkey)) {Summe.registerDynamicDataSources (); } return Super.DeterminTargetDataSource (); }}DynamicDataSourcesummoner (Schlüsselpunkt des Fokus)
Laden Sie die Datenquelleninformationen aus der Datenbank und montieren und registrieren Sie die Federbohnen dynamisch.
@Slf4j@componentpublic class dynamicDataSourcesummoner implementiert applicationListener <contexTreFreshedEvent> {// Übereinstimmung mit der Standarddatenquellen-ID von Spring-Data-source.xml private statische String default_data_source_bean_key = "DefaultDataSource"; @Autowired private configurableApplicationContext applicationContext; @Autowired Private dynamicDataSource dynamicDataSource; @Autowired Private MieterConfigdao Tenantconfigdao; privat statischer Booleschen geladen = falsch; / *** Ausführen nach Abschluss der Federlast try {registerDynamicDataSources (); } catch (Ausnahme e) {log.Error ("Datenquelleninitialisierung fehlgeschlagen, Ausnahme:", e); }}}} / *** Lesen Sie die DB -Konfiguration des Mieters aus der Datenbank und injizieren Sie dynamisch den Spring Container* / public void RegisterDynamicDataSources () {// Die DB -Konfiguration für alle Mieterliste <Tenantconfigentity> TenantConfigentities = TenantconfigdaO.Listall (); if (collectionUtils.isempty (Tenantconfigentities)) {Wirf neu illegalStateException ("Anwendungsinitialisierung fehlgeschlagen, bitte konfigurieren Sie die Datenquelle zuerst"); } // Registrieren Sie die Datenquellen -Bean im Container -AddDataSourcebeans (Tenantconfigentities); } / *** Erstellen Sie Beans basierend auf DataSource und Registrieren Sie sich im Container* / private void addDataSourcebeans (Liste <TenantConfigentity> TenantConfigentities) {MAP <Objekt, Objekt> targetDataSources = Maps.NewlinkedHasMap (); DefaultListableBeanFactory beanfactory = (StandardListableBeanFactory) applicationContext.getAutoWirecapelableBeanFactory (); für (TenantConfigentity Entity: TenantConfigentities) {String beankey = dataSourceutil.getDataSourcebeankey (entity.getTenantKey ()); // Wenn die Datenquelle im Frühjahr registriert wurde, registrieren Sie nicht, ob (applicationContext.containsBean (Beankey)) {druidDataSource existDataSource = applicationContext.getbean (Beankey, druiddataSource.class); if (isSamedatasource (existDataSource, Entity)) {Fortsetzung; }} // Bean AbstractbeanDeFinition beaandefinition = getbeandefinition (Entity, Beankey) zusammenstellen; // beanfactory.registerbeandefinition (Beankey, BeanDefinition) registrieren; // In die Karte einfügen, beachten Sie, dass das Bean -Objekt gerade jetzt targetDataSources.put (Beankey, applicationContext.getbean (Beankey)) erstellt wurde; } // Setzen Sie das erstellte Kartenobjekt auf targetDataSources; dynamicDataSource.settargetDataSources (targetDataSources); // Dieser Vorgang muss durchgeführt werden, bevor die AbstractroutingDataSource auf diese Weise wieder initialisiert wird. } / ** * Datenquelle Spring Bean * / Private AbstractBeAndeFinition getBeAndeFinition (TenantConFigentity Entity, String beankey) {beandefinitionbuilder builder = beandefinitionBuilder.generdeNdeFinition (DruidDataSource.Cllass); builder.getbeandefinition (). setAttribute ("id", Beankey); // Andere Konfigurationen erben defaultDataSource Builder.setParentName (default_data_source_bean_key); Builder.SetInitMethodName ("init"); Builder.SetDestroyMethodName ("Close"); Builder.AddpropertyValue ("Name", Beankey); builder.addpropertyValue ("url", dataSourceutil.getJdbcurl (entity.getDburl ())); Builder.AddpropertyValue ("Benutzername", entity.getDbuser ()); Builder.AddPropertyValue ("Passwort", entity.getDbpassword ()); builder.addpropertyValue ("ConnectionProperties", DataSourceutil.getConnectionProperties (entity.getDbPublickey ())); return Builder.getBeandefinition (); } / *** Bestimmen Sie, ob die Datenbank im Frühjahrscontainer mit den Datenbankinformationen der Datenbank übereinstimmt. Objects.equals (existDataSource.geturl (), DataSourceutil.getJdbcurl (entity.getDburl ())); if (! sameUrl) {return false; } boolean sameUser = objects.equals (existDataSource.getUnername (), entity.getDbuser ()); if (! sameUser) {return false; } try {String decryptPassword = configTools.decrypt (entity.getDbPublickey (), entity.getDbpassword ()); return objects.equals (existDataSource.getPassword (), decryptPassword); } catch (Ausnahme e) {log.Error ("Datenquellenkennwortverifizierung fehlgeschlagen, Ausnahme: {}", e); false zurückgeben; }}}FRINDE-DATA-SOURCE.XML
<!-- Introduce jdbc configuration file --> <context:property-placeholder location="classpath:data.properties" ignore-unresolvable="true"/> <!-- Public (default) data source --> <bean id="defaultDataSource" init-method="init" destroy-method="close"> <!-- Basic properties url, user, password --> <property name="url" value = "$ {ds.jdbcurl}" /> <Eigenschaft name = "userername" value = "$ {ds.user}" /> <Eigenschaft name = "password" value = "{ds.Password}" /> <!-Initialisierungsgröße, Minimum und Maximum-> <Eigenschaft ". NAME = "MaxActive" value = "10" /> <!-Konfigurieren Sie die Zeit, um die Verbindung zur Zeitüberschreitung in Millisekunden zu warten-> <Eigenschaftsname = "maxwait" value = "1000" /> <! Erkennen Sie einmal die Leerlaufverbindung, die in Millisekunden geschlossen werden muss. Um im Pool zu überleben, in Millisekunden-> <Property name = "mineVictableIdletImemillis" value = "240000" /> <Eigenschaft name = "validationQuery" value = "select 1" /> <!-Einheit: Sekunden: Sekundenzeit, Zeitüberschreitungszeit, um festzustellen, ob die Verbindung gültig ist. Führen Sie bei der Beantragung einer Verbindung, wenn die Leerlaufzeit größer als Zeitversuche ist, dass ValidationsQuery erfasst, ob die Verbindung gültig ist. Diese Konfiguration verringert die Leistung. -> <Eigenschaft name = "testonborrow" value = "true" /> <!-Führen Sie die ValidationQuery aus, wenn die Verbindung zurückgegeben wird, um zu überprüfen, ob die Verbindung gültig ist. Durch diese Konfiguration wird die Leistung verringert. -> <Eigenschaft name = "testonReturn" value = "false" /> <!-config Filter-> <Eigenschaft name = "filter" value = "config" /> <Eigenschaft name = "ConnectionProperties" value = "config.Decrypt = true; config.Decrypt.Key = $ {ds.publizey}" /> < /> < /> < /> < /lean> <!--ungen> <!--ungen> <!--unger "-lean <! name = "dataSource" ref = "multiplydatasource"/> </bean> <!-Multi-Data-Quelle-> <bean id = "multiplydatasource"> <Eigenschaft name = "defaultTargetDataSource" ref = "defaultDataSource"/> < value-ref = "defaultDataSource"/> </map> </property> </bean> <!-Annotation Transaction Manager-> <! <bean id = "mainSQLSessionFactory"> <Eigenschaft name = "dataSource" ref = "multiplydatasource"/> </bean> <!-Der Paketname, in dem sich die Dao-Schnittstelle befindet, findet der Frühling den DAO automatisch unter IT-> <bean id = "MainSQLMapper"> <Eigenschaftsname = " value = "mainSQLSessionFactory"/> <Eigenschaft name = "basepackage" value = "abc*.dao"/> </bean> <bean id = "defaultSQLSessionFactory"> <Eigenschaft name = "dataSource" ref = "defaultDataSource"/> </bean> <Ente id = "Default id =" name = "sqlSessionFactoryBeanname" value = "defaultSQLSessionFactory"/> <Eigenschaft name = "Basepackage" value = "abcbase.dal.dao"/> </bean> <!-Andere Konfiguration weggelassen->DynamicDataSourcepectAdvice
Wechseln Sie die Datenquellen automatisch mit AOP nur als Referenz.
@Slf4J@Aspekt@component@order (1) // Bitte beachten Sie: Die Reihenfolge hier muss geringer sein als die Reihenfolge von TX: Annotationsgetrieben. @Around ("Execution (*abc*.Controller.*.*(..)") öffentliches Objekt Doaround (ProceedingJoInpoint JP) löscht Throwable {servletRequestAttributes sra = (servletRequestAtributes) an RequestContexTholder.getRequestatTributes (); HttpServletRequest request = sra.getRequest (); HttpServletResponse response = sra.getResponse (); String mesantkey = Request.Getheader ("Mieter"); // Das Front-End muss in den Mieter-Header übergehen, ansonsten werden 400 zurückgegeben, wenn (! Stringutils.hastext (Tenantkey)) {webutils.tohttp (Antwort) .SendError (httpServletResponse.sc_bad_request); null zurückkehren; } log.info ("aktueller Mieterschlüssel: {}", Tenantkey); DataSourceContextHolder.SetDataSourceKey (Tenantkey); Objektergebnis = JP.Procece (); DataSourceContextHolder.ClearDataSourceKey (); Rückgabeergebnis; }}Zusammenfassen
Die oben genannte Implementierungsmethode für die dynamische Registrierung mehrerer Datenquellen der Springdynamik ist vom Editor eingeführten Datenquellen. Ich hoffe, es wird für alle hilfreich sein. Wenn Sie Fragen haben, hinterlassen Sie mir bitte eine Nachricht und der Editor wird allen rechtzeitig antworten. Vielen Dank für Ihre Unterstützung auf der Wulin.com -Website!