The same project sometimes involves multiple databases, that is, multiple data sources. Multiple data sources can be divided into two situations:
1) Two or more databases are not related and are independent. In fact, this can be developed as two projects. For example, in game development, one database is a platform database, and there are other databases corresponding to games under the platform;
2) Two or more databases are in a master-slave relationship. For example, mysql builds a master-master, and the other databases are in a master-slave relationship. Later, it has multiple slaves; or uses MHA to build master-slave replication;
At present, there are about two ways to build Spring multiple data sources that I know of, and you can choose according to the situation of multiple data sources.
1. Use the spring configuration file to directly configure multiple data sources
For example, if the two databases are not related, you can directly configure multiple data sources in the spring configuration file, and then configure the transactions separately, as follows Shown:
<context:component-scan base-package="net.aazj.service,net.aazj.aop" /> <context:component-scan base-package="net.aazj.aop" /> <!-- 引入属性文件 --> <context:property-placeholder location="classpath:config/db.properties" /> <!-- 配置数据源 --> <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url}" /> <property name="username" value="${jdbc_username}" /> <property name="password" value="${jdbc_password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="0" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="20" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="20" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="0" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="60000" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:config/mybatis-config.xml" /> <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" /> </bean> <!-- Transaction manager for a single JDBC DataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" /> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="net.aazj.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- Enables the use of the @AspectJ style of Spring AOP --> <aop:aspectj-autoproxy/>
Configuration of the second data source
<bean name="dataSource_2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url_2}" /> <property name="username" value="${jdbc_username_2}" /> <property name="password" value="${jdbc_password_2}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="0" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="20" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="20" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="0" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="60000" /> </bean> <bean id="sqlSessionFactory_slave" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource_2" /> <property name="configLocation" value="classpath:config/mybatis-config-2.xml" /> <property name="mapperLocations" value="classpath*:config/mappers2/**/*.xml" /> </bean> <!-- Transaction manager for a single JDBC DataSource --> <bean id="transactionManager_2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource_2" /> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager_2" /> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="net.aazj.mapper2" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_2"/> </bean>
As shown above, we configured two dataSources, two sqlSessionFactory, two transactionManagers, and the key point is the MapperScannerConfigurer Configuration - use the sqlSessionFactoryBeanName attribute to inject the names of different sqlSessionFactory. In this case, the corresponding sqlSessionFactory is injected into the mapper interface corresponding to different databases.
It should be noted that this configuration of multiple databases does not support distributed transactions, that is, multiple databases cannot be operated in the same transaction. The advantage of this configuration method is that it is very simple, but it is not flexible. It is not suitable for master-slave type multi-data source configuration. Master-slave multi-data source configuration needs to be particularly flexible and requires detailed configuration according to the type of business. For example, for some select statements that are particularly time-consuming, we hope to execute them on the slave, while operations such as update and delete can only be executed on the master. In addition, for some select statements that require high real-time performance, we also It may need to be executed on the master - for example, in a scenario where I go to the mall to buy a weapon, the purchase operation must be the master. At the same time, after the purchase is completed, I need to re-query the weapons and gold coins I own, then this query may We also need to prevent it from being executed on the master instead of the slave, because there may be a delay on the slave. We don’t want players to find that they can’t find the weapon in their backpack after the purchase is successful.
So for the configuration of multi-data sources of the master-slave type, flexible configuration needs to be carried out according to the business. Which selections can be placed on the slave and which selections cannot be placed on the slave. Therefore, the above configuration of the data source is not suitable.
2. Configuration of multiple data sources based on AbstractRoutingDataSource and AOP
The basic principle is that we define a DataSource class ThreadLocalRountingDataSource to inherit AbstractRoutingDataSource, and then inject the master and slave data sources into ThreadLocalRountingDataSource in the configuration file, and then pass AOP is used to flexibly configure where to choose the master data source and where to choose the slave data source. Let’s look at the code implementation:
1) First define an enum to represent different data sources:
package net.aazj.enums; /** * 数据源的类别:master/slave */ public enum DataSources { MASTER, SLAVE }
2) Use TheadLocal to save the key (key) of which data source each thread chooses:
package net.aazj.util; import net.aazj.enums.DataSources; public class DataSourceTypeManager { private static final ThreadLocal<DataSources> dataSourceTypes = 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) Define ThreadLocalRountingDataSource and inherit AbstractRoutingDataSource:
package net.aazj.util; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceTypeManager.get(); } }
4) Inject the master and slave data sources into ThreadLocalRountingDataSource in the configuration file:
<context:component-scan base-package="net.aazj.service,net.aazj.aop" /> <context:component-scan base-package="net.aazj.aop" /> <!-- 引入属性文件 --> <context:property-placeholder location="classpath:config/db.properties" /> <!-- 配置数据源Master --> <bean name="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url}" /> <property name="username" value="${jdbc_username}" /> <property name="password" value="${jdbc_password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="0" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="20" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="20" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="0" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="60000" /> </bean> <!-- 配置数据源Slave --> <bean name="dataSourceSlave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc_url_slave}" /> <property name="username" value="${jdbc_username_slave}" /> <property name="password" value="${jdbc_password_slave}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="0" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="20" /> <!-- 连接池最大空闲 --> <property name="maxIdle" value="20" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="0" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="60000" /> </bean> <bean id="dataSource" class="net.aazj.util.ThreadLocalRountingDataSource"> <property name="defaultTargetDataSource" ref="dataSourceMaster" /> <property name="targetDataSources"> <map key-type="net.aazj.enums.DataSources"> <entry key="MASTER" value-ref="dataSourceMaster"/> <entry key="SLAVE" value-ref="dataSourceSlave"/> <!-- 这里还可以加多个dataSource --> </map> </property> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:config/mybatis-config.xml" /> <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" /> </bean> <!-- Transaction manager for a single JDBC DataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" /> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="net.aazj.mapper" /> <!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> --> </bean>
In the above spring configuration file, we target the master database and slave The database defines two dataSources, dataSourceMaster and dataSourceSlave respectively, and then injects them into
5) Use Spring AOP to specify the key of dataSource, so that dataSource will select dataSourceMaster and dataSourceSlave based on the key:
package net.aazj.aop; import net.aazj.enums.DataSources; import 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.stereotype.Component; @Aspect // for aop @Component // for auto scan public class DataSourceInterceptor { @Pointcut("execution(public * net.aazj.service..*.getUser(..))") public void dataSourceSlave(){}; @Before("dataSourceSlave()") public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); } // ... ... }
Here we define an Aspect class, and we use @Before to comply with @Pointcut(" Before the method in execution(public * net.aazj.service..*.getUser(..))") is called, call DataSourceTypeManager.set(DataSources.SLAVE) to set the key type to DataSources.SLAVE, so dataSource will Select the dataSourceSlave dataSource based on key=DataSources.SLAVE. Therefore, the sql statement for this method will be executed on the slave database.
We can continue to expand the DataSourceInterceptor aspect and make various definitions in it to specify the appropriate data source corresponding to a method of a service.
In this way, we can use the powerful functions of Spring AOP to configure it very flexibly.
6) Analysis of the principle of AbstractRoutingDataSource
ThreadLocalRountingDataSource 继承了 AbstractRoutingDataSource, 实现其抽象方法 protected abstract Object determineCurrentLookupKey(); 从而实现对不同数据源的路由功能。我们从源码入手分析下其中原理:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean AbstractRoutingDataSource 实现了 InitializingBean 那么spring在初始化该bean时,会调用InitializingBean的接口 void afterPropertiesSet() throws Exception; 我们看下AbstractRoutingDataSource是如何实现这个接口的: @Override public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } }
targetDataSources 是我们在xml配置文件中注入的 dataSourceMaster 和 dataSourceSlave. afterPropertiesSet方法就是使用注入的。
dataSourceMaster 和 dataSourceSlave来构造一个HashMap——resolvedDataSources。方便后面根据 key 从该map 中取得对应的dataSource。
我们在看下 AbstractDataSource 接口中的 Connection getConnection() throws SQLException; 是如何实现的:
@Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); }
关键在于 determineTargetDataSource(),根据方法名就可以看出,应该此处就决定了使用哪个 dataSource :
protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; }
Object lookupKey = determineCurrentLookupKey(); 该方法是我们实现的,在其中获取ThreadLocal中保存的 key 值。获得了key之后,在从afterPropertiesSet()中初始化好了的resolvedDataSources这个map中获得key对应的dataSource。而ThreadLocal中保存的 key 值 是通过AOP的方式在调用service中相关方法之前设置好的。OK,到此搞定!
3. 总结
从本文中我们可以体会到AOP的强大和灵活。
以上就是sping,mybatis 多数据源处理的资料整理,希望能帮助有需要的朋友
更多spring mybatis多数据源实例详解相关文章请关注PHP中文网!