SpringBoot多数据源切换失败

前提条件!!!

  1. 重写AbstractRoutingDataSource类实现数据源切换功能
  2. 使用@Transactional注解实现数据库事务
  3. 在不使用@Transactional注解的方法,数据源切换功能是正常可用的

由于数据源切换失败的原因多种多样,使用请先看一下是否符合条件

示例伪代码如下(错误的代码):


/**
*具体的业务调用。
*在一个方法里切换到数据源A,执行sql语句;切换到数据源B,执行sql语句
*此方法还进行了事物的管理
*/
@Transactional
public void fun(){
	//切换数据源A
	useDatabaseA();
	//查询数据库A
	selectA();
	//切换数据源B
	useDatabaseB();
	//查询数据库B。
	//此处报错,错误信息为:在数据库A中不存在xxxx表
	selectB();
	insertB();
}

原因

使用@Transactional进行事务操作的时,会先获取Connection连接,然后缓存该Connection对象,后面执行sql语句操作时,直接从缓存中取Connection对象

以下是SpringBoot事务代码(DataSourceTransactionManager.java类)

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        //此处会尝试从缓存中获取Connection对象
        //由于上述伪代码在执行selectA()方法时,已经获取了Connection对象
        //而改Connsection对象连接的数据库A
        //并且执行selectB()方法时会直接获取缓存
        //所以执行selectB()方法实际上是在数据库A中执行数据库B的sql语句
        if (txObject.getConnectionHolder() == null ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            Connection newCon = this.dataSource.getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();

        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);

        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
        // so we don't want to do it unnecessarily (for example if we've explicitly
        // configured the connection pool to set it already).
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false);
        }

        prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);

        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        // Bind the connection holder to the thread.
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
        }
    }

    catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.dataSource);
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}


以下代码是开头的伪代码切换数据源无效的原因


/**
*具体的业务调用。
*在一个方法里切换到数据源A,执行sql语句;切换到数据源B,执行sql语句
*此方法还进行了事物的管理
*/
@Transactional
public void fun(){
	//切换数据源A,此时获取DatabaseA的Connection对象
	useDatabaseA();
	selectA();
	
	//切换数据源B,此时直接从缓存中取DatabaseA的Connection对象
	useDatabaseB();
	//由于获取的Connection对象连接的数据库A
	//所以执行查询数据库B的操作会抛出异常
	selectB();
	insertB();
}

解决方案

1.直接去掉@Transactional注解

因为跨数据库事务不能依赖Spring boot事务解决,需要使用分布式事务(个人愚见

2.直接去掉@Transactional注解,然后手动提交数据库事务.

使用场景只有其中一个数据库需要进行事务处理

伪代码如下:


//注入对象
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;

public void fun(){
	//切换数据源A,此时获取DatabaseA的Connection对象
	useDatabaseA();
	selectA();
	
	//切换数据源B,此时获取DatabaseB的Connection对象
	useDatabaseB();

    //获取事务对象
    TransactionStatus status = dataSourceTransactionManager.getTransaction(transactionDefinition);
    try{
        selectB();
        insertB();
        deleteB();
        //提交事务
        dataSourceTransactionManager.commit(status);
    } catch(Exception e){
        //异常。回滚事务
        dataSourceTransactionManager.rollback(status);
    }
}

你可能感兴趣的:(SpringBoot,java,数据库,spring,boot)