背景
springboot項目中用到了多數據源,但是框架又沒有采用分佈式的,導致不能使用市場上的分佈式事務解決方案來解決多數據源的事務問題,但是現在問題要得到解決,沒辦法,網上找了很多資料,將最終的解決方案分享給大家,實際使用的話比較繁瑣,有更好的解決方案,歡迎分享。
常見問題
1. 爲什麼在一個事務管理器裏面不能做到切換數據源(切數據源失敗)
事務衆所周知,有四大特性ACID,但是大部分人都忘了一個前置條件,就是這裏的事務是存在一個單體數據庫裏面,也就是這裏的事務只能在A數據庫裏面存在,或者B數據庫裏面存在,也就是事務是一個數據庫裏面單次操作集的單位,而不是單次操作集就是這裏的事務,畫一個圖更好理解,如下:
所以,在 @Transation 註解的類或方法中切數據源失敗的根本原因就是spring已經做了切庫操作,並且在@transation註解的加持下已經獲取到了第一個切庫操作的事務管理器,在第一個數據庫的事務管理器內是做不到切到第二個數據庫獲取第二個數據庫的事務管理器的,所以會失敗,看上面的圖就很明白了,所以如果你把@transation註解加到controller層裏面,切庫也會失敗。
示例代碼
/**
* 基本思路就是spring切庫後會把對應的會話等信息也做對應的調整,所以從IOC容器中sqlSessiionFactory獲取切庫後的session
* 並保存到對象中,就可以同時存在多個數據庫的事務,這裏再次說明了單例下的sqlSessionFactory不能同時存兩個數據庫的會話信息,
* 然後通過手動控制事務的形式,做到多數據庫的強事務。
*/
@Autowired
private SqlSessionFactory sqlSessionFactory;
@RequestMapping("/tsetTransaction")
@ResponseBody
public AjaxMessageEntity testTransaction(){
AjaxMessageEntity ajaxMessageEntity = new AjaxMessageEntity();
SqlSession sqlSession = null;
SqlSession sqlSession2 = null;
Connection connection = null;
Connection connection2 = null;
Savepoint savepoint = null;
Savepoint savepoint2 = null;
try {
//切換到默認庫
switchDB.change(ControllerCode.DB_DEFAULT);
//獲取到默認庫不自動提交的會話
sqlSession = sqlSessionFactory.openSession(false);
//獲取會話連接
connection = sqlSession.getConnection();
//設置會話連接爲非自動提交
connection.setAutoCommit(false);
//設置會話連接的回滾點
savepoint = connection.setSavepoint();
ChargesRecord chargesRecord = new ChargesRecord();
chargesRecord.setId("100000000000000032410000015554917611541304205");
chargesRecord.setUpdatedate(DateUtil.getCurDateTime());
//默認數據庫的數據庫操作一
sqlSession.update("com.iss.base.operation.charges.dao.mapper.ChargesRecordMapper.updateByPrimaryKey",chargesRecord);
chargesRecord.setUpdatedate(null);
chargesRecord.setInputdate(DateUtil.getCurDateTime());
//默認數據庫的數據庫操作二
sqlSession.update("com.iss.base.operation.charges.dao.mapper.ChargesRecordMapper.updateByPrimaryKey",chargesRecord);
//切到當前登錄用戶所在的數據庫
switchDB.change(LoginUserContext.getLoginUserInfo().getDbKye());
//同上
sqlSession2 = sqlSessionFactory.openSession(false);
connection2 = sqlSession2.getConnection();
connection2.setAutoCommit(false);
savepoint2 = connection2.setSavepoint();
SysMenuBO sysMenuBO = new SysMenuBO();
sysMenuBO.setId("100000000000000430910000015476185521051640339");
sysMenuBO.setInputDate(DateUtil.getCurDateTime());
sqlSession2.update("com.iss.base.system.dao.mapper.SysMenuMapper.updateByPrimaryKey",sysMenuBO);
if(true){
//測試異常
throw new RuntimeException();
}
//提交會話連接一操作
connection.commit();
//提交會話連接一操作
connection2.commit();
} catch (Exception e) {
e.printStackTrace();
//異常手動回滾
if(connection != null){
try {
connection.rollback(savepoint);
} catch (SQLException e1) {
e1.printStackTrace();
}
}
if(connection2 != null){
try {
connection2.rollback(savepoint2);
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}finally {
//關閉會話(很重要)
if(sqlSession != null){
sqlSession.close();
}
if(sqlSession2 != null){
sqlSession2.close();
}
}
return ajaxMessageEntity;
}
由於單數據庫的事務比較容易,可以用代理模式做封裝,這裏多數據源的事務操作嵌入了業務代碼比較深,目前沒有想到什麼比較好的形式做進一步的封裝,實際應用比較繁瑣,如有好的封裝方法,請指正,謝謝!