El mismo proyecto a veces involucra múltiples bases de datos, es decir, múltiples fuentes de datos. Múltiples fuentes de datos se pueden dividir en dos situaciones:
1) Dos o más bases de datos no tienen correlación y son independientes entre sí. De hecho, esto puede desarrollarse como dos proyectos. Por ejemplo, en el desarrollo de juegos, una base de datos es una base de datos de plataforma, y las otras bases de datos correspondientes a los juegos bajo la plataforma también están disponibles;
2) Dos o más bases de datos son relaciones maestras-esclavo, como MySQL construye un maestro maestro, seguido de múltiples esclavos; o copia maestra-esclavo construida con MHA;
Actualmente, hay aproximadamente dos formas de construir fuentes de múltiples datos de primavera, y puede elegir de acuerdo con la situación de las fuentes de múltiples datos.
1. Use archivos de configuración de Spring para configurar directamente múltiples fuentes de datos
Por ejemplo, en el caso de NO correlación entre dos bases de datos, puede configurar directamente múltiples fuentes de datos en el archivo de configuración de Spring y luego realizar configuraciones de transacciones, como se muestra a continuación:
<context: component-scan base-package = "net.aazj.service, net.aazj.aop" /> <context: component-scan base-package = "net.aazj.aop" /> <!-Introducir archivos de propiedades-> <context: Property-placeholder ubicación = "classpath: config /db.properties" /> <!-Configure la fuente de datos-> <name de bean-placeheder "DataSource" init-method = "init" destruye-method = "cierre"> <propiedad name = "url" value = "$ {jdbc_url}" /> <propiedad name = "username" value = "$ {jdbc_username}" /> <propiedad name = "contraseña" value = "$ {jdbc_password}" /> <!-inicialize the Connection size-> <sepsions "stialsize" $ {jdbc_password} " /> <!-Inicialize the Connection size-> <sertain n. valor = "0" /> <!-Número máximo de conexiones utilizadas por el grupo de conexión-> <propiedad name = "maxactive" value = "20" /> <!-Número máximo de conexiones disponibles-> <name de propiedad = "maxidle" value = "20" /> <!-Número mínimo de conexiones disponibles-> <nombre de propiedad = "minidle" valor = "0" /> <!-Get el máximo de espera de espera para la espera- name = "maxwait" value = "60000" /> < /bean> <bean id = "sqlsessionFactory"> <propiedad name = "dataSource" ref = "dataSource" /> <propiedad name = "configlocation" valor = "classpath: config /mybatis-config.xml" /> <name de propiedad = "mapperLocations" "" "" "" "" "MAPPERLOCATIONS" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""ótorificar" "" "" "" "" "" "" value = "classpath*: config/mappers/**/*. xml"/> </bean> <!-Transaction Manager para un solo JDBC DataSource-> <Bean ID = "TransactionManager"> <Property Name = "DataSource" Ref = "DataSource"/> </Bean> <!-Define Transactions Using Annotation-> <tx: annotation-Driven -Manager " /> <Bean> <Property Name = "BasePackage" Value = "net.aazj.mapper"/> <Property name = "sqlSessionFactoryName" value = "sqlsessionfactory"/> </ bean> <!-permite el uso del estilo @Aspectj de spring aop-> <aop: aspectoJ-AUTOPROXY />Configuración de la segunda fuente de datos
<bean name = "dataSource_2" init-method = "init" destruye-method = "cerrar"> <propiedad name = "url" value = "$ {jdbc_url_2}" /> <propiedad name = "username" value = "$ {jdbc_userne_2}" /> <name de propiedad = "contraseña" valor = "$ {jdbc_passwort Inicialice el tamaño de conexión-> <propiedad name = "inicialSize" value = "0" /> <!-Número máximo de conexiones utilizadas en el grupo de conexión-> <propiedad name = "maxactive" value = "20" /> <!-Máximo de conexión inactiva Pool-> <Nombre de propiedad = "Maxidle" Valor = "20" /> < Tiempo-> <Property name = "maxwait" value = "60000" /> < /bean> <bean id = "sqlsessionfactory_slave"> <propiedad name = "dataSource" ref = "dataSource_2" /> <Property name = "configLocation" value = "classPath: config /myBatis-conconfig-25.xml" /> <name = "mApperLocations" mApperlocations " value = "classpath*: config/mappers2/**/*. xml"/> </bean> <!-Manager de transacciones para un solo JDBC DataSource-> <bean id = "TransactionManager_2"> <Property Name = "DataSource" ref = "dataSource_2"/> </beon> < Transaction-Manager = "TransActionManager_2" /> <Bean> <Property Name = "BasePackage" value = "net.aazj.mapper2" /> <propiedad name = "sqlsessionFactoryName" value = "sqlSessionFactory_2" /> </ bean> Como se muestra anteriormente, configuramos dos Cources, dos SQLSessionFactory, dos Managentes de transacciones y la clave radica en la configuración de MappersCannerConfigurer, utilizando la propiedad SQLSessionFactoryName para inyectar diferentes nombres SQLSessionFactory. De esta manera, inyectamos el SQLSessionFactory correspondiente en las interfaces mapeadores correspondientes a diferentes bases de datos.
Cabe señalar que esta configuración de múltiples bases de datos no admite transacciones distribuidas, es decir, múltiples bases de datos no se pueden operar en la misma transacción. La ventaja de esta configuración es que es muy simple, pero no es flexible. No es muy adecuado para la configuración de la fuente multidatos de tipo de esclavo maestro. La configuración de la fuente multidatos de tipo de esclavo maestro debe ser particularmente flexible y requiere una configuración detallada de acuerdo con el tipo de negocio. Por ejemplo, para algunas declaraciones selectas que requieren mucho tiempo, esperamos ponerlas en el esclavo y para actualizar, eliminar y otras operaciones, solo podemos ejecutarlas en el maestro. Además, para algunas declaraciones seleccionadas con altos requisitos en tiempo real, es posible que también necesitemos ponerlas en el maestro, por ejemplo, en un escenario, voy al centro comercial a comprar un arma, y la operación de compra es definitivamente el maestro. Después de completar la compra, necesitamos volver a considerar las armas y las monedas de oro que poseo. Entonces, esta consulta también puede necesitar evitar que se ejecuten en el maestro, y no se puede ejecutar en el esclavo, porque puede haber demoras en el esclavo. No queremos que los jugadores encuentren que después de que la compra sea exitosa, no pueden encontrar armas en la mochila.
Por lo tanto, para la configuración de la fuente de múltiples datos de tipo master-esclavo, se requiere una configuración flexible de acuerdo con el negocio, que selecciona se puede colocar en el esclavo, y que selecciona no se puede colocar en el esclavo. Por lo tanto, la configuración anterior de la fuente de datos no es muy adecuada.
2. Configuración de la fuente de datos múltiples basada en AbstrutingDataSource y AOP
El principio básico es que definimos una clase DataSource ThreadLocalRountingingDataSource nosotros mismos para heredar AbstracTroutingDataSource, y luego inyectar fuentes de datos maestros y esclavos en ThreadLocalrountingDataSource en el archivo de configuración, y luego configurar de manera flexible a través de AOP, dónde elegir la fuente de datos maestro y dónde elegir la fuente de datos de esclavos. Veamos la implementación del código a continuación:
1) Primero defina un enum para representar diferentes fuentes de datos:
paquete net.aazj.enums; /** * Categoría de Fuente de datos: Master/Slave */public enum DataSources {Master, Slave} 2) Use el encabezado para guardar la clave de qué fuente de datos elegir para cada subproceso:
paquete net.aazj.util; importar net.aazj.enums.dataSources; Public Class DataSourCetyPemanager {Private Static final ThreadLocal <Stasources> DataSourCetyPes = new ThreadLocal <DataSources> () {@Override DataSources InitialValue () {return dataSources.master; }}; public static dataSources get () {return dataSourCetyPes.get (); } set public static void (DataSources DataSourCeType) {DataSourCetypes.set (DataSourCetype); } public static void reset () {dataSourCetypes.set (DataSources.Master0); }} 3) Definir ThreadLocalRountingDataSource y heredar AbstrutingDataSource:
paquete net.aazj.util; importar org.springframework.jdbc.dataSource.lookup.AbstracTroutingDataSource; public class ThreadLocalRountingDataSource extiende AbstrutingDataSource {@Override Object DetdetInECRENTLOVEWPKEY () {return dataSourCetyPemanager.get (); }}4) Inyectar fuentes de datos maestros y esclavos en ThreadLocalRontingDataSource en el archivo de configuración:
<context: component-scan base-package = "net.aazj.service, net.aazj.aop" /> <context: component-scan base-package = "net.aazj.aop" /> <!-Introducir archivos de propiedades-> <context: Property-placeLoation = "classpath: config /db.properties" /> <!-Configure los archivos de la fuente de datos-> <context: dataTeation-placeholeater dataUrcemter: config /db.properties " /> <!-Configure la fuente de datos-> <ruple nameaster =" DataSter init-method = "init" destruye-method = "cierre"> <propiedad name = "url" value = "$ {jdbc_url}" /> <propiedad name = "username" value = "$ {jdbc_username}" /> <propiedad name = "contraseña" value = "$ {jdbc_password}" /> <!-inicialize the Connection size-> <sepsions "stialsize" $ {jdbc_password} " /> <!-Inicialize the Connection size-> <sertain n. valor = "0" /> <!-Número máximo de conexiones utilizadas por el grupo de conexión-> <propiedad name = "maxactive" value = "20" /> <!-Número máximo de conexiones disponibles-> <name de propiedad = "maxidle" value = "20" /> <!-Número mínimo de conexiones disponibles-> <nombre de propiedad = "minidle" valor = "0" /> <!-Get el máximo de espera de espera para la espera- name = "maxwait" value = "60000" /> < /bean> <!-Configure Data Source Slave-> <Bean Name = "DataSourcesSlave" init-Method = "init" Destroy-Method = "Close"> <Property Name = "Url" Value = "$ {JDBC_URL_SLAVE}" /> <Nombre de propiedad = "Username" " valor = "$ {jdbc_username_slave}" /> <propiedad name = "contraseña" value = "$ {jdbc_password_slave}" /> <!-inicializar el tamaño de la conexión-> <propiedad name = "inicial" valor = "0" /> <!-Máximo número de conexiones utilizados por el grupo de conexión-> <name de propiedad = "mAxActive" value = "20" 20 " /" 20 " <propiedad de propiedad = "maxidle" valor = "20" /> <!-grupo de conexión mínima inactiva-> <propiedad name = "minidle" value = "0" /> <!-Obtener tiempo máximo de espera de espera tiempo-> <propiedad de propiedad = "maxwait" value = "60000" /> < /bean> <bean id = "dataSource"> <name de propiedad = "nombre de propiedad =" name de propietaria = "refinicio" ref = "ref =" dataMaster " name = "TargetDataSources"> <map key-type = "net.aazj.enums.dataSources"> <entry key = "maestro" value-ref = "dataSourcEMaster"/> <entry key = "slave" value-ref = "dataSourceSlave"/> <!-puede agregar múltiples datasources aquí-> </slave> </prown> </"dataSourcesSlave"/> <! id = "SQLSessionFactory"> <Property Name = "DataSource" ref = "DataSource"/> <Property name = "configLocation" value = "classpath: config/mybatis-config.xml"/> <propiedad name = "mapperLocations" value = "classpath*: config/mappers/**/*. xml"/> </"mapperLocation DataSource-> <Bean id = "TransactionManager"> <Property Name = "DataSource" ref = "dataSource" /> </reme> <!-Definición de transacciones utilizando anotación-> <tx: anotación transaccion-manager = "transaccionManager" /> <bean> <propiedad name = "basespackage" valor = "net.aazj.mapper" /<!-<!-<!-<!-<!-<! name = "SqlSessionFactoryBeanName" value = "SQLSessionFactory"/> -> </ Bean> En el archivo de configuración de Spring anterior, definimos DataSourCemaster y DataSourceSlave para la base de datos maestra y la base de datos de esclavos respectivamente, y luego lo inyectamos en <bean id = "dataSource"> para que nuestro DataSource pueda seleccionar DataSourCemaster y DataSourceSlave de acuerdo con las diferentes claves.
5) Use Spring AOP para especificar la clave de DataSource, por lo que DataSource seleccionará DataSourCemaster y DataSourceSlave de acuerdo con la clave:
paquete net.aazj.aop; importar net.aazj.enums.dataSources; import net.aazj.util.datasourcetyperager; importar org.spectj.lang.joinpoint; import org.spectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; import org.aspectj.lang.annotation.pointcut; import org.springframework.stereotpepe.component; @Aspect // para AOP @componente // para la clase Auto ScanPublic DataSourceInterceptor {@PointCut ("Ejecution (public * net.aazj.service .. *. GetUser (..))") public void dataSourceSlave () {}; @BeFore ("DataSourceSlave ()") public void antes (unkepoint jp) {dataSourCetyPemanager.set (dataSources.slave); } // ...} Aquí definimos una clase de aspecto. Usamos @before para llamar a DataSourCetyPemanager.set (DataSources.Slave) antes de que se cumpla con el método de conformidad con @PointCut ("Ejecution (public * net.aazj.service .. *. GetUser (..))"), y el tipo de clave se establece en DataSources.Slave, por lo que DataSource seleccionará DataSourceSlave de acuerdo a Key = DataSources. Por lo tanto, las declaraciones SQL para este método se ejecutarán en la base de datos de esclavos.
Podemos expandir continuamente el aspecto delinterceptor de datos de datos y hacer varias definiciones en él para especificar la fuente de datos correspondiente a la fuente de datos apropiada para un método de servicio determinado.
De esta manera, podemos usar las potentes funciones de Spring AOP para configurarlo de manera muy flexible.
6) Análisis del principio de AbstrutingDataSource
ThreadLocalRountingDataSource hereda AbstrutingDataSource, implementando su método abstracto protegido de objeto abstracto determineCurrentLlowUpkey (); implementando así la función de enrutamiento para diferentes fuentes de datos. Comencemos con el código fuente para analizar los principios:
La clase pública abstractación abstructingdataSource extiende los implementos de dataSource de abstracto inicializando la cantidad de servicios de control de la cantidad de dataurce. Luego, cuando la primavera inicializa el bean, llamará a la interfaz de la inicentilizadora nula, el void apropepertiosset () lanza una excepción; Veamos cómo AbstracTroutingDataSource implementa esta interfaz: @Override public void AfterPropertIesset () {if (this.TargetDataSources == null) {tirar nueva IllegalArgumentException (se requiere "Propiedad 'TargetDataSources'"); } this.ResOlvedDataSources = new HashMap <Object, DataSource> (this.TargetDataSources.size ()); para (map.entry <objeto, objeto> Entrada: this.TargetDataSources.EntrySet ()) {Object LookUpKey = ResolSsSpespecifiedLoPKey (Entry.getKey ()); DataSource dataSource = resuelveSpecifiedDataSource (Entry.getValue ()); this.ResOlvedDataSources.put (SeakupKey, DataSource); } if (this.defaultTargetDataSource! = NULL) {this.ResOlvedDefaultDataSource = resuelvePecifiedDataSource (this.defaultTargetDataSource); }} TargetDataSources es el DataSourCemaster y DataSourceSlave que inyectamos en el archivo de configuración XML. Se inyecta el método AfterPropertIesset.
DataSourCeMaster y DataSourceSlave para construir un HASHMAP - ResueltoDataSources. Es conveniente obtener la fuente de datos correspondiente del mapa de acuerdo con la clave más adelante.
Echemos un vistazo a cómo la conexión getConnection () lanza SQLException; En la interfaz abstractDataSource se implementa:
@Override Public Connection getConnection () lanza SQLException {return DetetETARGetDataSource (). GetConnection (); }La clave es determinarInTargetDataSource (), que se puede ver en función del nombre del método, y debemos decidir qué DataSource usar aquí:
DataSource protegido DetermineTargetDataSource () {afirmo.notnull (this.ResOlvedDataSources, "enrutador DataSource no inicializado"); Object BurectUpKey = DetetInECRENTLOWNUpKey (); DataSource dataSource = this.resolvedDataSources.get (seardUpKey); if (dataSource == null && (this.lenientfallback || showupKey == null)) {dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) {tirar nueva ilegalStateException ("no se puede determinar el dataSource de destino para la tecla de búsqueda [" + seakupkey + "]"); } return dataSource;} Object BurectUpKey = DetetInECRENTLOWNUpKey (); Este método es implementado por nosotros, donde obtenemos el valor clave guardado en ThreadLocal. Después de obtener la clave, obtenga la fuente de datos correspondiente a la clave en el mapa inicializado resuelto DataSources de AfterPropertIesset (). El valor clave guardado en ThreadLocal se establece antes de llamar a los métodos relevantes en el servicio a través de AOP. Ok, ¡aquí está hecho!
3. Resumen
De este artículo, podemos experimentar el poder y la flexibilidad de AOP.
Lo anterior es la clasificación de información del procesamiento de fuente de múltiples datos myBatis. Espero que pueda ayudar a los amigos que lo necesitan