如下配置了两个数据源:
spring:
datasource:
ds1:
jdbc-url: jdbc:mysql://localhost:3307/spring-boot-demos?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: password
ds2:
jdbc-url: jdbc:mysql://127.0.0.1:3308/spring-boot-demos?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: password
同时,配置了响应的数据源配置:
@Configuration
public class DataSourceConfigurations {
@Configuration
@MapperScan(basePackages = "com.example.mapper.ds1",
sqlSessionFactoryRef = "ds1SqlSessionFactory")
public static class Ds1Configuration {
public static final String DS1_MAPPER_LOCATION = "classpath*:mapper/ds1/*.xml";
@Primary
@Bean("ds1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.ds1")
public DataSource ds1DataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean("ds1TransactionManager")
public PlatformTransactionManager ds1TransactionManager(@Qualifier("ds1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Primary
@Bean(name = "ds1SqlSessionFactory")
public SqlSessionFactory ds1SqlSessionFactory(@Qualifier("ds1DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(DS1_MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
}
@Configuration
@MapperScan(basePackages = "com.example.ds2",
sqlSessionFactoryRef = "ds2SqlSessionFactory")
public static class Ds2Configuration {
public static final String DS2_MAPPER_LOCATION = "classpath*:mapper/ds2/*.xml";
@Bean
@ConfigurationProperties(prefix = "spring.datasource.ds2")
public DataSource ds2DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public PlatformTransactionManager ds2TransactionManager(@Qualifier("ds2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "ds2SqlSessionFactory")
public SqlSessionFactory ds2SqlSessionFactory(@Qualifier("ds2DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(DS2_MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
}
}
相对应的,有两个Mapper:
@Data
@TableName("tb_test1")
public class Test1DO {
@TableId
private Integer id;
private LocalDateTime createTime;
private boolean deleted;
private String name;
private Short status;
}
@Mapper
public interface Ds1Test1Mapper extends BaseMapper<Test1DO> {
}
@Mapper
public interface Ds2Test1Mapper extends BaseMapper<Test1DO> {
}
首先,先向ds2数据库的tb_test1表中插入id=1的数据。然后做如下操作:
@RequiredArgsConstructor
@Service
public class Test1ServiceImpl implements ITest1Service {
private final Ds1Test1Mapper ds1Test1Mapper;
private final Ds2Test1Mapper ds2Test1Mapper;
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void save() {
Test1DO test1DO = new Test1DO();
test1DO.setId(1);
test1DO.setName("test ds2");
test1DO.setStatus((short) 1);
ds1Test1Mapper.insert(test1DO);
ds2Test1Mapper.insert(test1DO);
}
@Override
public void save1() {
Test1DO test1DO = new Test1DO();
test1DO.setId(1);
test1DO.setName("test ds2");
test1DO.setStatus((short) 1);
ds1Test1Mapper.insert(test1DO);
ds2Test1Mapper.insert(test1DO);
}
}
当调用save1()时,ds1中插入了一条数据,而ds2中由于id冲突而失败。
如果调用save()时,ds1和ds2中都插入失败(ds1的数要清掉).
这个时候对save()进行断点:
//TransactionAspectSupport.java 377行 (springboot版本2.7.17)
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
发现ptm是上面配置的Primary TransactionManager。
如果将两个数据库表数据都清空,然后执行save(),对 commit 进行断点:
// DataSourceTransactionManager.doCommit()
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw translateException("JDBC commit", ex);
}
}
对 con 进行断点发现,其为3307的数据库的连接,然后整个过程没有别的连接了。并且3308的数据也能正常保存。
以上也就是两点:
1、当使用多个SqlSessionFactory配置数据源时,公用同一个TransactionManager,和同一个Connection(3307),这个时候,3308数据正常提交。
2、当提交3308的数据发生异常时,3307的数据也不会插入,即有事务属性。
目前对于上述两点不是很理解,为何只有3307的Connection,3308的数据也能提交,而且为何会有事务性。
可能是作者对SpringBoot的事务不是理解的很深入,希望有大佬能解惑。