1. 程式人生 > >ssm整合jta分散式事務那點事-.-

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的驅動,完美解決;

 

網上的教程很多.只有自己試過才知道~