背景
框架之前完成了多資料源的動態切換及事務的處理,想更近一步提供一個簡單的跨庫事務處理功能,經過網上的搜索調研,大致有XA事務/SEGA事務/TCC事務等方案,因為業務主要涉及政府及企業且并發量不大,所以采用XA事務,雖然性能有所損失,但是可以保證資料的強一致性
方案設計
針對注冊的資料源拷貝一份用于XA事務,使得本地事務和XA全域事務相互獨立可選擇的使用

Maven配置
引入atomikos第三方組件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
注冊XA資料源
使用Druid連接池,需要使用DruidXADataSource資料源物件,再使用AtomikosDataSourceBean進行包裝
注冊資料源時針對同一個連接注冊兩份,一份正常資料源,一份用于XA事務的資料源,資料源標識區分并關聯
因為spring默認注冊了XA事務管理器后,所有事務操作不再走本地事務,我們通過切換不同的資料源決定走本地事務還是XA事務
//主資料源xa模式
@Bean
@Qualifier("masterXADataSource")
public DataSource masterXADataSource() {
DruidXADataSource datasource = new DruidXADataSource();
if(driverClassName.equals("com.mysql.cj.jdbc.Driver")){
if(!dbUrl.contains("useOldAliasMetadataBehavior")){
dbUrl += "&useOldAliasMetadataBehavior=true";
}
if(!dbUrl.contains("useAffectedRows")){
dbUrl += "&useAffectedRows=true";
}
}
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
//configuration
datasource.setInitialSize(1);
datasource.setMinIdle(3);
datasource.setMaxActive(20);
datasource.setMaxWait(60000);
datasource.setTimeBetweenEvictionRunsMillis(60000);
datasource.setMinEvictableIdleTimeMillis(60000);
datasource.setValidationQuery("select 'x'");
datasource.setTestWhileIdle(true);
datasource.setTestOnBorrow(false);
datasource.setTestOnReturn(false);
datasource.setPoolPreparedStatements(true);
datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
datasource.setLogAbandoned(false); //移除泄露連接發生是是否記錄日志
try {
datasource.setFilters("stat,slf4j");
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//connectionProperties);
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
atomikosDataSourceBean.setUniqueResourceName("master-xa");
atomikosDataSourceBean.setXaDataSource(datasource);
atomikosDataSourceBean.setPoolSize(5);
atomikosDataSourceBean.setMaxPoolSize(20);
atomikosDataSourceBean.setTestQuery("select 1");
return atomikosDataSourceBean;
}
注冊XA事務管理器
使用spring內置的JtaTransactionManager事務管理器物件,設定AllowCustomIsolationLevels為true,否則指定自定義事務隔離級別會報錯
//xa模式全域事務管理器
@Bean(name = "jtaTransactionManager")
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, userTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
定義XA事務切面
自定義注解@GlobalTransactional并定義對應切面,使用指定注解時在ThreadLocal變數值進行標識,組合
@Transactional注解指定XA事務管理器,在切換資料原時判斷當前是否在XA事物中,從而切換不同的資料源
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_UNCOMMITTED,transactionManager = "jtaTransactionManager")
public @interface GlobalTransactional {
}
@Aspect
@Component
@Order(value = https://www.cnblogs.com/yanpeng19940119/p/99)
public class GlobalTransitionAspect {
private static Logger logger = LoggerFactory.getLogger(GlobalTransitionAspect.class);
@Autowired
private DynamicDataSource dynamicDataSource;
/**
* 切面點 指定注解
*/
@Pointcut("@annotation(com.code2roc.fastkernel.datasource.GlobalTransactional) " +
"|| @within(com.code2roc.fastkernel.datasource.GlobalTransactional)")
public void dataSourcePointCut() {
}
/**
* 攔截方法指定為 dataSourcePointCut
*/
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
GlobalTransactional methodAnnotation = method.getAnnotation(GlobalTransactional.class);
if (methodAnnotation != null) {
DataSourceContextHolder.tagGlobal();
logger.info("標記全域事務");
}
try {
return point.proceed();
} finally {
logger.info("清除全域事務");
DataSourceContextHolder.clearGlobal();
}
}
}
public class DataSourceContextHolder {
private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
// 對當前執行緒的操作-執行緒安全的
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
private static final ThreadLocal<String> contextGlobalHolder = new ThreadLocal<String>();
// 呼叫此方法,切換資料源
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
log.debug("已切換到資料源:{}", dataSource);
}
// 獲取資料源
public static String getDataSource() {
String value = https://www.cnblogs.com/yanpeng19940119/p/contextHolder.get();
if (StringUtil.isEmpty(value)) {
value ="master";
}
if (!StringUtil.isEmpty(getGlobal())) {
value = https://www.cnblogs.com/yanpeng19940119/p/value +"-xa";
}
return value;
}
// 洗掉資料源
public static void clearDataSource() {
contextHolder.remove();
log.debug("已切換到主資料源");
}
//====================全域事務標記================
public static void tagGlobal() {
contextGlobalHolder.set("1");
}
public static String getGlobal() {
String value = https://www.cnblogs.com/yanpeng19940119/p/contextGlobalHolder.get();
return value;
}
public static void clearGlobal() {
contextGlobalHolder.remove();
}
//===================================================
}
配置XA事務日志
通過在resource檔案夾下創建transactions.properties檔案可以指定XA事務日志的存盤路徑
com.atomikos.icatch.log_base_dir= tempfiles/transition/
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/484518.html
標籤:Java
上一篇:第一次的ssm整合
