此项目为单体应用,不涉及到分布式。所以没有采用分布式事务来解决此次问题。
springboot在多数据源时默认只能开启一个主数据库的事务,如果要同时开启多个数据源的事务,并回滚,需要在切面中手动开启所有数据源事务,并同时回滚。
org.springframework.boot
spring-boot-starter-web
com.oracle
ojdbc6
11.2.0.3
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-aop
server.port=8080
##数据源1
spring.datasource.one.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.one.jdbc-url=jdbc:oracle:thin:@192.168.111.200:1521:orcl
spring.datasource.one.username=root
spring.datasource.one.password=root
##数据源2
spring.datasource.two.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.two.jdbc-url=jdbc:oracle:thin:@192.168.111.201:1521:orcl
spring.datasource.two.username=root
spring.datasource.two.password=root
package com.study.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* 数据源配置
*/
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.one")
DataSource dsOne() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.two")
DataSource dsTwo() {
return DataSourceBuilder.create().build();
}
@Primary//系统应该默认执行该方法,如获取时不指定名称,则默认获取这个数据源,如果不添加,则启动时候会报错
@Bean("jdbcTemplateOne")
JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean("jdbcTemplateTwo")
JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 配置第一个数据源的事务管理
*/
@Bean(name = "oneTransactionManager")
@Primary
public PlatformTransactionManager oneTransactionManager(@Qualifier("dsOne") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* 配置第二个数据源的事务管理
*/
@Bean(name = "twoTransactionManager")
public PlatformTransactionManager twoTransactionManager(@Qualifier("dsTwo") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
package com.study.anno;
import java.lang.annotation.*;
/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME)//控制注解的生命周期,定义我们自己写的注解何时有效
@Target(ElementType.METHOD)//定义我们写的注解可以描述的成员,这里表示只能在方法上使用
public @interface MyTransactional {
String[] transactionManagers();
}
package com.study.aop;
import com.study.anno.MyTransactional;
import javafx.util.Pair;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.Stack;
/**
* 多数据源切面
*/
@Component
@Aspect
public class TransactionAspect {
/**
* 线程本地变量:为什么使用栈?※为了达到后进先出的效果※
*/
private static final ThreadLocal>> THREAD_LOCAL = new ThreadLocal<>();
/**
* 用于获取事务管理器
*/
@Autowired
private ApplicationContext applicationContext;
/**
* 切面
*/
@Pointcut("@annotation(transactional)")
public void pointcut(MyTransactional transactional) {
}
/**
* 声明事务
* @param transactional 注解
*/
@Before("pointcut(transactional)")
public void before(MyTransactional transactional) {
// 根据设置的事务名称按顺序声明,并放到ThreadLocal里
String[] transactionManagerNames = transactional.transactionManagers();
Stack> pairStack = new Stack<>();
for (String transactionManagerName : transactionManagerNames) {
DataSourceTransactionManager transactionManager = applicationContext.getBean(transactionManagerName, DataSourceTransactionManager.class);
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 非只读模式
def.setReadOnly(false);
// 事务隔离级别:采用数据库的
def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
// 事务传播行为
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus transactionStatus = transactionManager.getTransaction(def);
pairStack.push(new Pair(transactionManager, transactionStatus));
}
THREAD_LOCAL.set(pairStack);
}
/**
* 提交事务
*/
@AfterReturning("pointcut(transactional)")
public void afterReturning(MyTransactional transactional) {
// ※栈顶弹出(后进先出)
Stack> pairStack = THREAD_LOCAL.get();
while (!pairStack.empty()) {
Pair pair = pairStack.pop();
pair.getKey().commit(pair.getValue());
}
THREAD_LOCAL.remove();
}
/**
* 回滚事务
*/
@AfterThrowing(value = "pointcut(transactional)")
public void afterThrowing(MyTransactional transactional) {
// ※栈顶弹出(后进先出)
Stack> pairStack = THREAD_LOCAL.get();
while (!pairStack.empty()) {
Pair pair = pairStack.pop();
pair.getKey().rollback(pair.getValue());
}
THREAD_LOCAL.remove();
}
}
package com.study.service;
import com.study.anno.MyTransactional;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class DemoService {
//@Autowired默认按类型装配,如果想使用名称装配可以结合@Qualifier注解进行使用。
//@Resource,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。
@Resource
private JdbcTemplate jdbcTemplateOne;
@Resource
private JdbcTemplate jdbcTemplateTwo;
@MyTransactional(transactionManagers = {"oneTransactionManager", "twoTransactionManager"})
public void test() {
insert1();
insert2();
int c = 1 / 0;
}
public void insert1() {
String sql1 = "insert into mytable1(a) values ('5555')";
int update1 = jdbcTemplateOne.update(sql1);
System.out.println(update1);
}
public void insert2() {
String sql2 = "insert into mytable2(a) values ('5555')";
int update2 = jdbcTemplateTwo.update(sql2);
System.out.println(update2);
}
}
参考
多数据源事务(非分布式)_SomeOtherTime的博客-CSDN博客_多数据源事务