在Spring Boot和MyBatis中,我們有時需要在方法中同時使用兩個不同的數(shù)據(jù)庫,但使用
@Transactional
注解會變得復雜。這時我們可以用一種更靈活的方法來處理。
想象一下這樣的場景:我們有兩個數(shù)據(jù)庫,我們希望在一個方法中同時操作它們,但是普通的@Transactional
注解變得不太適用。
我們可以采用一種類似于“雙提交”的策略來解決這個問題。首先,我們讓兩個數(shù)據(jù)庫執(zhí)行所需的操作,然后立即提交。接下來,如果整個方法執(zhí)行成功,我們就提交這兩個數(shù)據(jù)庫的事務。但是,如果在方法執(zhí)行過程中出現(xiàn)了問題,我們會回滾這兩個數(shù)據(jù)庫的事務。
簡單來說,我們先讓兩個數(shù)據(jù)庫做好準備,等到方法完成后,如果一切順利,我們正式確認這兩個數(shù)據(jù)庫的操作。如果出現(xiàn)了錯誤,我們撤銷之前的操作,就像玩一個雙關游戲一樣。
通過這種方法,我們能夠更加靈活地在方法中操作多個數(shù)據(jù)庫,而不用被注解的方式束縛。這種方式讓事務的控制更加精準,保證了數(shù)據(jù)的一致性。文章來源:http://www.zghlxwxcb.cn/news/detail-790917.html
1. 使用實例
首先看一下如何使用,下面的方法里有兩條sql,分別向兩個不同的數(shù)據(jù)庫插入數(shù)據(jù),我們在方法上加自定義注解`@MoreTransaction`,里面?zhèn)魅雰蓚€事務管理器的beann名稱,當有異常時,自定義注解的切面方法攔截到異常,兩條插入語句sql都會被回滾。
@MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"})
public ResultData getDataSourceList(){
//向第一個數(shù)據(jù)庫插入數(shù)據(jù)
int i=userService.addUser(new User().setUserName("數(shù)據(jù)庫1"));
//故意制造異常,拋出給事務切面
int a=1/0;
//向第二個是數(shù)據(jù)庫插入數(shù)據(jù)
int k=userService.addUserInfo(new UserInfo().setUserAccount("數(shù)據(jù)庫2"));
Map map=new HashMap();
map.put("k",k);
return ResultData.success(map);
}
2. 首先分別配置兩個數(shù)據(jù)庫的數(shù)據(jù)源和事務管理器。
- 定義第一個第一個數(shù)據(jù)源
DataSourceOne
,定義事務管理器的bean為transactionManagerOne
/**
* 數(shù)據(jù)源1
*/
@Configuration
@MapperScan(basePackages = "com.example.mybatis.mapper",sqlSessionFactoryRef = "sqlSessionFactoryOne")
public class DataSourceConfigOne {
//配置第一個數(shù)據(jù)源的事務管理器,定義bean名稱為 transactionManagerOne
@Bean(name = "transactionManagerOne")
public PlatformTransactionManager transactionManagerOne(@Qualifier("dataSourceOne") DataSource dataSourceOne) {
return new DataSourceTransactionManager(dataSourceOne);
}
// --- 下面是配置數(shù)據(jù)源的代碼 --
@Bean(name = "dataSourceOne")
@Primary// 表示這個數(shù)據(jù)源是默認數(shù)據(jù)源
// 讀取application.properties中的配置參數(shù)映射成為一個對象,prefix表示參數(shù)的前綴
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource dataSourceOne() {
return DataSourceBuilder.create().build();
}
@Primary
public SqlSessionTemplate sqlsessiontemplateOne(@Qualifier("sqlsessiontemplateOne") SqlSessionFactory sessionfactory) {
return new SqlSessionTemplate(sessionfactory);
}
@Bean(name = "sqlSessionFactoryOne")
@Primary
public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource datasource)throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(datasource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}
}
- 定義第一個第一個數(shù)據(jù)源
DataSourceTwo
,定義事務管理器的bean為transactionManagerTwo
/**
* 數(shù)據(jù)源2
*/
@Configuration
@MapperScan(basePackages = "com.example.mybatis.mapper2",sqlSessionFactoryRef = "sqlSessionFactoryTwo")
public class DataSourceConfigTwo {
//配置第一個數(shù)據(jù)源的事務管理器,定義bean名稱為 transactionManagerOne
@Bean(name = "transactionManagerTwo")
public PlatformTransactionManager transactionManagerTwo(@Qualifier("dataSourceTwo") DataSource dataSourceTwo) {
return new DataSourceTransactionManager(dataSourceTwo);
}
// --- 下面是配置數(shù)據(jù)源的代碼 --
@Bean(name = "dataSourceTwo")
@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();
}
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. 事務切面方法,多數(shù)據(jù)源事務的實現(xiàn)(重點)
@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<>();
//存放事務的狀態(tài),每一個DataSourceTransactionManager 對應一個 TransactionStatus
Stack<TransactionStatus> transactionStatuStack = new Stack<>();
try {
//判斷自定義注解@MoreTransaction 是否傳入事務管理器的名字,將自定義注解的值對應的事務管理器入棧
if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {
return null;
}
//執(zhí)行業(yè)務方法
Object ret = thisJoinPoint.proceed();
//如果沒有異常,說明兩個sql都執(zhí)行成功,兩個數(shù)據(jù)源的sql全部提交事務
commit(dataSourceTransactionManagerStack, transactionStatuStack);
return ret;
} catch (Throwable e) {
//業(yè)務代碼發(fā)生異常,回滾兩個數(shù)據(jù)源的事務
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;
}
// 遍歷事務管理器名字數(shù)組,逐個開啟事務并將事務狀態(tài)和管理器存入棧中
for (String beanName : transactionMangerNames) {
// 從Spring上下文中獲取事務管理器
DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil.getBean(beanName);
// 創(chuàng)建新的事務狀態(tài)
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(new DefaultTransactionDefinition());
// 將事務狀態(tài)和事務管理器存入對應的棧中
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
}
return true;
}
/**
* 提交處理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
// 循環(huán),直到事務管理器棧為空
while (!dataSourceTransactionManagerStack.isEmpty()) {
// 從事務管理器棧和事務狀態(tài)棧中分別彈出當前的事務管理器和事務狀態(tài)
// 提交當前事務狀態(tài)
dataSourceTransactionManagerStack.pop()
.commit(transactionStatuStack.pop());
}
}
/**
* 回滾處理方法
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
// 循環(huán),直到事務管理器棧為空
while (!dataSourceTransactionManagerStack.isEmpty()) {
// 從事務管理器棧和事務狀態(tài)棧中分別彈出當前的事務管理器和事務狀態(tài)
// 回滾當前事務狀態(tài)
dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
}
}
}
5. 使用事務注解,將兩個數(shù)據(jù)源的事務管理器名字作為參數(shù)傳入。
@MoreTransaction(value = {"transactionManagerOne","transactionManagerTwo"})
public ResultData getDataSourceList(){
//向第一個數(shù)據(jù)庫插入數(shù)據(jù)
int i=userService.addUser(new User().setUserName("數(shù)據(jù)庫1"));
//故意制造異常,拋出給事務切面
int a=1/0;
//向第二個是數(shù)據(jù)庫插入數(shù)據(jù)
int k=userService.addUserInfo(new UserInfo().setUserAccount("數(shù)據(jù)庫2"));
Map map=new HashMap();
map.put("k",k);
return ResultData.success(map);
}
6. 提交請求后會發(fā)現(xiàn)控制臺報錯,但是數(shù)據(jù)庫里面并沒有插入數(shù)據(jù)。
文章來源地址http://www.zghlxwxcb.cn/news/detail-790917.html
到了這里,關于解決多數(shù)據(jù)源的事務問題 - 基于springboot--mybatis的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!