Spring multi data source configuration (rpm)

Reprinted from: https://www.cnblogs.com/digdeep/p/4512368.html

Sometimes with a project involves multiple databases, that is, multiple data sources. Multiple data sources can be divided into two cases:

1) two or more database is not relevant, independent, in fact, this can be used as two projects to develop. For example, in a game development database is a database platform, as well as at other game platforms corresponding to a database;

2) two or more databases is a master-slave relationship, there is such a build mysql master-master, which then has a plurality of Slave; master-slave or copying using MHA built;

At present I know Spring to build multiple data sources, there are about two ways, you can choose according to the situation of multiple data sources.

1. The spring configuration files using a plurality of data sources directly

For example, no correlation case two databases, a plurality of data sources can be configured directly in the spring profile, and are configured using the transaction, as follows:

Copy the code
    <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}" />
        <!- Initialization join size -> 
        <Property name = "for maxActive" value = "20 is" />
        <Property name = "initialSize" value = "0" />
        <-! Connection pool to use the maximum number of connections ->
        <!-- 连接池最大空闲 -->
        <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:the autoproxy-AspectJ /> <-! =============== =============== arranged a second data source -> <the bean name = "dataSource_2" class = "com.alibaba.druid.pool.DruidDataSource" = the init-Method "the init" the destroy-Method = "Close"> <property name="url" value="${jdbc_url_2}" /> <Property name = "username" value = "$ {} jdbc_username_2" /> <Property name = "password" value = "$ {jdbc_password_2}" /> <-! connection initialization size -> <Property name = "initialSize" value = "0" /> <-! maximum connection pool using the number of connections -> <Property name = "for maxActive" value = "20 is" /> <-! connection pool maximum idle -> <Property name = "maxIdle" value = "20 is" /> <-! connection minimum free pool -> <Property name = "minIdle "value =" 0 "/> <-! obtain maximum latency connection -> <Property name =" maxWait "value =" 60000 "/> </ the 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>
Copy the code

As indicated above, the dataSource we are disposed two, two sqlSessionFactory, the transactionManager two, and that the critical areas of MapperScannerConfigurer configuration - sqlSessionFactoryBeanName properties, injection of sqlSessionFactory different names, so it corresponds to the different databases the mapper interface injection corresponding to sqlSessionFactory.

It should be noted that this configuration does not support multiple databases distributed transactions, which is the same transaction can not be operated multiple databases. The advantage of this configuration is very simple, but it is not flexible. For the master-slave type configuration in terms of multiple data sources do not adapt, master-slave configuration of multiple data sources, the need is particularly flexible, require careful configuration according to the type of traffic. For example, for some time-consuming particularly large select statement, we want to perform put on slave, and for the update, delete and other operations must be performed only on the master, in addition to a number of demanding real-time requirements of the select statement, we also need to be put on a master perform - such as a scene I went to the mall to buy a weapon, purchase operation was given a master, at the same time after the purchase is complete, you need to re-query the weapons and gold I have, then this query might also need to prevent the implementation of the master, but not on the slave up to perform, because there may be a delay on the slave, we do not want players to discover after the purchase is successful, the situation in the backpack, but can not find the weapons appear.

So configured for master-slave type of multiple data sources, according to the service needs to be flexible configuration, which can be placed on the select Slave, which can not be placed on the select slave. Configuration so that the data source is less adapted to the above.

2. Based on the configuration and AOP AbstractRoutingDataSource multiple data sources

The basic principle is that we own definition of a DataSource class ThreadLocalRountingDataSource, to inherit AbstractRoutingDataSource, then injected into the master and slave data sources to ThreadLocalRountingDataSource in the configuration file, and then by AOP to flexible configuration, select the master data source in what areas need areas in which select slave data source. The following look at the code to achieve:

1) define a enum to represent different data sources:

Copy the code
net.aazj.enums Package; 

/ ** 
 * category data sources: Master / Slave 
 * / 
public enum {the DataSources 
    the MASTER, SLAVE 
}
Copy the code

2) to save each thread through TheadLocal which flag data sources (key) to select:

Copy the code
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);
    }
}
Copy the code

3) the definition of ThreadLocalRountingDataSource, inheritance AbstractRoutingDataSource:

Copy the code
package net.aazj.util;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceTypeManager.get();
    }
}
Copy the code

4) injection master and slave data source in the configuration file to ThreadLocalRountingDataSource:

