分布式事务系列(3.1)jotm的分布式案例

1 系列目录

2 与Spring集成方式使用jotm

工程代码地址:与Spring集成方式使用jotm

先来感受下一个分布式事务的案例(使用一般的数据库驱动,不需要支持分布式XA协议):

2.1 业务逻辑的操作

UserDao和LogDao,操作分别如下:

@Repository
public class UserDao {

    @Resource(name="jdbcTemplateA")
    private JdbcTemplate jdbcTemplate;

    public void save(User user){
        jdbcTemplate.update("insert into user(name,age) values(?,?)",user.getName(),user.getAge());
    }
}

@Repository
public class LogDao {

    @Resource(name="jdbcTemplateB")
    private JdbcTemplate jdbcTemplate;

    public void save(User user){
        jdbcTemplate.update("insert into log(name,age) values(?,?)",user.getName(),user.getAge());
    }
}

即上述两个JdbcTemplate使用不同的数据库。

UserService综合上述两个业务操作,使它们处于同一个事务中:

@Service
public class UserService {

    @Autowired
    private UserDao userDao;
    @Autowired
    private LogDao logDao;

    @Transactional
    public void save(User user){
        userDao.save(user);
        logDao.save(user);
        throw new RuntimeException();
    }
}

2.2 配置

上述业务代码我们看不到分布式事务的存在,这种正是我们想要的效果,分布式事务对业务透明。到底是如何来实现呢?

2.2.1 dataSource和JdbcTemplate配置

 <bean id="dataSourceA" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"  destroy-method="shutdown">
     <property name="dataSource">
        <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
            <property name="transactionManager" ref="jotm" />
            <property name="driverName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8" />
        </bean>
    </property>
    <property name="user" value="root" />
    <property name="password" value="xxxxx" />
</bean>

 <bean id="dataSourceB"   class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"  destroy-method="shutdown">
    <property name="dataSource">
        <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
            <property name="transactionManager" ref="jotm" />
            <property name="driverName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/test2?useUnicode=true&amp;characterEncoding=utf-8" />
        </bean>
    </property>
    <property name="user" value="root" />
    <property name="password" value="xxxx" />
</bean>

自行配置2个数据库地址

我们平常使用的dataSource,大部分是c3p0、dbcp等,这里就不能使用它们了,需要换成可以模拟XA协议的dataSource,即StandardXAPoolDataSource。这个连接池是xapool提供的。

顺便简单介绍下xapool官网地址:

  • 它设计了一个GenericPool,这个pool里面可以存放任何Object
  • 它提供了dataSource的实现,同时还提供了针对分布式的dataSource即StandardXAPoolDataSource,它可以通过使用普通的数据库驱动来模拟两阶段提交协议中XAResource的作用。本来XAResource是需要由数据库XA驱动来实现的。
  • 不过好久都没更新了,官网上最近一次更新还是06年

之后再详细介绍它的源码内容。

2.2.2 事务配置

我们知道分布式事务中需要一个事务管理器即接口javax.transaction.TransactionManager、面向开发人员的javax.transaction.UserTransaction。对于jotm来说,他们的实现类都是Current,如下源码所示:

public class Current implements UserTransaction, TransactionManager

我们如果想使用分布式事务的同时,又想使用Spring带给我们的@Transactional便利,就需要配置一个JtaTransactionManager,而该JtaTransactionManager是需要一个userTransaction实例的,所以用到了上面的Current,如下配置:

<bean id="jotm" class="org.objectweb.jotm.Current" />

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="userTransaction" ref="jotm" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

同时上述StandardXADataSource是需要一个TransactionManager实例的,所以上述StandardXADataSource配置把jotm加了进去

2.2.3 jar包依赖

整个工程主要是利用jotm和xapool来实现分布式事务。jotm提供事务管理器javax.transaction.TransactionManager和面向开发人员的功能接口javax.transaction.UserTransaction,而xapool则是对非XA数据库驱动进行包装,来模拟XA数据库驱动干的事。所以依赖的pom如下:

<!-- jotm -->
<dependency>
    <groupId>org.ow2.jotm</groupId>
    <artifactId>jotm-core</artifactId>
    <version>2.3.1-M1</version>
</dependency>
<dependency>
    <groupId>org.ow2.jotm</groupId>
    <artifactId>jotm-datasource</artifactId>
    <version>2.3.1-M1</version>
</dependency>

<!-- xapool -->
<dependency>
    <groupId>com.experlog</groupId>
    <artifactId>xapool</artifactId>
    <version>1.5.0</version>
</dependency>

上述jotm-datasource主要是为了将上述StandardXAPoolDataSource数据源放置到容器中,如tomcat,而不是应用程序中。应用程序通过JNDI的方式从tomcat容器中获取上述数据源。所以对于本工程来说可以不要,对于下文说的通过JNDI方式使用jotm则是必须的

2.3 分布式事务执行的大致过程

下面先粗略的说明下分布式事务的大致执行过程,即下面的执行过程:

