【SpringBoot2.0】基于Atomikos的多数据源分布式事务(XA)解决方案
最近工作中在同一项目中用到了多数据源,虽然项目本身对多数据源的事务没有要求,甚至可以不使用事务。但是本着精益求精的原则,加上各种资料的查阅,终于实现了多数据源的XA(分布式事务)
项目框架
- springboot2.0.x
- springmvc
- mybatis
对多数据源的mapper的处理
针对多个数据源,mapper通常有两种处理方式:一种是将各个数据源对应的mapper放置在不同的package中,通过配置各个对应的SqlSessionFactory
来实例化各自的mapper;另一种是使用动态数据源,即在程序运行时,通过注解or函数动态的设置当前使用哪个数据源,而mapper本身是不知道自己会使用哪个数据源的
目前我使用的是前一种方案,毕竟这种方案各个mapper都很清楚的知道自己将会使用哪个数据源,第二种方案我目前还没使用的需求。
对于如何配置各个数据源对应不同的mapper,我会再后续的文章中阐述。本文着重阐述如何在springboot
中配置XA
第一步,引入spring-boot-starter
和spring-boot-starter-jta-atomikos
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
在springboot
中配置xa共需要配置两个东西,一个是支持xa的数据源,一个是基于xa的分布式事务管理器
因为在项目中我们已经引入了spring-boot-starter-jta-atomikos
,它会自带一个支持xa的分布式事务管理器(此依赖还会自动创建一个支持xa的数据源,但是只能自动创建一个,而我们如果用xa的话,肯定是多数据源,所以这里感觉springboot的这个“默认”配置有点zz),因此我们只需要配置好多个数据源即可(必须是支持xa的数据源)
我们这里以两个数据源为例:
我们先构造一个数据源创建器
@Component
public class DataSourceConfig implements BeanClassLoaderAware{
@Setter
ClassLoader beanClassLoader;
protected XADataSource createXaDataSource(DataSourceProperties properties) {
String className = properties.getXa().getDataSourceClassName();
if (!StringUtils.hasLength(className)) {
className = DatabaseDriver.fromJdbcUrl(properties.determineUrl())
.getXaDataSourceClassName();
}
Assert.state(StringUtils.hasLength(className),
"No XA DataSource class name specified");
XADataSource dataSource = createXaDataSourceInstance(className);
bindXaProperties(dataSource, properties);
return dataSource;
}
private XADataSource createXaDataSourceInstance(String className) {
try {
Class<?> dataSourceClass = ClassUtils.forName(className, this.beanClassLoader);
Object instance = BeanUtils.instantiateClass(dataSourceClass);
Assert.isInstanceOf(XADataSource.class, instance);
return (XADataSource) instance;
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to create XADataSource instance from '" + className + "'");
}
}
private void bindXaProperties(XADataSource target,
DataSourceProperties dataSourceProperties) {
Binder binder = new Binder(getBinderSource(dataSourceProperties));
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
}
private ConfigurationPropertySource getBinderSource(
DataSourceProperties dataSourceProperties) {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("user", dataSourceProperties.determineUsername());
source.put("password", dataSourceProperties.determinePassword());
source.put("url", dataSourceProperties.determineUrl());
source.putAll(dataSourceProperties.getXa().getProperties());
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAliases("user", "username");
return source.withAliases(aliases);
}
}
这里思路是使用特定的配置文件(DataSourceProperties
)来构建一个DataSource对象,这里有不少代码都参考的SpringBoot中XADataSourceAutoConfiguration.java
的源码
然后我们构建两个数据源:
第一个数据源
@Configuration
public static class MybatisDbBConfigODS extends DataSourceConfig{
@Autowired
XADataSourceWrapper wrapper;
@Bean
@Primary
@ConfigurationProperties("spring.datasource.ods")
public DataSourceProperties getDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource getDataSource() throws Exception {
XADataSource xaDataSource = createXaDataSource(getDataSourceProperties());
return this.wrapper.wrapDataSource(xaDataSource);
}
}
这里的思路是先将当前数据源需要的属性(例如用户名、密码、数据库连接地址等)加载到DataSourceProperties
中,然后使用此对象来创建DataSource
对象,注意这里的XADataSourceWrapper
对象,它的作用是将XA数据源转化为普通的数据源。
第二个数据源的创建方法类似:
@Configuration
@MapperScan(basePackages = "com.xinbo.ods.dao.cdr", sqlSessionFactoryRef = "sqlSessionFactory4cdr", sqlSessionTemplateRef = "sqlSessionTemplate4cdr", annotationClass = Mapper.class)
public static class MybatisDbBConfigCDR extends DataSourceConfig{
@Autowired
XADataSourceWrapper wrapper;
@Bean("UNJi29SsAF19WVCkeKdt")
@ConfigurationProperties("spring.datasource.cdr")
public DataSourceProperties getDataSourceProperties() {
return new DataSourceProperties();
}
@Bean("ahS54La6KNmWnchdQQil")
public DataSource getDataSource4cdr() throws Exception {
XADataSource xaDataSource = createXaDataSource(getDataSourceProperties());
return this.wrapper.wrapDataSource(xaDataSource);
}
}
要注意的是,最好每个@Bean
注解中使用唯一的名字,否则会和上的重名,然后出现问题
到这里所有的配置已经配置好了,然后我们使用事务时,只需要在需要的类或方法上加上@Transactional
注解即可,当一个事务中同时操作多个数据库时,遇到异常会回滚对所有数据库的操作。
PS:对于springboot是如何创建支持XA的事务管理器TransactionManager
的,如果有兴趣,可以参考这个类的源码:AtomikosJtaConfiguration.java