Copy the code
    <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}" />
        <! - the maximum idle connection pool ->
        <Property name = "for maxActive" value = "20 is" />
        <! - size initialization connection ->
        <! - Maximum number of connections the connection pool ->
        <Property name = "initialSize" value = "0" />
        <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}" /> 
        <-! connection pool largest free ->
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <! - Maximum number of connector connection pool -> 
        <Property name = "for maxActive" value = "20 is" /> 
        <Property name = "maxIdle" value = "20 is" /> 
        <! - the minimum free connection pool - -> 
        <Property name = "minIdle" value = "0" /> 
        <-! obtain maximum latency connection -> 
        <Property name = "maxWait" value = "60000" /> 
    </ the bean>     
    the bean ID <= "the 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" />
    <-! define transactions using annotation ->
    </ bean>    
    <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>        
Copy the code

The above spring profile, we focused on the master database and the slave database are defined and dataSourceSlave two dataSourceMaster the dataSource, then poured into <bean id = "dataSource" class = "net.aazj.util.ThreadLocalRountingDataSource">, so we since the dataSource can be selected according to the different key and dataSourceSlave the dataSourceMaster.

5) Use dataSource Spring AOP to specify the key, whereby key will select dataSourceMaster and dataSource according dataSourceSlave:

Copy the code
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
@Order(0)  // execute before @Transactional public class DataSourceInterceptor { @Pointcut("execution(public * net.aazj.service..*.getUser(..))") public void dataSourceSlave(){}; @Before("dataSourceSlave()") public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }
// ... ... }
Copy the code

Aspect Here we define a class, we use the method to @Before in line with @Pointcut ( "execution (public * net.aazj.service .. *. GetUser (..))") Before the call, the call DataSourceTypeManager. set (DataSources.SLAVE) is provided for key types DataSources.SLAVE, we will choose dataSourceSlave the dataSource the dataSource key = DataSources.SLAVE. Therefore, the method for the sql statement is executed on the slave database (via Sign Liu 1987 alert, a problem of the execution order among the plurality of Aspect exist here, must ensure that the data source Aspect handover must be performed before the Aspect @Transactional , it is used here @Order (0) to ensure that the data source is switched executed before @Transactional).

We can continue to expand DataSourceInterceptor this Aspect, conducted various definitions in, specify the appropriate data source corresponding dataSource for a certain method of service.

So that we can use the power of Spring AOP to very flexibly configured.

6) AbstractRoutingDataSource principle analysis

ThreadLocalRountingDataSource inherited AbstractRoutingDataSource, abstract methods to achieve its protected abstract Object determineCurrentLookupKey (); routing function in order to achieve different data sources. We start from the source code analysis under which the principles of:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
AbstractRoutingDataSource realized InitializingBean then spring upon initialization of the bean, it will call interface InitializingBean the 
void afterPropertiesSet () throws Exception; how we look AbstractRoutingDataSource is implementation of this interface:
Copy the code
    @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);
        }
    }
Copy the code
targetDataSources we injected in the xml configuration file dataSourceMaster and dataSourceSlave. afterPropertiesSet method is to use injection 
dataSourceMaster and dataSourceSlave to construct a HashMap - resolvedDataSources. Facilitate later acquires the corresponding key from the map in accordance with the dataSource .
We look Connection getConnection AbstractDataSource interface () throws SQLException; how to achieve:
    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

The key is determineTargetDataSource (), according to the method name can be seen, the use should be decided here which dataSource:

Copy the code
    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;
    }
Copy the code
Object lookupKey = determineCurrentLookupKey (); This is our method implemented, in which the acquired key value stored in a ThreadLocal. After obtaining a key, 
get key corresponding dataSource initialization () from afterPropertiesSet good of resolvedDataSources this map. The ThreadLocal stored in key value
is provided by the AOP related methods before calling the service of good. OK, get this!

7) extended ThreadLocalRountingDataSource

Above we just realized select master-slave data source. If you have more than one master, or more than one slave. The HA composed of a plurality of master, to be achieved when one of the linked master is automatically switch to another master station, this function can be used LVS / Keepalived achieved, may be realized through further expansion ThreadLocalRountingDataSource, a thread can additionally each dedicated to one second to test whether mysql normal to achieve. Also for slave to achieve load balancing among multiple, while slave station when a hung up, to be removed from the implement load balancing, this feature can be used either LVS / Keepalived achieved, also be extended by taking a step forward ThreadLocalRountingDataSource to achieve.

3. Summary

From this article we can appreciate the AOP powerful and flexible.

As used herein, it is mybatis, in fact, use Hibernate should also be a similar configuration.

Guess you like

Origin www.cnblogs.com/xuningchuanblogs/p/10950436.html