由于平時(shí)項(xiàng)目里有用到多個(gè)數(shù)據(jù)源,之前采用AOP的方式切換數(shù)據(jù)源,卻發(fā)現(xiàn)事務(wù)無(wú)法生效。今天嘗試了下在Spring Boot下創(chuàng)建多個(gè)數(shù)據(jù)源,并實(shí)現(xiàn)分布式事務(wù),即多事務(wù)同步提交與回滾。
這里需要用到Atomikos,它是一種無(wú)需服務(wù)器支持的分布式事務(wù)組件。
接下來(lái)介紹如何搭建多數(shù)據(jù)源與分布式事務(wù):
- pom.xml新增依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
- application.properties新增多數(shù)據(jù)源配置
#開(kāi)啟JTA支持
spring.jta.enabled=true
#數(shù)據(jù)源def
spring.datasource.def.xa-properties.url=jdbc:mysql://localhost:3306/yysoft?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
spring.datasource.def.xa-properties.username=root
spring.datasource.def.xa-properties.password=mysql
spring.datasource.def.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSource
#數(shù)據(jù)源唯一標(biāo)識(shí)
spring.datasource.def.unique-resource-name=defDataSource
#數(shù)據(jù)源opencart1
spring.datasource.opencart1.xa-properties.url=jdbc:mysql://localhost:3307/opencart1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
spring.datasource.opencart1.xa-properties.username=opencart1
spring.datasource.opencart1.xa-properties.password=uorejwrew
spring.datasource.opencart1.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSource
#數(shù)據(jù)源唯一標(biāo)識(shí)
spring.datasource.opencart1.unique-resource-name=opencart1DataSource
- Mapper要分成兩個(gè)目錄,xml不用,如圖所示

Mapper

xml
- 多數(shù)據(jù)源配置類
package com.yysoft.core.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.yysoft.core.dao.def", sqlSessionTemplateRef = "defSqlSessionTemplate")//指定包使用的sqlSession
public class DefDSConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.def")
@Primary
public DataSource defDataSource() {
return new AtomikosDataSourceBean();
}
@Bean
@Primary
public SqlSessionFactory defSqlSessionFactory(@Qualifier("defDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/yysoft/core/dao/def/mapper/*Mapper.xml"));//掃描指定目錄的xml
return bean.getObject();
}
@Bean
@Primary
public DataSourceTransactionManager defTransactionManager(@Qualifier("defDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
@Primary
public SqlSessionTemplate defSqlSessionTemplate(@Qualifier("defSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
這里主數(shù)據(jù)庫(kù)要用@Primary注解,或者會(huì)報(bào)錯(cuò)。
package com.yysoft.core.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.yysoft.core.dao.opencart1", sqlSessionTemplateRef = "opencart1SqlSessionTemplate")//指定包使用的sqlSession
public class Opencart1DSConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.opencart1")
public DataSource opencart1DataSource() {
return new AtomikosDataSourceBean();
}
@Bean
public SqlSessionFactory opencart1SqlSessionFactory(@Qualifier("opencart1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/yysoft/core/dao/opencart1/mapper/*Mapper.xml"));//掃描指定目錄的xml
return bean.getObject();
}
@Bean
public DataSourceTransactionManager opencart1TransactionManager(@Qualifier("opencart1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public SqlSessionTemplate opencart1SqlSessionTemplate(@Qualifier("opencart1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- 事務(wù)管理器配置類
package com.yysoft.core.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
@Configuration
public class TransactionManagerConfig {
@Bean
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
JtaTransactionManager manager = new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
return manager;
}
}
- 在Service層使用
通過(guò)4和5的配置,總共創(chuàng)建了兩個(gè)數(shù)據(jù)源,還有三個(gè)事務(wù)管理器,分別是主庫(kù)的事務(wù)管理器,次庫(kù)的事務(wù)管理器以及分布式事務(wù)管理器。通過(guò)@Transactional來(lái)控制Service類使用哪個(gè)事務(wù)管理器,如果是@Transactional,則為主庫(kù)事務(wù)(或@Transactional("defTransactionManager")),@Transactional("opencart1TransactionManager"),則為次庫(kù)事務(wù),@Transactional("transactionManager"),則為分布式事務(wù)。
package com.yysoft.core.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.yysoft.core.dao.def.SysMenuQueryMapper;
import com.yysoft.core.dao.def.SysUserMapper;
import com.yysoft.core.dao.opencart1.OcDownloadMapper;
import com.yysoft.core.model.OcDownload;
import com.yysoft.core.model.SysMenu;
import com.yysoft.core.model.SysUser;
import com.yysoft.core.model.SysUserExample;
import com.yysoft.core.web.model.MenuModel;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.yysoft.core.service.IUserService;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Transactional("transactionManager")//分布式事務(wù)
public class UserService implements IUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private OcDownloadMapper ocDownloadMapper;
@Override
public void removeUsers(String userIds) {
String[] userIdArr = userIds.split(",");
for (String userId : userIdArr) {
SysUser user = sysUserMapper.selectByPrimaryKey(userId);
user.setState("0");
sysUserMapper.updateByPrimaryKey(user);//主庫(kù)
}
OcDownload ocDownload = new OcDownload();
ocDownload.setFilename("111");
ocDownload.setMask("000");
ocDownload.setDateAdded(new Date());
ocDownloadMapper.insertSelective(ocDownload);//次庫(kù)
int i = 1/0;//制造異常
}
}
當(dāng)執(zhí)行int i = 1/0;后,主庫(kù)和次庫(kù)的事務(wù)會(huì)同步回滾,從而保證了事務(wù)的一致性。
第一次寫(xiě)簡(jiǎn)書(shū),把自己今天配置的過(guò)程寫(xiě)了出來(lái),希望也能幫到一些人。