在项目中当涉及到多数据源操作的时候,例如增加、更新、删除。我们要采用一些额外的手段来进行事务的操作,常规的例如:
1、XA事务:通过两阶段进行事务确认。可以用seata
2、TCC事务:可以通过补偿的方式来进行事务的逆向处理。可以用seata,Hmily或手动进行实现
3、最大努力通知:不保证强一致性,但是基于最终一致性通过异步或者后续补偿机制来完成事务。例如rocketMQ.
本文是介绍的一种基于XA事务的方式,采用轻量级的atomikos 的方式来保证动态多数据源的事务。
引入maven:
org.springframework.boot
spring-boot-starter-jta-atomikos
配置项:
@Configuration
//表示通过 aop 框架暴露改代理对象,AopContext 能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan(basePackages = "xx.xx.多数据源动态实现.mapper" , sqlSessionTemplateRef = "sqlSessionTemplate")
public class ApplicationConfig {
}
AtomikosConfig
JTA 事务配置类:Java Transaction API。
定义了 PlatformTransactionManager 实际是 JtaTransactionManager。
@Configuration
public class AtomikosConfig {
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws SystemException {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({"userTransaction", "atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws SystemException {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
DruidConfig
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(Environment env)
{
String prefix = "spring.datasource.druid.master.";
return getDataSource(env, prefix, MASTER);
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(Environment env)
{
String prefix = "spring.datasource.druid.slave.";
return getDataSource(env, prefix, SLAVE);
}
protected DataSource getDataSource(Environment env, String prefix, String dataSourceName)
{
Properties prop = build(env, prefix);//获取配置信息
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName(dataSourceName);
ds.setXaProperties(prop);
return ds;
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource)
{
Map
MybatisConfig
public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception
{
// String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.config-location");
// typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
return sessionFactory.getObject();
}
@Bean(name = "sqlSessionFactoryMaster")
public SqlSessionFactory sqlSessionFactoryMaster(Environment env, @Qualifier("masterDataSource") DataSource dataSource) throws Exception
{
return createSqlSessionFactory(env, dataSource);
}
@Bean(name = "sqlSessionFactorySlave")
public SqlSessionFactory sqlSessionFactorySlave(Environment env, @Qualifier("slaveDataSource") DataSource dataSource) throws Exception
{
return createSqlSessionFactory(env, dataSource);
}
@Bean(name = "sqlSessionTemplate")
public DynamicSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryMaster") SqlSessionFactory factoryMaster,
@Qualifier("sqlSessionFactorySlave") SqlSessionFactory factorySlave) throws Exception
{
Map sqlSessionFactoryMap = new HashMap<>();
sqlSessionFactoryMap.put(DruidConfig.MASTER, factoryMaster);
sqlSessionFactoryMap.put(DruidConfig.SLAVE, factorySlave);
DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster);
customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);
return customSqlSessionTemplate;
}
自定义SqlSessionTemplate
这里初始化会结合 MybatisConfig :有几个数据源就会初始化几个 SqlSessionFactory 具体执行语句的时候就会使用具体的 SqlSessionFactory 去执行。
public class DynamicSqlSessionTemplate extends SqlSessionTemplate {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
private Map targetSqlSessionFactorys;
private SqlSessionFactory defaultTargetSqlSessionFactory;
@Override
public SqlSessionFactory getSqlSessionFactory()
{
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys
.get(DynamicDataSourceContextHolder.getDataSourceType());
if (targetSqlSessionFactory != null)
{
return targetSqlSessionFactory;
}
else if (defaultTargetSqlSessionFactory != null)
{
return defaultTargetSqlSessionFactory;
}
return this.sqlSessionFactory;
}
private class SqlSessionInterceptor implements InvocationHandler
{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
final SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
try
{
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory()))
{
sqlSession.commit(true);
}
return result;
}
catch (Throwable t)
{
Throwable unwrapped = unwrapThrowable(t);
if (DynamicSqlSessionTemplate.this.exceptionTranslator != null
&& unwrapped instanceof PersistenceException)
{
Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null)
{
unwrapped = translated;
}
}
throw unwrapped;
}
finally
{
closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
}
}
}
}
多数据源配置:自行根据项目所用的进行配置
@Override
@Transactional
public Map testDataSource() {
SpringUtils.getAopProxy(this).insertA();
SpringUtils.getAopProxy(this).insertB();
int i=10/0;
return new HashMap();
}
@DataSource(value = DataSourceType.slave)
public void insertA(){
dynamicTestMapper.dynamicTestA();
}
@DataSource(value = DataSourceType.master)
public void insertB(){
dynamicTestMapper.dynamicTestB();
}