ssm整合jta分散式事務那點事-.-
之前一直自己寫demo,然後用的是springboot整合jta,也沒遇到啥問題,而且網上教程很多;這次在實際的一個專案中需要增加一個數據庫連線,所以需要分散式事務了,結果一直報錯-.-最終解決;
一.最開始沒打算用到分散式事務的,就是動態的切換下資料來源就行了:
1.準備配置類:
public enum MyDataSource {
DEFAULT, INDUSTRY
}
這裡使用到了ThreadLocal,他的目的是在每次請求的執行緒中,做到獨立執行緒中的資料共享;
package com.zc.www.config; import com.zc.www.model.datasource.MyDataSource; /** * @Auther: gaoyang * @Date: 2018/11/20 10:43 * @Description:資料來源 */ public class MyDataSourceHolder { private final static ThreadLocal<MyDataSource> my = new ThreadLocal<>(); public static void set(MyDataSource myDataSource) { my.set(myDataSource); } public static MyDataSource get() { return my.get(); } public static void clear() { my.remove(); } }
然後繼承動態資料來源,後面資料來源就用這個:
package com.zc.www.config; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @Auther: gaoyang * @Date: 2018/11/20 10:58 * @Description:動態資料來源配置 */ public class MyDynamic extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return MyDataSourceHolder.get(); } }
2.配置資料來源資訊:
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close" autowire="no"> <property name="fairQueue" value="false" /> <property name="minIdle" value="10" /> <property name="maxIdle" value="20" /> <property name="maxActive" value="100" /> <property name="initialSize" value="10" /> <property name="testOnBorrow" value="true" /> <property name="validationQuery" value="select version()" /> <property name="validationInterval" value="30000" /> <property name="removeAbandoned" value="true" /> <property name="removeAbandonedTimeout" value="180" /> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="dataSource2" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close" autowire="no"> <property name="fairQueue" value="false" /> <property name="minIdle" value="10" /> <property name="maxIdle" value="20" /> <property name="maxActive" value="100" /> <property name="initialSize" value="10" /> <property name="testOnBorrow" value="true" /> <property name="validationQuery" value="select 1" /> <property name="validationInterval" value="30000" /> <property name="removeAbandoned" value="true" /> <property name="removeAbandonedTimeout" value="180" /> <property name="driverClassName" value="${jdbc.mysql.driver}" /> <property name="url" value="${jdbc.mysql.url}" /> <property name="username" value="${jdbc.mysql.username}" /> <property name="password" value="${jdbc.mysql.password}" /> </bean>
<bean class="com.zc.www.config.MyDynamic" id="MyDynamic">
<property name="defaultTargetDataSource" ref="dataSource"/>
<property name="targetDataSources">
<map key-type="com.zc.www.model.datasource.MyDataSource">
<entry key="DEFAULT" value-ref="dataSource"></entry>
<entry key="INDUSTRY" value-ref="dataSource2"></entry>
</map>
</property>
</bean>
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation"
value="classpath:mybatis-configuration.xml" />
<property name="dataSource" ref="MyDynamic" />
<property name="plugins">
<array>
<!-- 分頁外掛配置 -->
<bean id="paginationInterceptor"
class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="postgresql" />
</bean>
</array>
</property>
<property name="globalConfig" ref="globalConfig"></property>
</bean>
<!-- 自動掃描注入類 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zc.www.mapper" />
<property name="sqlSessionFactoryBeanName"
value="sqlSessionFactory" />
</bean>
上面我用的是mybaitis-plus;
下面是切面的事務控制:
<!-- 事務相關控制 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="MyDynamic" />
</bean>
<tx:annotation-driven
transaction-manager="transactionManager" />
<tx:advice id="txAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<!-- 對業務層所有方法新增事務,除了以get、find、select開始的 -->
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- 查詢操作沒有必要開啟事務,給只讀事務新增一個屬性read-only -->
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="select*" read-only="true" />
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
3.使用aop動態的切換資料來源:
配置自定義註解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface DynamicSource {
MyDataSource value() default MyDataSource.INDUSTRY;
}
@Pointcut(value = "execution(* com.zc.www.mapper.*.*(..))")
public void datasource() {
}
@Before(value = "datasource()")
public void dynamicsource(JoinPoint j) {
Class declaringType = j.getSignature().getDeclaringType();
DynamicSource annotation = (DynamicSource) declaringType.getAnnotation(DynamicSource.class);
if (annotation != null) {
MyDataSourceHolder.set(annotation.value());
MyDataSource myDataSource = MyDataSourceHolder.get();
System.out.println(myDataSource);
} else {
MethodSignature signature = (MethodSignature) j.getSignature();
Method method = signature.getMethod();
if (method != null) {
DynamicSource annotation2 = method.getAnnotation(DynamicSource.class);
if (annotation2 != null) {
MyDataSourceHolder.set(annotation2.value());
}
}else{
MyDataSourceHolder.set(MyDataSource.DEFAULT);
}
}
}
@After(value = "datasource()")
public void dynamicAfter(){
MyDataSourceHolder.clear();
}
以上就實現了動態的切換資料來源的功能;當然也可以使用多個sqlsession的方式,然後掃描不同的mapper包下介面的方式做多資料來源;
以上方式的缺點就是如果你用的切面事務,或者使用註解開啟事務的話,如果在一個方法中你操作了多個數據庫的話,就會報找不到表的錯誤.因為事務是不可以在開啟事務後進行切換資料來源的;當然如果用不同sqlsession的方式也不可以,因為你沒用到xa方式的提交事務方式,同樣不支援;下面我們來看看怎麼配置jta;
二.配置ssm整合jta分散式事務;
1.配置資訊:
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>3.9.3</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
只需要以上兩個包依賴;
<bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
<description>mysql xa datasource</description>
<property name="uniqueResourceName">
<value>mysqlDataSource</value>
</property>
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="user">${jdbc.mysql.username}</prop>
<prop key="password">${jdbc.mysql.password}</prop>
<prop key="URL">${jdbc.mysql.url}</prop>
</props>
</property>
<property name="poolSize" value="20"/>
</bean>
<bean id="postDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
<description>post xa datasource</description>
<property name="uniqueResourceName">
<value>postDataSource</value>
</property>
<property name="xaDataSourceClassName" value="org.postgresql.xa.PGXADataSource" />
<property name="xaProperties">
<props>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="URL">${jdbc.url}</prop>
</props>
</property>
<property name="poolSize" value="20"/>
</bean>
<!-- atomikos事務管理器 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
init-method="init" destroy-method="close">
<description>UserTransactionManager</description>
<property name="forceShutdown">
<value>true</value>
</property>
</bean>
<!-- atomikos使用者事務實現 -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<!-- spring 事務管理器 -->
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<!--注入 atomikos事務管理器 -->
<property name="transactionManager">
<ref bean="atomikosTransactionManager" />
</property>
<!--注入 atomikos使用者事務實現 -->
<property name="userTransaction">
<ref bean="atomikosUserTransaction" />
</property>
</bean>
<!-- spring事務模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref bean="springTransactionManager" />
</property>
</bean>
<bean id="mysql" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-configuration.xml"></property>
<property name="dataSource" ref="mysqlDataSource"></property>
</bean>
<bean id="post" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-configuration.xml"></property>
<property name="dataSource" ref="postDataSource"></property>
</bean>
<!-- 自動掃描注入類 -->
<bean id="d1" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zc.www.mapper2" />
<property name="sqlSessionFactoryBeanName"
value="mysql" />
</bean>
<bean id="d2" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zc.www.mapper" />
<property name="sqlSessionFactoryBeanName"
value="post" />
</bean>
這裡我就沒做動態切換了.將兩個庫不同的mapper介面放到了不同的包裡;
<tx:annotation-driven
transaction-manager="springTransactionManager" />
<tx:advice id="txAdvice"
transaction-manager="springTransactionManager">
<tx:attributes>
<!-- 對業務層所有方法新增事務,除了以get、find、select開始的 -->
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- 查詢操作沒有必要開啟事務,給只讀事務新增一個屬性read-only -->
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="select*" read-only="true" />
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* com.zc.www.service.**.*.*(..))" />
<aop:advisor pointcut-ref="pointcut" advice-ref="txAdvice" />
</aop:config>
以上同樣是切換控制事務;
注意:
我其實主要出問題的地方在這裡:
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
org.postgresql.xa.PGXADataSource
這兩個分別是mysql和postgresql的xa驅動,然後我直接就根據網上的貼上過來了,殊不知驅動要跟資料庫匹配;可能這裡我犯傻的比較低階吧.
向之前我使用的mysql驅動,包下根本就沒有該驅動,可能是新版本換了其他的全限制類名?
postgresql的驅動有是有,不過報資料來源中url找不到的錯了,然後試過才知道,也是驅動版本問題;我的資料庫使用的是postgresql10版本,而我驅動使用的是9.x,不過之前是照常用的,這次使用xa驅動就報錯了.然後換成了最新的4x.x的驅動,完美解決;
網上的教程很多.只有自己試過才知道~