JTA事务

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的值。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容