@Transactional
public void save(User user){
    userDao.save(user);
    logDao.save(user);
    throw new RuntimeException();
}
  • 第一步:事务拦截器开启事务

    我们知道加入了@Transactional注解,同时开启tx:annotation-driven,会对本对象进行代理,加入事务拦截器。在事务拦截器中,获取javax.transaction.UserTransaction,这里即org.objectweb.jotm.Current,然后使用它开启事务,并和当前线程进行绑定,绑定关系数据存放在org.objectweb.jotm.Current中

  • 第二步:使用jdbcTemplate进行业务操作

    jdbcTemplateA要从dataSourceA中获取Connection,和当前线程进行绑定,同时以对应的dataSourceA作为key。同时判断当前线程是否含有事务,通过dataSourceA中的org.objectweb.jotm.Current发现当前线程有事务,则把Connection自动提交设置为false,同时将该连接纳入当前事务中。

    jdbcTemplateB要从dataSourceB中获取Connection,和当前线程进行绑定,同时以对应的dataSourceB作为key。同时判断当前线程是否含有事务,通过dataSourceB中的org.objectweb.jotm.Current发现当前线程有事务,则把Connection自动提交设置为false,同时将该连接纳入当前事务中。

  • 第三步:

    一旦抛出异常,则需要进行事务的回滚操作。回滚就是将当前事务进行回滚,该事务的回滚会调用和它关联的所有Connection的回滚。

这里再举一个简单的例子,如下:

Connection connA=dataSourceA.getConnection();
Connection connB=dataSourceB.getConnection();

Statement statementA=connA.createStatement();
Statement statementB=connB.createStatement();

String sql="insert into user(name,age) values('"+user.getName()+"',"+user.getAge()+")";

try {
    connA.setAutoCommit(false);
    connB.setAutoCommit(false);

    statementA.execute(sql);
    statementB.execute(sql);

    //throw new RuntimeException();

    connA.commit();
    connB.commit();
} catch (Exception e) {
    e.printStackTrace();
    statementA.close();
    statementB.close();
    connA.rollback();
    connB.rollback();
}finally{
    connA.close();
    connB.close();
}

我们这样做:把所有的Connection的自动提交都设置为false,一旦执行过程中发生异常,调用每个Connection的回滚方法,如果没异常,则全部提交。这样做也可以实现分布式事务操作。

jotm也是同样的思路,在上述工程中,使用jdbcTemplate操作,就会把使用的Connection的自动提交设置为false,同时把这个Connection交给事务管理,一旦抛出异常,事务就会把它拥有的所有Connection全部回滚。

3 通过JNDI方式使用jotm

工程代码地址:通过JNDI方式使用jotm

再介绍下通过JNDI方式如何来使用jotm,以及碰到的最新版jotm-core中的一个bug。

3.1 操作代码

public void save(User user){
    UserTransaction userTransaction=null;
    try {
        Context ctx = new InitialContext();
        DataSource dataSourceA = (DataSource) ctx.lookup("java:comp/env/jdbc/dataSourceA");
        DataSource dataSourceB = (DataSource) ctx.lookup("java:comp/env/jdbc/dataSourceB");
        userTransaction = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
        userTransaction.begin();

        Connection connA=dataSourceA.getConnection();
        Connection connB=dataSourceB.getConnection();

        Statement statementA=connA.createStatement();
        Statement statementB=connB.createStatement();

        String sqlA="insert into user(name,age) values('"+user.getName()+"',"+user.getAge()+")";
        String sqlB="insert into log(name,age) values('"+user.getName()+"',"+user.getAge()+")";

        statementA.execute(sqlA);
        statementB.execute(sqlB);

        userTransaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        if(userTransaction!=null){
            try {
                userTransaction.rollback();
            } catch (IllegalStateException | SecurityException
                    | SystemException e1) {
                e1.printStackTrace();
            }
        }
    }
}
  • 第一步:先通过JNDI方式获取面向开发人员的UserTransaction事务
  • 第二步:通过JNDI方式获取dataSource,然后进行sql操作
  • 第三步:使用UserTransaction提交事务
  • 第四步:一旦执行过程中发生异常,使用UserTransaction回滚事务

3.2 tomcat的JNDI配置

在tomcat的context.xml配置文件中如下方式配置:

3.2.1 配置UserTransaction

配置如下:

<Transaction factory="org.objectweb.jotm.UserTransactionFactory"/>

这个配置默认将"java:comp/UserTransaction"和上述UserTransactionFactory产生的对象关联了起来(还不太了解JNDI的话,需要去补充下知识)。所以可以通过如下方式来获取:

userTransaction = (UserTransaction) ctx.lookup("java:comp/UserTransaction")

我们来看下,jotm提供的UserTransaction实现是什么对象,即该UserTransactionFactory产生的对象是?

可以看到提供的UserTransaction实现是org.objectweb.jotm.Current。

3.2.2 配置两个dataSource

