JTA事务

作者: 米刀灵 | 来源:发表于2017-08-29 12:53 被阅读263次

    JDBC和JTA事务区别
    简单的说 jta是多库的事务 jdbc是单库的事务。

    jdbc事务
    JDBC事务由Connnection对象控制管理,也就是说,事务管理实际上是在JDBC Connection中实现。事务周期限于Connection的生命周期。java.sql.Connection提供了两种事务模式:自动提交和手工提交。

    自动提交:缺省是自动提交。一条对数据库的更新(增/删/改)代表一项事务操作,操作成功后,系统将自动调用commit()来提交,否则将调用rollback()来回滚。
    手工提交:通过调用setAutoCommit(false)来禁止自动提交。这样就可把多个数据库操作的表达式作为一个事务,在操作完成后调用commit()来进行整体提交,其中任何一个操作失败,都不会执行到commit(),并产生异常;此时可在异常捕获时调用rollback()进行回滚,以保持多次更新操作后,相关数据的一致性,示例如下:

        try {
            conn =DriverManager.getConnection(...);
            conn.setAutoCommit(false);//禁止自动提交,设置回滚点
            stmt = conn.createStatement();
            stmt.executeUpdate(...); //数据库更新操作1
            stmt.executeUpdate(...); //数据库更新操作2
            conn.commit(); //事务提交
        }catch(Exception ex) {
            log.error(...);
            try {
                conn.rollback(); //操作不成功则回滚
            }catch(Exception e) {
                log.error(...);
            }
        }
    

    JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个JDBC事务不能跨越多个数据库。也无法在通过RPC的方式调用中保证事务。

    jta事务
    由于JDBC无法实现分布式事务。例如操作不同的数据库或MQ(MQ也可以认为是一个数据源),这时候就无法使用JDBC来管理事务:

        /** 支付订单处理 **/
        @Transactional(rollbackFor = Exception.class)
        public void completeOrder() {
            orderDao.update(); // 订单服务本地更新订单状态
            accountService.update(); // 调用资金账户服务给资金帐户加款
            pointService.update(); // 调用积分服务给积分帐户增加积分
            accountingService.insert(); // 调用会计服务向会计系统写入会计原始凭证
            merchantNotifyService.notify(); // 调用商户通知服务向商户发送支付结果通知
        }
    

    其中调用了五个服务,这五个服务都通过RPC的方式调用。虽然方法中增加了@Transactional注解,但是由于采用调用了分布式服务,该事务并不能达到ACID的效果。
    JTA(Java Transaction API)提供了跨数据库连接(或其他JTA资源)的事务管理能力。JTA事务管理则由JTA容器实现。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。下列任一个Java平台的组件都可以参与到一个JTA事务中:JDBC连接、JDO PersistenceManager 对象、JMS 队列、JMS 主题、企业JavaBeans(EJB)等。

    Spring+Atomikos实现JTA:

    添加依赖:

      <!-- jta -->
      <dependency>
          <groupId>com.atomikos</groupId>
          <artifactId>transactions-jdbc</artifactId>
          <version>4.0.4</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.6.11</version>
      </dependency>
    

    配置文件applicationContext-jta.xml:

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:context="http://www.springframework.org/schema/context"
               xmlns:aop="http://www.springframework.org/schema/aop"
               xmlns:tx="http://www.springframework.org/schema/tx"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
            <bean id="dataSource_main" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
                <property name="uniqueResourceName" value="mysql/main" /><!-- 取名任意,但不要重复-- >
                <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
                <property name="xaProperties">
                    <props>
                        <prop key="user">yourusername</prop>
                        <prop key="password">yourpswd</prop>
                        <prop key="URL">jdbc:mysql://yourdatabasehost/tmg</prop>
                    </props>
                </property>
                <property name="minPoolSize" value="1" />
                <property name="maxPoolSize" value="3" />
                <property name="maxIdleTime" value="60" />
            </bean>
    
            <bean id="dataSource_other" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
                <property name="uniqueResourceName" value="mysql/other" /><!-- 取名任意,但不要重复-- >
                <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
                <property name="xaProperties">
                    <props>
                        <prop key="user">yourusername</prop>
                        <prop key="password">yourpswd</prop>
                        <prop key="URL">jdbc:mysql://yourdatabasehost/tmg</prop>
                    </props>
                </property>
                <property name="minPoolSize" value="1" />
                <property name="maxPoolSize" value="3" />
                <property name="maxIdleTime" value="60" />
            </bean>
    
    
            <!-- 配置mybitasSqlSessionFactoryBean -->
            <bean id="sqlSessionFactory_main" class="org.mybatis.spring.SqlSessionFactoryBean">
                <property name="dataSource" ref="dataSource_main" />
                <property name="configLocation" value="classpath:config/mybatis.xml"/>
                <property name="mapperLocations" value="classpath*:mybatis.mappers.main.*/*.xml" />
            </bean>
            <bean id="sqlSessionFactory_other" class="org.mybatis.spring.SqlSessionFactoryBean">
                <property name="dataSource" ref="dataSource_other" />
                <property name="configLocation" value="classpath:config/mybatis.xml"/>
                <property name="mapperLocations" value="classpath*:mybatis.mappers.other.*/*.xml" />
            </bean>
    
            <!-- 配置SqlSessionTemplate -->
            <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
                <property name="basePackage" value="com.tmg.dao.main" />
                <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_main" />
            </bean>
            <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
                <property name="basePackage" value="com.tmg.dao.other" />
                <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_other" />
            </bean>
    
            <!-- 下面三个Bean可以独立使用来进行事务控制 -->
            <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
                <property name="forceShutdown" value="true" />
            </bean>
    
            <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
                <property name="transactionTimeout" value="3000" />
            </bean>
    
            <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
                <property name="transactionManager" ref="atomikosTransactionManager"/>
                <property name="userTransaction" ref="atomikosUserTransaction"/>
            </bean>
    
            <aop:config proxy-target-class="true">
                <aop:advisor pointcut="execution(* *com.tmg.service..*(..))" advice-ref="txAdvice" />
            </aop:config>
    
            <tx:advice id="txAdvice" transaction-manager="transactionManager">
                <tx:attributes>
                    <tx:method name="insert*"  propagation="REQUIRED"  read-only="true" />
                    <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
                    <tx:method name="save*"  propagation="REQUIRED"  read-only="true" />
                    <tx:method name="delete*"  propagation="REQUIRED"  read-only="true" />
                    <tx:method name="del*"  propagation="REQUIRED"  read-only="true" />
                    <tx:method name="update*"  propagation="REQUIRED"  read-only="true" />
                </tx:attributes>
            </tx:advice>
    
        </beans>
    

    Dao层:

    Service层:

        @Override
        public void addAndDel(MybtUser user) {
            userMapper_main.insert(user);
            userMapper_other.deleteByPrimaryKey(user.getId());
        }
    

    测试:

        @Test
        public void testJta() throws Exception{
            MybtUser user = new MybtUser(5,3,"E");
            userService.addAndDel(user);
        }
    

    把MQ也加入JTA事务:

    <bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="messageListener" ref="jmsQueueReceiver" />
        <property name="destination" ref="queueDestination" />
        <property name="sessionTransacted" value="true"/>
        <property name="transactionManager" ref="transactionManager"/>
    </bean>
    

    这样消息监听器进行消息接收和对应的数据库访问就会处于同一数据库控制下,当消息接收失败或数据库访问失败都会进行事务回滚操作。当指定了transactionManager时,消息监听容器将忽略sessionTransacted的值。

    相关文章

      网友评论

          本文标题:JTA事务

          本文链接:https://www.haomeiwen.com/subject/srsodxtx.html