程序员人生 网站导航

Java分布式事务原理及应用

栏目:php教程时间:2017-03-31 09:39:11

学习Java的同学注意了!!! 
学习进程中遇到甚么问题或想获得学习资源的话,欢迎加入Java学习交换群,群号码:183993990  我们1起学Java!

引言

  JTA(Java Transaction API)允许利用程序履行散布式事务处理--在两个或多个网络计算机资源上访问并且更新数据。JDBC驱动程序的JTA支持极大地增强了数据访问能力。

  本文的目的是要提供1个关于的Java事务处理API(JTA)的高级的概述,和与散布式事务相干的内容。1个事务处理定义了1个工作逻辑单元,要末完全成功要末不产生任何结果。 1个散布式事务处理只是1个在两个或更多网络资源上访问和更新数据的事务处理,因此它在那些资源之间必定是等价的。在本文中,我们主要关心的是如何处理关系数据库系统。

  我们要讨论的散布式事务处理(DTP)模型中包括的组件是:

    利用程序

    利用程序服务器

    事务管理程序

    资源适配器

    资源管理程序

  在以后的内容中,我们将描写这些组件和它们与JTA和数据库访问的关系。

  访问数据库

  最好把散布式事务处理中包括的组件看做是独立的进程,而不是斟酌它们在1个特定的电脑中的位置。这些组件中的1些可以保存在单机中,或也可在好几台机器之间散布。 下面例子中的图表可以显示在1台特定的电脑上的组件,但是这些操作之间的关系是必须重要斟酌的。

  最简单的例子:用于本地数据库事务处理的利用程序

  关系数据库访问的最简单的情势仅仅包括利用程序、资源管理程序和资源适配器。利用程序只不过是发送要求到数据库并且从数据库中获得数据的终究用户访问点

  我们讨论的资源管理程序是1个关系数据库管理系统(RDBMS),比如Oracle或SQL Server。所有的实际数据库管理都是由这个组件处理的。

  资源适配器是外部空间之间的通讯管道组件,或是要求翻译器,在本例中,是利用程序和资源管理程序。在我们的讨论中,这是1个JDBC驱动程序。

  下面的描写是资源管理程序本地事务处理的1个描写,也就是说,1个事务处理被被限制在1个特定的企业数据库

  利用程序发送1个用于JDBC驱动程序数据的要求,然后翻译这个要求并把它通过网络发送到数据库中。 数据库把数据发送回驱动程序,然后把翻译的结果发送回利用程序,以下图所示:

 

 

 


 

这个例子说明了在1个简化的系统中的基本的信息流;但是,今天的企业使用的利用程序服务器都添加了其他的组件到这个进程处理中。

 

  利用程序服务器

  利用程序服务器是事务处理操作的另外一个组件。利用程序服务器处理大部份的利用程序操作并且取得终究用户利用程序的1些负载。基于前面的例子,我们可以看出利用程序服务器在事务处理上添加了另外一个操作层:


 


  

