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包下接口的方式做多数据源;

扫描二维码关注公众号,回复: 4416310 查看本文章

以上方式的缺点就是如果你用的切面事务,或者使用注解开启事务的话,如果在一个方法中你操作了多个数据库的话,就会报找不到表的错误.因为事务是不可以在开启事务后进行切换数据源的;当然如果用不同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的驱动,完美解决;

网上的教程很多.只有自己试过才知道~

猜你喜欢

转载自blog.csdn.net/myth_g/article/details/84337734
今日推荐