美文网首页
Spring多数据源配置

Spring多数据源配置

作者: 小螺钉12138 | 来源:发表于2018-12-23 23:47 被阅读0次

Spring多数据源配置原理

Spring的多数据源配置主要靠是AbstractRoutingDataSource类,该类中有个抽象方法用来获取数据源的名称,创建一个Java类来实现获取数据源名称的方法

  • AbstractRoutingDataSource获取数据源名称的方法
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;
    }
    
protected abstract Object determineCurrentLookupKey();

由于存在多线程的安全性问题,可以将数据源名称变量存到本地线程里面,获取数据源名称时可以直接从本地线程中获取

/**
 * 保存数据源名称的类
 */

public class DatabaseContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static void setCustomerType(String customerType) {
        contextHolder.set(customerType);
    }

    public static String getCustomerType() {
        return contextHolder.get();
    }

    public static void clearCustomerType() {
        contextHolder.remove();
    }
}

/**
 * 获取数据源名称的类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getCustomerType();
    }
}

切换数据源的时候,可以选择配置AOP去切换数据源,也可以手动通DatabaseContextHolder.setCustomerType(dataSourceName)去切换数据源

  • xml配置文件
<!--数据源1-->
<bean id="dataSourcev14" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.v14.driverClassName}"/>
        <property name="url" value="${jdbc.v14.url}"/>
        <property name="username" value="${jdbc.v14.username}"/>
        <property name="password" value="${jdbc.v14.password}"/>
    </bean>
<!--数据源2-->
    <bean id="dataSourcev11" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.v11.driverClassName}"/>
        <property name="url" value="${jdbc.v11.url}"/>
        <property name="username" value="${jdbc.v11.username}"/>
        <property name="password" value="${jdbc.v11.password}"/>
    </bean>

<!--动态数据源引用-->
    <bean id="dynamicDataSource" class="cn.com.egova.source.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="dataSourcev11" value-ref="dataSourcev11"></entry>
                <entry key="dataSourcev14" value-ref="dataSourcev14"></entry>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSourcev14">
        </property>
    </bean>
<!--Spring Jdbc引用-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dynamicDataSource"/>
    </bean>

Spring动态数据库切换原理

  • 查询数据库记录时手动切换数据源
<!--手动切换数据源-->
DatabaseContextHolder.setCustomerType("dataSourcev11");
<!--查询相应的数据库记录-->
int count2 = jdbcTemplate.queryForObject("select count(*) from to_stat_info",Integer.class);
  • jdbcTemplate类查询源码
<!--经过层层的封装之后到了execute方法-->
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");

        <!--获取数据源连接方法-->
        Connection con = DataSourceUtils.getConnection(getDataSource());
        Statement stmt = null;
        try {
            Connection conToUse = con;
            if (this.nativeJdbcExtractor != null &&
                    this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
                conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
            }
            stmt = conToUse.createStatement();
            applyStatementSettings(stmt);
            Statement stmtToUse = stmt;
            if (this.nativeJdbcExtractor != null) {
                stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
            }
            T result = action.doInStatement(stmtToUse);
            handleWarnings(stmt);
            return result;
        }
        catch (SQLException ex) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
        }
        finally {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }
    
  • DataSourceUtils类中getConnection方法
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
        }
    }
    
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        //首先查看事务管理器里面是否有Connection连接
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(dataSource.getConnection());
            }
            return conHolder.getConnection();
        }
        logger.debug("Fetching JDBC Connection from DataSource");
        //从数据源中获取连接,实现AbstractRoutingDataSource类中方法(因为配置文件中dataSource实现类是DynamicDataSource继承了AbstractRoutingDataSource抽象类)
        Connection con = dataSource.getConnection();

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            logger.debug("Registering transaction synchronization for JDBC Connection");
            ConnectionHolder holderToUse = conHolder;
            if (holderToUse == null) {
                holderToUse = new ConnectionHolder(con);
            }
            else {
                holderToUse.setConnection(con);
            }
            holderToUse.requested();
            TransactionSynchronizationManager.registerSynchronization(
                    new ConnectionSynchronization(holderToUse, dataSource));
            holderToUse.setSynchronizedWithTransaction(true);
            if (holderToUse != conHolder) {
                TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
            }
        }

        return con;
    }
  • AbstractRoutingDataSource类中获取数据源Connection连接方法
public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

//determineTargetDataSource方法
protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        //刚才创建的DynamicDataSource,实现AbstractRoutingDataSource抽象方法获取数据源名称
        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;
    }
//getConnection方法
public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

注意

实现动态数据源时,要注意一下aop的事务配置,因为aop的管理器会在方法执前先注入数据源,后面doGetConnection中获取连接时会直接从事务管理器里面获取,这样就会导致数据源切换失败

相关文章

网友评论

      本文标题:Spring多数据源配置

      本文链接:https://www.haomeiwen.com/subject/ninokqtx.html