配置如下:

    <Resource name="jdbc/dataSourceA"
        auth="Container"
        type="javax.sql.DataSource"
        factory="org.objectweb.jotm.datasource.DataSourceFactory"
        username="root"
        password="ligang"
        driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8"/> 

     <Resource name="jdbc/dataSourceB"
        auth="Container"
        type="javax.sql.DataSource"
        factory="org.objectweb.jotm.datasource.DataSourceFactory"
        username="root"
        password="ligang"
        driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/test2?useUnicode=true&amp;characterEncoding=utf-8"/>

我们来仔细研究下,它到底用的是什么dataSource,来看下上述配置的factory即org.objectweb.jotm.datasource.DataSourceFactory的内容:

可以看到这里和spring中的配置文件里基本差不多,多和上面的spring配置文件对比对比。

  • 创建StandardXADataSource,设置相关参数
  • 创建StandardXAPoolDataSource,设置相关参数

继续,下面还有:

将StandardXADataSource设置进StandardXAPoolDataSource中。 同时StandardXAPoolDataSource需要设置下事务管理器TransactionManager,通过jotm对象来获取的。

3.2.3 最新版本的一个bug

上述事务管理器是从jotm对象获取的,我们继续看下jotm是如何来的?这里正是jotm-core-2.3.1-M1.jar出现bug的地方:

即在加载DataSourceFactory类的时候,就会创建Jotm,来详细看下2.3.1-M1版本的创建方法:

public Jotm(boolean local, boolean bound) throws NamingException {
    this(local, bound, null);
}

可以看到,这里的第三个参数为null,继续看下第三个参数是干什么的?

我们可以看到第三个参数为null,会产生运行时异常即空指针异常,没有捕获到继续向上层传递,而DataSourceFactory也没有捕获到,直接导致DataSourceFactory类加载失败。

解决办法就是换成低版本的jotm-core,如2.2.2版本就可以了,不会产生上述问题。或者直接调用三个参数的构造函数,对于第三个参数给出一个空的实现

4 结束语

本篇主要说明了与spring集成方式的jotm案例、使用jndi方式的jotm案例。下一篇就该详细介绍下整个过程的执行原理。提出的问题如下:

  • jotm做了哪方面的工作?
  • xapool做了哪方面的工作?
  • 2pc的过程怎么体现的?
时间: 2024-09-20 05:46:53

分布式事务系列(3.1)jotm的分布式案例的相关文章

分布式事务系列(2.1)分布式事务的概念

1 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 2 X/Open DTP DTP全称是Distributed Transaction P

分布式事务系列(3.2)jotm分布式事务源码分析

1 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 2 了解xapool 我们在前一篇文章中了解到jotm配合xapool共同完成了分布式事

分布式事务系列(1.2)Spring的事务体系

1 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 2 三种事务模型 三种事务模型如下: 本地事务模型 编程式事务模型 声明式事务模型 先来

分布式事务系列(4.1)Atomikos的分布式案例

1 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 2 Atomikos使用非XA数据库驱动实现分布式事务 项目地址见:Atomikos使用

分布式事务系列(开篇)提出疑问和研究过程

1 前言 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 对于我们这种初学者,可能会使用spring带给我们的@Transactional,

分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager

1 系列目录 分布式事务系列(开篇)提出疑问和研究过程 分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析 分布式事务系列(1.2)Spring事务体系 分布式事务系列(2.1)分布式事务模型与接口定义 分布式事务系列(3.1)jotm的分布式案例 分布式事务系列(3.2)jotm分布式事务源码分析 分布式事务系列(4.1)Atomikos的分布式案例 2 jdbc事务 2.1 例子 public void save(User user)

WCF分布式开发步步为赢(12):WCF事务机制(Transaction)和分布式事务编程

今天我们继续学习WCF分布式开发步步为赢系列的12节:WCF事务机制(Transaction)和分布式事务编程.众所周知,应用系统开发过程中,事务是一个重要的概念.它是保证数据与服务可靠性的重要机制.作为面向服务应用的开发平台,WCF也提供了对事物编程模型的支持..NET 2.0提供的System.Transactions类来开发事务应用程序.同样WCF也支持事务特性,WCF事务机制是什么,它与微软已有的技术如Microsoft 分布式事务协调器 (MSDTC)有何关系?与Enterpise S

关于分布式事务

一.普通事务与分布式事务 1.1 普通事务 普通事务就是一般所说的数据库事务,大家对数据库事务应该都很了解,这里再简单介绍下. 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成.当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的

谈谈分布式事务之四: 两种事务处理协议OleTx与WS-AT

在年前写一个几篇关于分布式事务的文章,实际上这些都是为了系统介绍WCF事务处理体系而提供的相关的背景和基础知识.今天发最后一篇,介绍分布式事务采用的两种协议,即OleTx和WS-AT,内容比较枯燥,但对于后续对WCF事务处理框架进行深入剖析的系列文章来说,确是不可以缺少的.总的来说,基于WCF的分布式事务采用的是两阶段提交(2PC:Two Phase Commit)协议.具体来说,我们可以选择如下两种事务处理协议实现WCF的分布式式事务,它们按照各自的方式提供了对两阶段提交的实现. OleTx: