Le même projet implique parfois plusieurs bases de données, c'est-à-dire plusieurs sources de données. Plusieurs sources de données peuvent être divisées en deux situations:
1) Deux bases de données ou plus n'ont aucune corrélation et sont indépendantes les unes des autres. En fait, cela peut être développé comme deux projets. Par exemple, dans le développement de jeux, une base de données est une base de données de plate-forme, et les autres bases de données correspondant aux jeux sous la plate-forme sont également disponibles;
2) Deux bases de données ou plus sont des relations maître-esclaves, telles que MySQL construit un maître-maître, suivi de plusieurs esclaves; ou copie maître-esclave construite avec MHA;
Actuellement, il existe environ deux façons de construire des sources multiples printanier, et vous pouvez choisir en fonction de la situation des sources multi-données.
1. Utilisez des fichiers de configuration Spring pour configurer directement plusieurs sources de données
Par exemple, en cas de corrélation entre deux bases de données, vous pouvez configurer directement plusieurs sources de données dans le fichier de configuration de Spring, puis effectuer des configurations de transaction, comme indiqué ci-dessous:
<Context: Component-Scan Base-Package = "net.aazj.service, net.aazj.aop" /> <context: composant-scan Base-Package = "net.aazj.aop" /> <! - introduire des fichiers de propriétés -> <contexte: propriété-lieu de location de localisation = "classpath: config / db.properties" /> <! - configurer la source de données -> <ean name = "Datasource" Init-Method = "init" destrement-méthod = "close"> <propriété name = "url" value = "$ {jdbc_url}" /> <propriété name = "username" value = "$ {jdbc_username}" /> <propriété name = "mot de passe" value = "$ {jdbc_password}" /> <! Value = "0" /> <! - Nombre maximum de connexions utilisées par le pool de connexions -> <propriété named = "maxactive" value = "20" /> <! - Nombre maximum de connexions disponibles -> <propriété name = "maxidle" value = "20" /> <! - Nombre minimum de connexions -> <propriété name = "minidle" value = "0" /> <! name = "maxwait" value = "60000" /> </ bean> <bean id = "sqlSessionFactory"> <propriété name = "dataSource" ref = "dataSource" /> <propriété name = "configLocation" value = "classpath: config / mybatis-config.xml" /> <propriété name = "maperlocations" Value = "ClassPath *: config / mappers / ** / *. xml" /> </ank> <! - Manager des transactions pour un seul JDBC DataSource -> <bean id = "TransactionManager"> <propriété name = "DataSource" Ref = "DataSource" /> </Ebrive /> <ean> <propriété name = "basepackage" value = "net.aazj.mapper" /> <propriété name = "sqlSessionFactoryBeAnName" value = "sqlSessionFactory" /> </ank> <! - Active l'utilisation du style @aspectj de Spring AOP -> <aop: aspectj-Autoproxy />Configuration de la deuxième source de données
<bean name = "dataSource_2" init-méthod = "init" destère-méthode = "close"> <propriété name = "url" value = "$ {jdbc_url_2}" /> <propriété name = "username" value = "$ {jdbc_usename_2}" /> <propriété name = "passway" value = "$ {jdbc_pass Initialisez la taille de la connexion -> <propriété name = "initialSize" value = "0" /> <! - Nombre maximum de connexions utilisées dans le pool de connexions -> <propriété name = "maxactive" value = "20" /> <! - maximum inactive de connexion inlassée -> <propriété name = "MAXIDLE" Value = "20" /> <! - Minimum Connection Pool -> <propriété nom = "minIdle" Value = "0" 0 " Temps -> <propriété name = "maxwait" value = "60000" /> </ank> <bean id = "sqlSessionfactory_slave"> <propriété name = "dataSource" ref = "dataSource_2" /> <propriété name = "configLocation" value = "classpath: config / mybatis-config-2.xml" /> <prewet name = "MAPERLOCA Value = "ClassPath *: config / Mappers2 / ** / *. xml" /> </ank> <! - Transaction Manager pour un seul JDBC DataSource -> <Bean Id = "TransactionManager_2"> <propriété Name = "DataSource" Ref = "DataSource_2" /> </ande transaction-manager = "TransactionManager_2" /> <ean> <propriété name = "basepackage" value = "net.aazj.mapper2" /> <propriété name = "sqlSessionFactoryBeAnName" value = "sqlSessionFactory_2" /> </ bean> Comme indiqué ci-dessus, nous configurons deux données de données, deux SQLSessionFactory, deux transactionsManagers et la clé réside dans la configuration de MapperscannerConfigurer - en utilisant la propriété SQLSessionFactoryBame pour injecter des noms SQLSessionFactory différents. De cette façon, nous injectons le SQLSessionFactory correspondant dans les interfaces de mappeur correspondant à différentes bases de données.
Il convient de noter que cette configuration de plusieurs bases de données ne prend pas en charge les transactions distribuées, c'est-à-dire que plusieurs bases de données ne peuvent pas fonctionner dans la même transaction. L'avantage de cette configuration est qu'il est très simple, mais il n'est pas flexible. Il n'est pas très adapté à la configuration de la source multi-données de type maître-esclave. La configuration de la source multi-données de type maître-esclave doit être particulièrement flexible et nécessite une configuration détaillée en fonction du type d'entreprise. Par exemple, pour certaines instructions sélectionnées de temps, nous espérons les mettre sur l'esclave, et pour la mise à jour, la suppression et d'autres opérations, nous ne pouvons les exécuter que sur le maître. De plus, pour certaines déclarations sélectionnées avec des exigences en temps réel élevées, nous devrons également les mettre sur le maître - par exemple, dans un scénario, je vais au centre commercial pour acheter une arme, et l'opération d'achat est certainement le maître. Une fois l'achat terminé, nous devons requérir les armes et les pièces d'or que je possède. Ensuite, cette requête peut également devoir les empêcher d'être exécutées sur le maître et ne peut pas être exécutée sur l'esclave, car il peut y avoir des retards sur l'esclave. Nous ne voulons pas que les joueurs constatent qu'après le succès de l'achat, ils ne peuvent pas trouver d'armes dans le sac à dos.
Par conséquent, pour la configuration de la source multi-données de type maître-esclave, une configuration flexible est requise en fonction de l'entreprise, qui sélectionne peut être placée sur l'esclave et qui sélectionne ne peut pas être placée sur l'esclave. Par conséquent, la configuration ci-dessus de la source de données n'est pas très appropriée.
2. Configuration de la source multi-données basée sur AbstractroutingDatasource et AOP
Le principe de base est que nous définissons une classe de données de données threadLocalroutingDatasource nous-mêmes pour hériter de l'abstractroutingdatasource, puis injecter des sources de données maître et esclaves dans le threadlocalrougnantDatasource dans le fichier de configuration, puis configurer flexible via AOP, où choisir la source de données maître et où choisir les sources de données esclaves. Voyons l'implémentation du code ci-dessous:
1) Définissez d'abord une énumération pour représenter différentes sources de données:
package net.aazj.enums; / ** * Catégorie de données Source: Master / Slave * / Public Enum DataSources {Master, Slave} 2) Utilisez le chef de tête pour enregistrer la clé de la source de données à choisir pour chaque thread:
Package net.aazj.util; importer net.aazj.enums.datasources; classe publique DataSourCetyPeMeager {private static final threadLocal <dataSources> dataSourCetyTys = 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) Définissez le threadlocalroutingDatasource et hériter de l'abstractroutingdatasource:
Package net.aazj.util; import org.springframework.jdbc.datasource.lookup.abstractroutingDataSource; La classe publique ThreadLocalRountingDataSource étend AbstratTroutingDataSource {@Override Protected Object Deter déterminatRentLookUpKey () {return dataSourceTypeManager.get (); }}4) Injecter des sources de données maître et esclaves dans ThreadLocalRountingDataSource dans le fichier de configuration:
<Context: Component-Scan Base-Package = "net.aazj.service, net.aazj.aop" /> <context: composant-scan Base-Package = "net.aazj.aop" /> <! - Introduire des fichiers de propriété -> <context: propriété-localisation localisation = "classpath: config / db.properties" /> <! Init-Method = "init" destrement-méthod = "close"> <propriété name = "url" value = "$ {jdbc_url}" /> <propriété name = "username" value = "$ {jdbc_username}" /> <propriété name = "mot de passe" value = "$ {jdbc_password}" /> <! Value = "0" /> <! - Nombre maximum de connexions utilisées par le pool de connexions -> <propriété named = "maxactive" value = "20" /> <! - Nombre maximum de connexions disponibles -> <propriété name = "maxidle" value = "20" /> <! - Nombre minimum de connexions -> <propriété name = "minidle" value = "0" /> <! name = "maxwait" value = "60000" /> </ bean> <! - Configurer l'esclave de source de données -> <bean name = "dataSourceslave" init-méthod = "init" destre-méthod = "close"> <propriété name = "url" value = "$ {jdbc_url_slave}" /> <propriété name = "userame" Value = "$ {jdbc_username_slave}" /> <propriété name = "mot de passe" value = "$ {jdbc_password_slave}" /> <! - Initialiser la taille de la connexion -> <propriété name = "initialSize" vase connexion = "0" /> <! Pool -> <propriété name = "maxidle" value = "20" /> <! - Pool de connexion inactif minimum -> <propriété name = "minidle" value = "0" /> <! - Obtenez un temps d'attente maximal de connexion -> <beany name = "maxwait" value = "60000" /> </ bean> <bean id = "dataSource"> <propriété Name = "DefaultTargetDatdata /> <propriété name = "TargetDataSources"> <map key-type = "net.aazj.enums.datasources"> <entrée key = "maître" value-ref = "dataSourCemaster" /> <entrée " id = "sqlSessionFactory"> <propriété name = "dataSource" ref = "dataSource" /> <propriété name = "configLocation" value = "classpath: config / mybatis-clonfig.xml" /> <propriété name = "mapperlocations" value = "classpath *: config / mappes / ** / *. xml" /> </ bean> DataSource -> <bean id = "TransactionManager"> <propriété name = "dataSource" ref = "dataSource" /> </ank> <! - Définition des transactions à l'aide de l'annotation -> <tx: annotation-moteur transaction-manager = "transactionManager" /> <ean> <propriété name = "BasEpackage" value = "net.aazj.mapper" /> <- property name = "sqlSessionFactoryBeAnName" value = "sqlSessionFactory" /> -> </ank> Dans le fichier de configuration de printemps ci-dessus, nous définissons respectivement DataSourCemaster et DataSourceslave pour la base de données maître et la base de données des esclaves, puis l'injecter dans <bean id = "dataSource"> afin que notre DataSource puisse sélectionner DataSourceMaster et DataSourceslave selon les différentes touches.
5) Utilisez Spring AOP pour spécifier la clé de DataSource, donc DataSource sélectionnera DataSourCemaster et DataSourceslave en fonction de la clé:
Package net.aazj.aop; importer net.aazj.enums.datasources; importer net.aazj.util.datasourceTypeManager; 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.stereteotype.COMOnT; @Aspect // pour AOP @ composant // pour la classe Auto ScanPublic DataSourceInterceptor {@PointCut ("EXECUTION (public * net.aazj.service .. *. GetUser (..))") public void DataSourceslave () {}; @Before ("DataSourceslave ()") public void avant (joinpoint jp) {dataSourCetyPeMeNager.set (dataSources.slave); } // ...} Ici, nous définissons une classe d'aspect. Nous utilisons @Before pour appeler DataSourceTypeManager.Set (DataSources.slave) avant la méthode conforme à @PointCut ("EXECUTION (public * net.aazj.service .. *. Getuser (..))") est appelé, et le type de clé est défini sur DataSources.slave, So DataSource sélectionnera DataSourceslave selon Key = DataSources. Par conséquent, les instructions SQL pour cette méthode seront exécutées sur la base de données des esclaves.
Nous pouvons développer en continu l'aspect DataSourceInterceptor et y faire diverses définitions pour spécifier la source de données correspondant à la source de données appropriée pour une certaine méthode de service.
De cette façon, nous pouvons utiliser les fonctions puissantes de Spring AOP pour la configurer de manière très flexible.
6) Analyse du principe de l'abstractroutingdatasource
ThreadLocalroutingDataSource hérite d'abstractroutingDataSource, implémentant sa méthode abstraite protégée d'objet abstrait déterminant de déterminer laetLookupkey (); Implémentation ainsi de la fonction de routage pour différentes sources de données. Commençons par le code source pour analyser les principes:
La classe abstraite du public AbstractroutingDataSource étend AbstractDataSource implémente InitializingBeanAbStractracTroutingDataSource implémente InitializationBean. Ensuite, lorsque le printemps initialise le bean, il appellera l'interface de l'initialisation de BEAC void AfterProperTiesset () lève une exception; Voyons comment AbstractroutingDataSource implémente cette interface: @Override public void AfterProperTesTet () {if (this.targetDataSources == null) {Throw New illégalargumentException ("propriété 'cibleDataSources' est requis"); } this.resolveddatasources = new hashmap <objet, dataSource> (this.targetDataSources.size ()); for (map.entry <objet, objet> entrée: this.targetDataSources.EntrySet ()) {objet LookupKey = résolvespecifiedLookupKey (entry.getKey ()); DataSource dataSource = résolvespecifiedDataSource (entrée.getValue ()); this.resolveddatasources.put (LookupKey, dataSource); } if (this.defaultTargetDataSource! = null) {this.resolveddefaultDataSource = résolvespecifiedDataSource (this.defaultTargetDataSource); }} TargetDataSources est le DataSourCemaster et DataSourceslave que nous injectons dans le fichier de configuration XML. La méthode AfterPropertisesset est injectée.
DataSourCemaster et DataSourceslave pour construire un hashmap - ResolvedDataSources. Il est pratique d'obtenir la source de données correspondante à partir de la carte selon la clé ultérieurement.
Jetons un coup d'œil à la façon dont la connexion GetConnection () lève SQLEXception; Dans l'interface AbstractDataSource est implémentée:
@Override Public Connection getConnection () lève sqlexception {return déterminetargetDataSource (). GetConnection (); }La clé consiste à déterminertargetDataSource (), qui peut être vue en fonction du nom de la méthode, et nous devons décider quelle source de données utiliser ici:
DataSource protégé de déterminantargetDataSource () {assert.notnull (this.resolveddatasources, "routeur de données de données non initialisé"); Object LookupKey = déterminatCurrentlookupKey (); DataSource dataSource = this.resolveddatasources.get (lookupkey); if (dataSource == null && (this.leentfallback || lookupkey == null)) {dataSource = this.resolveddefaultDataSource; } if (dataSource == NULL) {Throw New illégalStateException ("Impossible de déterminer la clé de données cible pour la clé de recherche [" + LookupKey + "]"); } return dataSource;} Object LookupKey = déterminatCurrentlookupKey (); Cette méthode est implémentée par nous, où nous obtenons la valeur clé enregistrée dans ThreadLocal. Après avoir obtenu la clé, obtenez la source de données correspondant à la clé de la carte ResolvedDataSources initialisée de AfterProperTeset (). La valeur clé enregistrée dans ThreadLocal est définie avant d'appeler les méthodes pertinentes du service via AOP. Ok, ici c'est fait!
3. Résumé
À partir de cet article, nous pouvons découvrir la puissance et la flexibilité de l'AOP.
Ce qui précède est le tri des informations du traitement des sources multi-données MyBatis. J'espère que cela peut aider les amis dans le besoin