到目前为止,我们的例子说明了单个的本地事务处理,并且描写了散布式事务处理模型的5个组件中的4个。第5个组件,事务管理程序只有当事务将要被分配的时候才会开始被斟酌。

  散布式事务处理和事务管理程序

  像我们前面所提到的,1个散布式事务处理是1个在两个或更多网络资源上访问和更新数据的事务处理。

  这些资源可以由好几个位于1个单独服务器上的不同的关系型数据库管理系统组成,比如说Oracle、SQL Server和Sybase;它们也能够包括存在于若干不同的服务器上的同1种数据库的若干个实例。在任何情况下,1个散布式事务处理包括各种的资源管理程序之间的协同作用。这个协同作用是事务管理函数。

  事务管理程序负责作出要末提交(commit)要末退回(rollback)任何散布式事务处理的决定。1个提交决定应当致使1个成功的事务处理;而退回操作则是保持数据库中的数据不变。 JTA指定1个散布式事务处理中的事务管理程序和另外一个组件之间的标准Java接口:利用程序,利用程序服务器和资源管理程序。 这个关系被显示在下面的图表中:


 



  在事务管理程序周围的数字框框相应于JTA的3个接口部份:

  1—UserTransaction—javax.transaction.UserTransaction接口提供能够编程地控制事务处理范围的利用程序。 javax.transaction.UserTransaction方法开启1个全局事务并且使用调用线程与事务处理关联。

  2—Transaction Manager—javax.transaction.TransactionManager接口允许利用程序服务器来控制代表正在管理的利用程序的事务范围。

  3—XAResource—javax.transaction.xa.XAResource接口是1个基于X/Open CAE Specification的行业标准XA接口的Java映照。

  注意,1个限制性环节是通过JDBC驱动程序的XAResource接口的支持。JDBC驱动程序必须支持两个正常的JDBC交互作用:利用程序和/或利用程序服务器,而且和JTA的XAResource部份。

  编写利用程序水平代码的开发者不会关心散布式事务处理管理的细节。 这是散布式事务处理基本结构的工作—利用程序服务器、事务管理程序和JDBC驱动程序。利用程序代码中唯1的需要注意的就是当连接处于1个散布式事务范围内的时候,不应当调用1个会影响事务边界的方法。特别的是,1个利用程序不应当调用Connection方法commit、rollback和setAutoCommit(true),由于它们将破坏散布式事务的基本结构管理。

  散布式事务处理

  事务管理程序是散布式事务基本结构的基本组件;但是JDBC驱动程序和利用程序服务器组件应当具有下面的特点:

  驱动程序应当实现JDBC 2.0利用程序接口,包括Optional Package接口XADataSource和XAConnection和JTA接口XAResource。

  利用程序服务器应当提供1个DataSource类,用来实现与散布式事务基本结的交互和1个连接池模块(用于改良性能)。

  散布式事务处理的第1步就是利用程序要发送1个事务要求到事务管理程序。虽然最后的commit/rollback决定把事务作为1个简单的逻辑单元来对待,但是依然可能会包括许多事务分支。1个事务分支与1个到包括在散布式事务中的每一个资源管理程序相干联。因此,到3个不同的关系数据库管理的要求需要3个事务分支。每一个事务分支必须由本地资源管理程序提交或返回。事务管理程序控制事务的边界,并且负责最后决定应当提交或返回的全部事务。 这个决定由两个步骤组成,称为Two - Phase Commit Protocol。

  在第1步骤中,事务管理程序轮询所有包括在散布式事务中的资源管理程序(关系数据库管理)来看看哪一个可以准备提交。如果1个资源管理程序不能提交,它将不响应,并且把事务的特定部份返回,以便数据不被修改。

  在第2步骤中,事务管理程序判断否定响应的资源管理程序中是不是有能够返回全部事务的。如果没有否定响应的话,翻译管理程序提交全部事务并且返回结果到利用程序中。

  开发事项管理程序代码的开发者必须与所有3个JTA接口有关:UserTransaction、TransactionManager和XAResource,这3个接口都被描写在

  Sun JTA specification中。JDBC驱动程序开发者只需要关心XAResource接口。这个接口是允许1个资源管理程序参与事务的行业标准X/Open XA协议的Java映照。连接XAResource接口的驱动程序组件负责在事务管理程序和资源管理程序之间担负"翻译"的任务。下面的章节提供了XAResource调用的例子。

 

  JDBC驱动程序和XAResource

  为了简化XAResource的说明,这些例子说明了1个利用程序在不包括利用程序服务器和事项管理程序的情况下应当如何使用JTA。 基本上,这些例子中的利用程序也担负利用程序服务器和事项管理程序的任务。大部份的企业使用事务管理程序和利用程序服务器,由于它们能够比1个利用程序更能够高效地管理散布式事务。 但是遵守这些例子,1个利用程序开发者可以测试在JDBC驱动程序中的JTA支持的硬朗性。而且有1些例子可能不是工作在某个特定的数据库上,这是由于关联在数据库上的1些内在的问题。

  在使用JTA之前,你必须首先实现1个Xid类用来标识事务(在普通情况下这将由事务管理程序来处理)。Xid包括3个元素:formatID、gtrid(全局事务标识符)和bqual(分支修饰词标识符)。

  formatID通常是零,这意味着你将使用OSI CCR(Open Systems Interconnection Commitment, Concurrency和Recovery 标准)来命名。如果你要是用另外1种格式,那末formatID应当大于零。⑴值意味着Xid为无效。

  gtrid和bqual可以包括64个字节2进制码来分别标识全局事务和分支事务。唯1的要求是gtrid和bqual必须是全局唯1的。另外,这可以通过使用指定在OSI CCR中的命名规则规范来完成。

  下面的例子说明Xid的实现:

