springboot+mybatis多数据源的事务问题
1.springboot+mybatis实现多数据源后,针对单个数据源我们可以使用@Transactional(name="xxxTransactionManager")
来指定使用的事务管理器,但是如果被注解的方法需要同时支持两个事务管理器呢,这个时候如果用@Transactional注解就不
合适了,属性name只支持字符串类型,所以只能填一个,如果不传name属性,而且项目没有配置默认的事务管理器,在调用方
法时则会抛出未指定默认事务管理器的异常。这个时候,我们可以用编程式事务来解决这个问题。
2.多数据源配置已经完成,但是还没有事务管理器,所以先配置支持事务。
import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; 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.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; /** * 数据源1 */ @Configuration @MapperScan(basePackages = "com.example.mybatis.mapper",sqlSessionFactoryRef = "sqlSessionFactoryOne") public class DataSourceConfigOne { @Bean(name = "dataSourceOne") @Primary// 表示这个数据源是默认数据源 // 读取application.properties中的配置参数映射成为一个对象,prefix表示参数的前缀 @ConfigurationProperties(prefix = "spring.datasource.one") public DataSource dataSourceOne() { return DataSourceBuilder.create().build(); } @Bean(name = "sqlSessionFactoryOne") @Primary public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource datasource)throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( // 设置mybatis的xml所在位置 new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); return bean.getObject(); } @Bean(name = "transactionManagerOne") public PlatformTransactionManager transactionManagerOne(@Qualifier("dataSourceOne") DataSource dataSourceOne) { return new DataSourceTransactionManager(dataSourceOne); } @Primary public SqlSessionTemplate sqlsessiontemplateOne(@Qualifier("sqlsessiontemplateOne") SqlSessionFactory sessionfactory) { return new SqlSessionTemplate(sessionfactory); } }
import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; 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.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; /** * 数据源2 */ @Configuration @MapperScan(basePackages = "com.example.mybatis.mapper2",sqlSessionFactoryRef = "sqlSessionFactoryTwo") public class DataSourceConfigTwo { @Bean(name = "dataSourceTwo") // 读取application.properties中的配置参数映射成为一个对象,prefix表示参数的前缀 @ConfigurationProperties(prefix = "spring.datasource.two") public DataSource dataSourceTwo() { return DataSourceBuilder.create().build(); } @Bean(name = "sqlSessionFactoryTwo") public SqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource datasource)throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( // 设置mybatis的xml所在位置 new PathMatchingResourcePatternResolver().getResources("classpath:mapper2/*.xml")); bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);//下划线-驼峰映射 return bean.getObject(); } @Bean(name = "transactionManagerTwo") public PlatformTransactionManager transactionManagerTwo(@Qualifier("dataSourceTwo") DataSource dataSourceTwo) { return new DataSourceTransactionManager(dataSourceTwo); } public SqlSessionTemplate sqlsessiontemplateTwo(@Qualifier("sqlsessiontemplateTwo") SqlSessionFactory sessionfactory) { return new SqlSessionTemplate(sessionfactory); } }
3.使用自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.PARAMETER}) public @interface MoreTransaction { String[] value() default {}; }
4.事务切面
@Aspect @Component public class TransactionAop { @Pointcut("@annotation(com.example.mybatis.config.aop.annotation.MoreTransaction)") public void MoreTransaction() { } @Pointcut("execution(* com.example.mybatis.controller.*.*(..))") public void excudeController() { } @Around(value = "MoreTransaction()&&excudeController()&&@annotation(annotation)") public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, MoreTransaction annotation) throws Throwable { Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>(); Stack<TransactionStatus> transactionStatuStack = new Stack<>(); try { if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) { return null; } Object ret = thisJoinPoint.proceed(); commit(dataSourceTransactionManagerStack, transactionStatuStack); return ret; } catch (Throwable e) { rollback(dataSourceTransactionManagerStack, transactionStatuStack); log.error(String.format("MultiTransactionalAspect, method:%s-%s occors error:", thisJoinPoint.getTarget().getClass().getSimpleName(), thisJoinPoint.getSignature().getName()), e); throw e; } } /** * 开启事务处理方法 * * @param dataSourceTransactionManagerStack * @param transactionStatuStack * @param multiTransactional * @return */ private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatuStack,MoreTransaction multiTransactional) { String[] transactionMangerNames = multiTransactional.value(); if (ArrayUtils.isEmpty(multiTransactional.value())) { return false; } for (String beanName : transactionMangerNames) { DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil.getBean(beanName); TransactionStatus transactionStatus = dataSourceTransactionManager .getTransaction(new DefaultTransactionDefinition()); transactionStatuStack.push(transactionStatus); dataSourceTransactionManagerStack.push(dataSourceTransactionManager); } return true; } /** * 提交处理方法 * * @param dataSourceTransactionManagerStack * @param transactionStatuStack */ private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop()); } } /** * 回滚处理方法 * @param dataSourceTransactionManagerStack * @param transactionStatuStack */ private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop()); } } }
5.service层两个我要新增的方法,这两个方法是两个数据源(一个是我本地的,一个是阿里云的)
@Override public int addUserInfo(UserInfo record) { return userInfoMapper.insert(new UserInfo().setId(UUID.randomUUID().toString()).setUserAccount("吾问无为谓")); }
@Override public int addUser(User record) { return userMapper.insertSelective(record); }
6.controller层的调用方法上使用事务注解,将两个数据源的事务管理器名字作为参数传入。
@GetMapping("/dataSourceList") @ResponseBody @MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"}) public ResultData getDataSourceList(){ int i=userService.addUser(new User().setPhone("11111111111").setUserName("哈哈哈")); int a=1/0; int k=userService.addUserInfo(new UserInfo().setId(UUID.randomUUID().toString()).setUserAccount("吾问无为谓")); Map map=new HashMap(); map.put("k",k); return ResultData.success(map); }
7.提交请求后会发现控制台报错,但是数据库里面并没有插入数据。