之前的 《spring 動(dòng)態(tài)切換获询、添加數(shù)據(jù)源實(shí)現(xiàn)以及源碼淺析》 中介紹了如何使用 spring 提供的 AbstractRoutingDataSource 配置多數(shù)據(jù)源,有了多數(shù)據(jù)源自然要管理事務(wù)的一致性仍翰。
上篇文章中提到過配置多數(shù)據(jù)源的兩種方式
- 使用AbstractRoutingDataSource
- 配置多個(gè) SqlSessionFactory
前言
閱讀了一下spring的源碼,由于 spring 事務(wù)的機(jī)制观话,在開啟事務(wù)之前spring 會(huì)去創(chuàng)建當(dāng)前數(shù)據(jù)源的 事務(wù)object予借,直到事務(wù)提交,spring 都不會(huì)在乎你是否切換了數(shù)據(jù)源频蛔。這就導(dǎo)致了灵迫,使用 AbstractRouting DataSource 方式開啟事務(wù)時(shí),切換數(shù)據(jù)源不生效晦溪。
關(guān)于如何解決這個(gè)問題瀑粥,感興趣的朋友可以去閱讀一下:http://www.reibang.com/p/61e8961c6154
本文只討論上述第二種方式結(jié)合 atomikos 管理多數(shù)據(jù)源事務(wù)。
Atomikos
來自:http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-jta.html
Atomikos is a popular open source transaction manager which can be embedded into your Spring Boot application. You can use the
spring-boot-starter-jta-atomikos
Starter to pull in the appropriate Atomikos libraries. Spring Boot auto-configures Atomikos and ensures that appropriatedepends-on
settings are applied to your Spring beans for correct startup and shutdown ordering.
By default, Atomikos transaction logs are written to a
transaction-logs
directory in your application’s home directory (the directory in which your application jar file resides). You can customize the location of this directory by setting aspring.jta.log-dir
property in yourapplication.properties
file. Properties starting withspring.jta.atomikos.properties
can also be used to customize the AtomikosUserTransactionServiceImp
. See theAtomikosProperties
Javadoc for complete details.
引入spring-boot-starter-jta-atomikos三圆,spring boot 為我們自動(dòng)配置
Atomikos狞换,我們可以通過 spring.jta.xxx
修改默認(rèn)配置。
Talk is cheap. Show me the code
- demo源碼:https://gitee.com/yintianwen7/taven-springboot-learning/tree/master/spring-atomikos
- 添加 maven 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
- application.properties
#spring.jta.log-dir=classpath:tx-logs
spring.jta.transaction-manager-id=txManager
spring.datasource.druid.system-db.name=system-db
spring.datasource.druid.system-db.url=jdbc:mysql://localhost:3306/test1?useSSL=false
spring.datasource.druid.system-db.username=root
spring.datasource.druid.system-db.password=taven753
spring.datasource.druid.system-db.initialSize=5
spring.datasource.druid.system-db.minIdle=5
spring.datasource.druid.system-db.maxActive=20
spring.datasource.druid.system-db.maxWait=60000
spring.datasource.druid.system-db.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.system-db.minEvictableIdleTimeMillis=30000
spring.datasource.druid.system-db.validationQuery=SELECT 1
spring.datasource.druid.system-db.validationQueryTimeout=10000
spring.datasource.druid.system-db.testWhileIdle=true
spring.datasource.druid.system-db.testOnBorrow=false
spring.datasource.druid.system-db.testOnReturn=false
spring.datasource.druid.system-db.poolPreparedStatements=true
spring.datasource.druid.system-db.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.system-db.filters=stat,wall
spring.datasource.druid.system-db.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.system-db.useGlobalDataSourceStat=true
spring.datasource.druid.business-db.name=business-db
spring.datasource.druid.business-db.url=jdbc:mysql://localhost:3306/test2?useSSL=false
spring.datasource.druid.business-db.username=root
spring.datasource.druid.business-db.password=taven753
spring.datasource.druid.business-db.initialSize=5
spring.datasource.druid.business-db.minIdle=5
spring.datasource.druid.business-db.maxActive=20
spring.datasource.druid.business-db.maxWait=60000
spring.datasource.druid.business-db.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.business-db.minEvictableIdleTimeMillis=30000
spring.datasource.druid.business-db.validationQuery=SELECT 1
spring.datasource.druid.business-db.validationQueryTimeout=10000
spring.datasource.druid.business-db.testWhileIdle=true
spring.datasource.druid.business-db.testOnBorrow=false
spring.datasource.druid.business-db.testOnReturn=false
spring.datasource.druid.business-db.poolPreparedStatements=true
spring.datasource.druid.business-db.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.business-db.filters=stat,wall
spring.datasource.druid.business-db.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.business-db.useGlobalDataSourceStat=true
- system 數(shù)據(jù)源的配置類
@Configuration
@MapperScan(basePackages = SystemDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "systemSqlSessionFactory")
public class SystemDataSourceConfig {
static final String PACKAGE = "com.gitee.taven.mapper.system";
@Autowired
private SystemProperties systemProperties;
@Bean(name = "systemDataSource")
@Primary
public DataSource systemDataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaProperties(PojoUtil.obj2Properties(systemProperties));
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("systemDataSource");
ds.setPoolSize(5);
ds.setTestQuery("SELECT 1");
return ds;
}
@Bean
@Primary
public SqlSessionFactory systemSqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(systemDataSource());
return sqlSessionFactoryBean.getObject();
}
}
- 更新于 2019.6.24舟肉,在簡友LT的幫助下修噪,發(fā)現(xiàn)了一個(gè)bug,由于MySQL的wait_timeout 機(jī)制度气,會(huì)導(dǎo)致數(shù)據(jù)庫連接在長時(shí)間未使用的情況(MySQL默認(rèn)是8小時(shí))下失效
雖然我們配置了Druid的檢查機(jī)制割按,但是還要在AtomikosDataSourceBean 的屬性上添加一個(gè)屬性,如下:
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
// 省略
ds.setTestQuery("SELECT 1");
- business 數(shù)據(jù)源的配置類同上
- 省略了 mybatis 代碼磷籍,通過service 測試事務(wù)适荣,拋出異常后,事務(wù)會(huì)回滾
@Service
public class UserService {
@Autowired private UsersMapper usersMapper;
@Autowired private UserInformationsMapper userInformationsMapper;
@Transactional
public void testJTA() {
Users u = new Users();
u.setUsername("hmj");
u.setPassword("hmjbest");
usersMapper.insertSelective(u);
UserInformations ui = new UserInformations();
ui.setUserid(666l);
ui.setEmail("dsb");
userInformationsMapper.insertSelective(ui);
// int i = 10/0;
}
}