import javax.transaction.xa.*;
public class MyXid implements Xid
{
 protected int formatId;
 protected byte gtrid[];
 protected byte bqual[];
 public MyXid()
 {
 }
 public MyXid(int formatId, byte gtrid[], byte bqual[])
 {
  this.formatId = formatId;
  this.gtrid = gtrid;
  this.bqual = bqual;
 }

 public int getFormatId()
 {
  return formatId;
 }

 public byte[] getBranchQualifier()
 {
  return bqual;
 }

 public byte[] getGlobalTransactionId()
 {
  return gtrid;
 }

}



  其次,你需要创建1个你要使用的数据库的数据源:

public DataSource getDataSource()
 throws SQLException
 {
  SQLServerDataSource xaDS = new
  com.merant.datadirect.jdbcx.sqlserver.SQLServerDataSource();
  xaDS.setDataSourceName("SQLServer");
  xaDS.setServerName("server");
  xaDS.setPortNumber(1433);
  xaDS.setSelectMethod("cursor");
  return xaDS;
}


  例1—这个例子是用“两步提交协议”来提交1个事务分支:

XADataSource xaDS;
XAConnection xaCon;
XAResource xaRes;
Xid xid;
Connection con;
Statement stmt;
int ret;
xaDS = getDataSource();
xaCon = xaDS.getXAConnection("jdbc_user", "jdbc_password");
xaRes = xaCon.getXAResource();
con = xaCon.getConnection();
stmt = con.createStatement();
xid = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
try {
  xaRes.start(xid, XAResource.TMNOFLAGS);
  stmt.executeUpdate("insert into test_table values (100)");
  xaRes.end(xid, XAResource.TMSUCCESS);
  ret = xaRes.prepare(xid);
  if (ret == XAResource.XA_OK) {
    xaRes.commit(xid, false);
   }
}
catch (XAException e) {
 e.printStackTrace();
}
finally {
 stmt.close();
 con.close();
 xaCon.close();
}


  由于所有这些例子中的初始化代码相同或非常相似,仅仅是1些重要的地方的代码由不同。

  例2—这个例子,与例1相似,说明了1个返回进程:

xaRes.start(xid, XAResource.TMNOFLAGS);
stmt.executeUpdate("insert into test_table values (100)");
xaRes.end(xid, XAResource.TMSUCCESS);
ret = xaRes.prepare(xid);
if (ret == XAResource.XA_OK) {
 xaRes.rollback(xid);
}


  例3—这个例子说明1个散布式事务分支如何中断,让相同的连接做本地事务处理,和它们稍后该如何继续这个分支。 散布式事务的两步提交作用不影响本地事务。

xid = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
xaRes.start(xid, XAResource.TMNOFLAGS);
stmt.executeUpdate("insert into test_table values (100)");
xaRes.end(xid, XAResource.TMSUSPEND);
这个更新在事务范围以外完成,所以它不受XA返回影响。

stmt.executeUpdate("insert into test_table2 values (111)");
xaRes.start(xid, XAResource.TMRESUME);
stmt.executeUpdate("insert into test_table values (200)");
xaRes.end(xid, XAResource.TMSUCCESS);
ret = xaRes.prepare(xid);

if (ret == XAResource.XA_OK) {

xaRes.rollback(xid);

}


  例4—这个例子说明1个XA资源如何分担不同的事务。 创建了两个事务分支,但是它们不属于相同的散布式事务。 JTA允许XA资源在第1个分支上做1个两步提交,虽然这个资源依然与第2个分支相干联。

xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
xid2 = new MyXid(100, new byte[]{0x11}, new byte[]{0x22});
xaRes.start(xid1, XAResource.TMNOFLAGS);
stmt.executeUpdate("insert into test_table1 values (100)");
xaRes.end(xid1, XAResource.TMSUCCESS);
xaRes.start(xid2, XAResource.TMNOFLAGS);
ret = xaRes.prepare(xid1);
if (ret == XAResource.XA_OK) {
 xaRes.commit(xid2, false);
}
stmt.executeUpdate("insert into test_table2 values (200)");
xaRes.end(xid2, XAResource.TMSUCCESS);
ret = xaRes.prepare(xid2);
if (ret == XAResource.XA_OK) {
 xaRes.rollback(xid2);
}


  例5—这个例子说明不同的连接上的事务分支如何连接成为1个单独的分支,如果它们连接到相同的资源管理程序。这个特点改良了散布式事务的效力,由于它减少了两步提交处理的数目。两个连接到数据库服务器上的XA将被创建。每一个连接创建它自己的XA资源,正规的JDBC连接和语句。在第2个XA资源开始1个事务分支之前,它将观察是不是使用和第1个XA资源使用的是同1个资源管理程序。如果这是实例,它将加入在第1个XA连接上创建的第1个分支,而不是创建1个新的分支。 稍后,这个事务分支使用XA资源来准备和提交。

xaDS = getDataSource();
xaCon1 = xaDS.getXAConnection("jdbc_user", "jdbc_password");
xaRes1 = xaCon1.getXAResource();
con1 = xaCon1.getConnection();
stmt1 = con1.createStatement();

xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02});
xaRes1.start(xid1, XAResource.TMNOFLAGS);
stmt1.executeUpdate("insert into test_table1 values (100)");
xaRes1.end(xid, XAResource.TMSUCCESS);
xaCon2 = xaDS.getXAConnection("jdbc_user", "jdbc_password");
xaRes2 = xaCon1.getXAResource();
con2 = xaCon1.getConnection();
stmt2 = con1.createStatement();

if (xaRes2.isSameRM(xaRes1)) {
 xaRes2.start(xid1, XAResource.TMJOIN);
 stmt2.executeUpdate("insert into test_table2 values (100)");
 xaRes2.end(xid1, XAResource.TMSUCCESS);
}
else {
 xid2 = new MyXid(100, new byte[]{0x01}, new byte[]{0x03});
 xaRes2.start(xid2, XAResource.TMNOFLAGS);
 stmt2.executeUpdate("insert into test_table2 values (100)");
 xaRes2.end(xid2, XAResource.TMSUCCESS);
 ret = xaRes2.prepare(xid2);
 if (ret == XAResource.XA_OK) {
  xaRes2.commit(xid2, false);
 }
}
ret = xaRes1.prepare(xid1);
if (ret == XAResource.XA_OK) {
 xaRes1.commit(xid1, false);
}


  例6—这个例子说明在毛病恢复的阶段,如何恢复准备好的或快要完成的事务分支。 它首先试图返回每一个分支;如果它失败了,它尝试着让资源管理程序丢掉关于事务的消息。

MyXid[] xids;
xids = xaRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
for (int i=0; xids!=null && i
 try {
  xaRes.rollback(xids[i]);
 }
 catch (XAException ex) {
  try {
   xaRes.forget(xids[i]);
  }
 catch (XAException ex1) {
  System.out.println("rollback/forget failed: " + ex1.errorCode);
 }
}

}

学习Java的同学注意了!!! 
学习进程中遇到甚么问题或想获得学习资源的话,欢迎加入Java学习交换群,群号码:183993990  我们1起学Java!

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