SpringBoot2.X版本的Mybatis多数据源动态切换的配置可以参照我上一篇文章:SpringBoot2.X版本之Mybatis多数据源动态切换_hanxiaozhang的博客-CSDN博客
org.springframework.boot
spring-boot-starter-jta-atomikos
将druid的Type:com.alibaba.druid.pool.DruidDataSource修改成com.alibaba.druid.pool.xa.DruidXADataSource,原因:DruidXADataSource支持分布式事务
datasource:
master:
type: com.alibaba.druid.pool.xa.DruidXADataSource
jta相关参数配置:
jta:
log-dir: classpath:tx-logs
transaction-manager-id: txManager
添加JtaTransactionManager配置:
/**
* JtaTransactionManager(分布式事务使用)
* @return
* @throws SystemException
*/
@Bean
public JtaTransactionManager jtaTransactionManager() throws SystemException {
log.info("Initialized -> [{}]", "JtaTransactionManager Start");
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setTransactionManager(userTransactionManager());
jtaTransactionManager.setUserTransaction(userTransactionImp());
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
private UserTransactionManager userTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}
private UserTransactionImp userTransactionImp() throws SystemException {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(5000);
return userTransactionImp;
}
修改数据源配置:
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource.master")
public DruidXADataSource dbMasterXa() {
return new DruidXADataSource();
}
@Bean
@ConfigurationProperties(prefix = "datasource.slave1")
public DruidXADataSource dbSlave1Xa() {
return new DruidXADataSource();
}
@Bean
@ConfigurationProperties(prefix = "datasource.slave2")
public DruidXADataSource dbSlave2Xa() {
return new DruidXADataSource();
}
@Bean
@ConfigurationProperties(prefix = "datasource.other")
public DruidXADataSource dbOtherXa() {
return new DruidXADataSource();
}
@Bean
public DataSource dbMaster() {
log.info("Initialized -> [{}]", "DataSource DB_Master Start");
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setUniqueResourceName(DataSourceKey.DB_MASTER.toString());
xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
xaDataSource.setXaDataSource(dbMasterXa());
xaDataSource.setPoolSize(5);
return xaDataSource;
}
@Bean
public DataSource dbSlave1() {
log.info("Initialized -> [{}]", "DataSource DB_Slave1 Start");
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setUniqueResourceName(DataSourceKey.DB_SLAVE1.toString());
xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
xaDataSource.setXaDataSource(dbSlave1Xa());
xaDataSource.setPoolSize(5);
return xaDataSource;
}
@Bean
public DataSource dbSlave2() {
log.info("Initialized -> [{}]", "DataSource DB_Slave2 Start");
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setUniqueResourceName(DataSourceKey.DB_SLAVE2.toString());
xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
xaDataSource.setXaDataSource(dbSlave2Xa());
xaDataSource.setPoolSize(5);
return xaDataSource;
}
@Bean
public DataSource dbOther() {
log.info("Initialized -> [{}]", "DataSource DB_Other Start");
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setUniqueResourceName(DataSourceKey.DB_OTHER.toString());
xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
xaDataSource.setXaDataSource(dbOtherXa());
xaDataSource.setPoolSize(5);
return xaDataSource;
}
修改sqlSessionFactory配置,添加事务配置:
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
log.info("Initialized -> [{}]", "SqlSessionFactory Start");
... ...
... ...
//配置事务
sqlSessionFactoryBean.setTransactionFactory(new DynamicDataSourceTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
package com.hanxiaozhang.config.datasource;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.apache.commons.lang3.Validate.notNull;
/**
* 〈一句话功能简述〉
* 〈动态数据源的Transaction〉
*
* @author hanxinghua
* @create 2019/12/26
* @since 1.0.0
*/
public class DynamicDataSourceTransaction implements Transaction {
private static final Log LOGGER = LogFactory.getLog(DynamicDataSourceTransaction.class);
/**
* 数据源
*/
private final DataSource dataSource;
/**
*数据库标识
*/
private DataSourceKey mainDataSourceKey;
/**
* 数据库连接
*/
private Connection mainConnection;
/**
* 标识连接Map
*/
private ConcurrentMap otherConnectionMap;
/**
* 是否断开事务
*/
private boolean isConnectionTransactional;
/**
* 是否自动提交
*/
private boolean autoCommit;
public DynamicDataSourceTransaction(DataSource dataSource) {
notNull(dataSource, "No DataSource specified");
this.dataSource = dataSource;
otherConnectionMap = new ConcurrentHashMap<>();
mainDataSourceKey=DynamicDataSourceContextHolder.get()==null?DataSourceKey.DB_MASTER:DynamicDataSourceContextHolder.get();
}
/**
* {@inheritDoc}
*/
@Override
public Connection getConnection() throws SQLException {
DataSourceKey dataSourceKey = DynamicDataSourceContextHolder.get()==null?DataSourceKey.DB_MASTER:DynamicDataSourceContextHolder.get();
if (dataSourceKey.equals(mainDataSourceKey)) {
if (mainConnection != null) {
return mainConnection;
} else {
openMainConnection();
mainDataSourceKey =dataSourceKey;
return mainConnection;
}
} else {
if (!otherConnectionMap.containsKey(dataSourceKey)) {
try {
Connection conn = dataSource.getConnection();
otherConnectionMap.put(dataSourceKey, conn);
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
return otherConnectionMap.get(dataSourceKey);
}
}
private void openMainConnection() throws SQLException {
this.mainConnection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.mainConnection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.mainConnection, this.dataSource);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"JDBC Connection ["
+ this.mainConnection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
/**
* {@inheritDoc}
*/
@Override
public void commit() throws SQLException {
if (this.mainConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Committing JDBC Connection [" + this.mainConnection + "]");
}
this.mainConnection.commit();
for (Connection connection : otherConnectionMap.values()) {
connection.commit();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void rollback() throws SQLException {
if (this.mainConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Rolling back JDBC Connection [" + this.mainConnection + "]");
}
this.mainConnection.rollback();
for (Connection connection : otherConnectionMap.values()) {
connection.rollback();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws SQLException {
DataSourceUtils.releaseConnection(this.mainConnection, this.dataSource);
for (Connection connection : otherConnectionMap.values()) {
DataSourceUtils.releaseConnection(connection, this.dataSource);
}
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}
package com.hanxiaozhang.config.datasource;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import javax.sql.DataSource;
/**
* 〈一句话功能简述〉
* 〈动态数据源的TransactionFactory〉
*
* @author hanxinghua
* @create 2019/12/26
* @since 1.0.0
*/
public class DynamicDataSourceTransactionFactory extends SpringManagedTransactionFactory {
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new DynamicDataSourceTransaction(dataSource);
}
}
在浏览器输入:http://127.0.0.1/test,执行测试。distributedTrans方法中会抛出java.lang.ArithmeticException: / by zero异常,使事务回滚,分别查看两个数据库可以发现数据没有入库,回滚成功
Controller :
@RequestMapping()
public String test1(){
DictDO dictDO = new DictDO();
dictDO.setName("分布式事务测试");
dictDO.setType("分布式事务测试");
dictDO.setDescription("分布式事务测试");
dictDO.setCreateDate(new Date());
testService.distributedTrans(dictDO);
return "OK";
}
Service:
@Override
@Transactional(rollbackFor = Exception.class)
public void distributedTrans(DictDO dictDO) {
testService1.save1(dictDO);
testService1.save2(dictDO);
int i=1/0;
}
@Override
@TargetDataSource(dataSourceKey = DataSourceKey.DB_SLAVE1)
public void save1(DictDO dictDO){
dictDao.save(dictDO);
}
@Override
@TargetDataSource(dataSourceKey = DataSourceKey.DB_MASTER)
public void save2(DictDO dictDO){
dictDao.save(dictDO);
}
注意mysql-connector-java版本:5.1.46,高版本会遇到一些其他问题,我没有做过多的研究。
源码可以通过邮箱获取:[email protected]