ssm整合jta分布式事务那点事-.-

论坛 期权论坛 脚本     
匿名网站用户   2020-12-21 09:34   11   0

之前一直自己写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的驱动,完美解决;

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

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP