配置多个数据源的事务
一、使用场景
在我们的工作中,肯定会遇到多数据源的情况,如何配置多数据源我前面也有文章写过,这里就不多讨论了。今天学到了一手配置多数据源的事务,就给大家分享分享吧。
其实Spring也有事务注解@Transactional,但是他只能满足一种数据源事务的提交,而我们分布式项目肯定不止步于单数据源,有着多个多种数据源,如果要在b数据源删除一个数据,a数据源增加一个数据的话,程序不出问题还好,一旦报出异常就可能导致a数据源删除数据成功,b数据源增加数据失败的情况,而@Transactional注解是做不到这类事务的回滚的,这个时候就需要配置多数据源的事务了。今天就讲一讲具体的配置步骤,主要思想还是利用Spring的AOP实现。
二、配置步骤
-
第一步当然是配置文件配置数据源,application.yml文件如下:
spring: datasource: druid: pethome: url: jdbc:mysql:///pethome?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource book: url: jdbc:mysql:///book_manager?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource
-
第二步就是创建一个配置类,然后配置yml文件里面的数据源为Datasource.以及两个数据源的事务管理器。
@Configuration public class DatasourceConfig { @Bean(name = "book") @Primary @ConfigurationProperties(prefix = "spring.datasource.druid.book") public DataSource book() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "pethome") @ConfigurationProperties(prefix = "spring.datasource.druid.pethome") public DataSource pethome() { return DruidDataSourceBuilder.create().build(); } @Bean("pethomeManager") public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("pethome") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean("bookManager") @Primary public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("book") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
-
第三步是配置一个多数据源的注解@DatasourceTransactionals
/** * 事务注解 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface DataSourceTransactionals { /** * 事务管理器数组 */ String[] transactionManagers(); }
-
第四步创建注解的AOP,为多数据源提交回滚事务。
@Component @Aspect @Order(-1) public class DataSourceTransactionAspect { /** * 线程本地变量:为什么使用栈?※为了达到后进先出的效果※ */ private static final ThreadLocal<Stack<Pair<DataSourceTransactionManager, TransactionStatus>>> THREAD_LOCAL = new ThreadLocal<>(); /** * 用于获取事务管理器 */ @Autowired private ApplicationContext applicationContext; //设置事务定义器 public DefaultTransactionDefinition getTransactionDefinition(){ DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // 非只读模式 def.setReadOnly(false); // 事务隔离级别:采用数据库的 def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); // 事务传播行为 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); return def; } /** * 切面 */ @Pointcut("@annotation(cn.antu.dynamic.annotation.DataSourceTransactionals)") public void pointcut() { } /** * 声明事务 * * @param transactional 注解 */ @Before("pointcut() && @annotation(transactional)") public void before(DataSourceTransactionals transactional) { // 根据设置的事务名称按顺序声明,并放到ThreadLocal里 String[] transactionManagerNames = transactional.transactionManagers(); Stack<Pair<DataSourceTransactionManager, TransactionStatus>> pairStack = new Stack<>(); for (String transactionManagerName : transactionManagerNames) { DataSourceTransactionManager transactionManager = applicationContext.getBean(transactionManagerName, DataSourceTransactionManager.class); TransactionStatus transactionStatus = transactionManager.getTransaction(getTransactionDefinition()); pairStack.push(new Pair(transactionManager, transactionStatus)); } THREAD_LOCAL.set(pairStack); } /** * 提交事务 */ @AfterReturning("pointcut()") public void afterReturning(){ // ※栈顶弹出(后进先出) Stack<Pair<DataSourceTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get(); while (!pairStack.empty()) { Pair<DataSourceTransactionManager, TransactionStatus> pair = pairStack.pop(); pair.getKey().commit(pair.getValue()); } THREAD_LOCAL.remove(); } /** * 回滚事务 */ @AfterThrowing(value = "pointcut()") public void afterThrowing() { // ※栈顶弹出(后进先出) Stack<Pair<DataSourceTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get(); while (!pairStack.empty()) { Pair<DataSourceTransactionManager, TransactionStatus> pair = pairStack.pop(); pair.getKey().rollback(pair.getValue()); } THREAD_LOCAL.remove(); } }
-
下面就是测试,这里使用的是jdbctemplate,如果你使用的是mybatis还需要第六步。
public class TestController { @Resource(name = "pethome") private DataSource pethome; @Resource(name = "book") private DataSource book; // @Resource(name = "bookTransactionManager") // private DataSourceTransactionManager dataSourceTransactionManager; @RequestMapping(value="/test1",method= RequestMethod.POST) @ApiOperation(value = "测试jdbc", notes = "测试") @DataSourceTransactionals(transactionManagers = {"pethomeManager","bookManager"}) public String test(){ JdbcTemplate jdbcTemplate1 = new JdbcTemplate(book); jdbcTemplate1.update("delete FROM books WHERE id = 19"); JdbcTemplate jdbcTemplate = new JdbcTemplate(pethome); jdbcTemplate.update("delete FROM t_menu WHERE id = 22"); jdbcTemplate.update("delete FROM t_employee WHERE id = 338"); System.out.println(1/0); return "11"; } }
-
mybatis专属配置,非必须
public class MybatisConfig { @Configuration @MapperScan(basePackages = "cn.antu.dynamic.mapper", sqlSessionTemplateRef = "sqlSessionTemplate") public static class bookConfig { @Bean @Primary public SqlSessionFactory sqlSessionFactory(@Qualifier("book") DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } @Bean @Primary public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } @Configuration @MapperScan(basePackages = "cn.antu.dynamic.mapper", sqlSessionTemplateRef = "sqlSessionTemplate2") public static class pethomeConfig { @Bean public SqlSessionFactory sqlSessionFactory2(@Qualifier("pethome") DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate2(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } }
三、总结
总的来说还是很简单的,把代码复制到项目中,数据源改一改即可使用。其实最主要的还是切面哪里,首先定义一个DefaultTransactionDefinition类,然后在before方法里面遍历出@DatasourceTrsactionals注解设置的事务管理器数组,利用ApplicationContext得到对应的事务管理器bean,再得到TransactionStatus,然后以对应的事务管理器为key,TransactionStatus为value放到pair中,最后在入栈。遍历完以后放到ThreadLocal中去。接着是afterReturning方法用于提交事务,先在ThreadLocal里面得到当前线程的stack<pair<DataSourceTransactionManager, TransactionStatus>>,然后利用key/value提交事务,至于回滚是在afterThrowing方法里,和提交类似。最后提交或者回滚的时候记得删除当前栈哟(threadlocal.remove())哟。
今天的分享就到这里了,感谢大家的观看。欢迎大家收藏订阅哟!