ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Mybatis plus的多数据源@DS切换不生效,事务注解@Transactional问题

2021-11-08 15:34:09  阅读:406  来源: 互联网

标签:bookService 数据源 Transactional plus reqDto save public


由于使用了微服务,会有多个数据库的情况,有时业务需要,需要切换数据源,所以使用了Mybatis plus的@DS来切换多数据源

yml数据库配置如下:

spring:
  datasource:
    dynamic:
      primary: master
      datasource:
        master:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://user
          username: root
          password: root
        common:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://book
          username: root
          password: root

service如下,默认是master数据源

@Service
@Slf4j
public class MasterService {
    @Autowired
    UserService userService;
    @Autowired
    BookService bookService;

    /**必须master库方法先执行,才能回滚,达到事务效果*/
    @Transactional(rollbackFor = Exception.class)
    public void upload(ReqDto reqDto){
        userService.save(reqDto);
        bookService.save(reqDto);
    }
}

userService

@Service
@Log4j2
public class UserService extends ServiceImpl<UserMapper, User> {
    @Resource
    private UserMapper userMapper;
    
    public void save(ReqDto reqDto) {
        userMapper.save(reqDto);
    }
}

bookService

@Service
@Log4j2
@DS("common")
public class BookService extends ServiceImpl<BookMapper, Book> {
    @Resource
    private BookMapper bookMapper;
    
    public void save(ReqDto reqDto) {
        bookMapper.save(reqDto);
    }
}

但是神奇的事发生的,bookService的数据库应该是common,但是却是master的,也就是说@DS切换数据源没有起作用
于是开始排查

去除MasterService.upload上面的@Transactional,数据源切换正常,但是事务无效
BookService的save上面加@Transactional,数据源没有切换
BookService的save上面加@Transactional(propagation = Propagation.REQUIRES_NEW),数据源切换,且事务有效
原因:

开启事务的同时,会从数据库连接池获取数据库连接;
如果内层的service使用@DS切换数据源,只是又做了一层拦截,但是并没有改变整个事务的连接;
在这个事务内的所有数据库操作,都是在事务连接建立之后,所以会产生数据源没有切换的问题;
为了使@DS起作用,必须替换数据库连接,也就是改变事务的传播机智,产生新的事务,获取新的数据库连接;
所以bookService的save方法上除了加@Transactional外,还需要设置propagation = Propagation.REQUIRES_NEW
使得代码走以下逻辑:
 

private TransactionStatus handleExistingTransaction(
        .....省略.....
		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
			if (debugEnabled) {
				logger.debug("Suspending current transaction, creating new transaction with name [" +
						definition.getName() + "]");
			}
			SuspendedResourcesHolder suspendedResources = suspend(transaction);
			try {
				return startTransaction(definition, transaction, debugEnabled, suspendedResources);
			}
			catch (RuntimeException | Error beginEx) {
				resumeAfterBeginException(transaction, suspendedResources, beginEx);
				throw beginEx;
			}
		}
		.....省略.....)

在走startTransaction,再走doBegin,重新创建新事务,获取新的数据库连接,从而得到@DS的数据源

startTransaction(definition, transaction, debugEnabled, suspendedResources);
protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				Connection newCon = obtainDataSource().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);
			txObject.setReadOnly(definition.isReadOnly());

			// 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);
			}
			.....省略.....)

最终代码如下,只需要修改的是bookService
bookService

@Service
@Log4j2
@DS("common")
public class BookService extends ServiceImpl<BookMapper, Book> {
    @Resource
    private BookMapper bookMapper;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(ReqDto reqDto) {
        bookMapper.save(reqDto);
    }
}

@DS数据源切换生效
@Transaction事务生效
需要注意:
master:userService
common:bookService

common数据库的操作,需要在master之后,这样当bookService.save失败,会使得userService回滚;
如果common的操作先,那当userService失败,无法使bookService回滚

会回滚

    @Transactional(rollbackFor = Exception.class)
    public void upload(ReqDto respDto){
        userService.save(respDto);
        bookService.save(respDto);
    }

不会回滚

    @Transactional(rollbackFor = Exception.class)
    public void upload(ReqDto respDto){
        bookService.save(respDto);
        userService.save(respDto);
    }

标签:bookService,数据源,Transactional,plus,reqDto,save,public
来源: https://blog.csdn.net/WXF_Sir/article/details/121208566

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有