第18章 群雄逐鹿下的Java事务管理

第18章 群雄逐鹿下的Java事务管理

本章内容

  • Java平台的局部事务支持

  • Java平台的分布式事务支持

  • 继续前行之前的反思

对于应用程序的开发人员来说,更多的时候,我们只是通过相应产品提供的API接口来访问事务资源,并考虑如何在应用的业务逻辑中界定事务边界,而对于各提供商如何在产品中实现事务支持,通常不是我们需要关心的问题。

本章内容将更多地讲述各事务处理场景下,我们可以通过哪些产品提供的事务处理接口或者标准的事务处理接口来进行事务控制。当然,期间我们也可能提及相应场景下比较受欢迎的几款事务处理的产品实现。

下面将按照从局部事务场景到全局事务场景的顺序,介绍在各场景中Java平台都为我们准备了哪些可用的事务处理API。

18.1 Java平台的局部事务支持

在Java的局部事务场景中,系统里事务管理的具体处理方式,会随着所使用的数据访问技术的不同而各异。我们不是使用专用的事务API米管理事务,而是通过当前使用的数据访问技术所提供的基于connection的API来管理事务。(这里的connection不是特指java.sql.Connection类型,而是泛指应用程序与事务资源之间的通信通道。对于JDBC来说,恰好对应java.sql.Connection,而对于Hibemate,那就应该是Session,诸如此类。)

数据库资源的局部事务管理。 要在对数据库的访问过程中进行事务管理,每种数据访问技术都提供了特定于它自身的事务管理API,比如JDBC是Java平台访问关系数据库最基础的API。如果直接使用JDBC进行数据访问的话,我们可以将数据库连接(java.sql.Connection)的自动提交(AutoCommit)功能设置为false,改为手动提交来控制整个事务的提交或者回滚,如下方代码清单所示。

Connection connection = nu1l;
boolean rollback = false;
try {
    
    
	connection = dataSource.getConnection();
	connection.setAutoCommt(false);
	//使用JDBC进行数据访问
  connection.commit();
}
catch(SQLException e) {
    
    
	e.printStackTrace(); // 不要这样做
	rol1back = true;
}
finally {
    
    
	if(connection != null) {
    
    
		if(rollback) {
    
    
			try{
    
    
				connection.rollback();
			} catch(SQLException e) {
    
    
				e.printStackTrace{
    
    );//不要这样做
			}
		}
		else {
    
    
			try {
    
    
				connection.close();
			} catch(SQLException e) {
    
    
				e.printStackTrace();//不要这样做
			}
		}
  }
}

而如果我们使用Hibernate进行数据访问,那就得使用Hibernate的Session进行数据访问期间的事务管理”,如下方代码清单所示。

Session session = null;
Transaction transaction = null;
try {
    
    
	session = sessionFactory.openSession();
	transaction = session.beginTransaction();
	// 使用Hibemnate进行数据访问
	session.flush();
	transaction.commit();
}
catch(HibernateException e) {
    
    
	transaction.rollback();  
}
finally {
    
    
	session.close();
}

同样的,如果我们使用JDO、TopLink甚至JPA进行数据访问的话,这些数据访问技术也都在它们的数据访问API上,提供了相应的事务管理支持。

消息服务资源的局部事务管理。 在使用JMS进行消息处理的过程中,我们可以通过JMS的javax.jms.Session来控制整个处理过程的事务,如下方代码清单所示。

boolean rollback = false;
Connection con = null;
Session session = null;
try {
    
    
    con = cf.createConnection();
    session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
    // 使用JMS API处理响应消息
    session.commit();
} catch (JMSException е) {
    
    
    e.printStackTrace(); //不要这样做
    rollback = true;
} finally {
    
    
    if (con != null) {
    
    
        if (rollback)
            try {
    
    
                session.rollback();
            } catch (JMSException e1) {
    
    
                e1.printStackTrace(); // 不要这样做
            }
        else {
    
    
            try {
    
    
                con.close();
            } catch (JMSException e) {
    
    
                e.printStackTrace(); // 不要这样做
            }
        }
    }
}

在通过javax.jms.Connection的createSession方法创建javax.jms.Session的时候,我们将该方法的第一个参数指定为true,要求创建一个事务型的javax.jms.Session实例,然后就可以根据情况提交回滚事务了。

以上两种情况之外的时候,我们可能需要借助于JCA(Java Connector Architecture)来管理局部事务。JCA允许通过javax.resource.spi.LocalTransaction接口公开局部事务控制接口。但是,JCA只能与JavaEE的应用服务器集成,所以,通过JCA访问事务资源的应用程序需要绑定到相应的JavaEE服务器。

18.2 Java平台的分布式事务支持

Java平台上的分布式事务管理,主要是通过JTA(Java Transaction API)或者JCA(Java Connector Architecture)提供支持的。

18.2.1 基于JTA的分布式事务管理

JTA是Sun公司提出的标准化分布式事务访问的Java接口规范。 不过,JTA规范定义的只是一套Java接口定义,具体的实现留给了相应的提供商去实现,各JavaEE应用服务器需要提供对JTA的支持。

另外,除了可以使用绑定到各JavaEE应用服务器的JTA实现之外,Java平台上也存在几个独立的并且比较成熟的JTA实现产品,这包括:

  • JOTM中
  • Atomikos
  • JBossTransactions

事务管理通常有两种方式,直接使用JTA接口的编程事务管理以及基于应用服务器的声明性事务管理

1. JTA编程事务管理使用

JTA进行分布式事务的编程式事务管理,通常使用javax.transaction.UserTransaction接口进行,各应用服务器都提供了针对它的JNDI查找服务。下方代码清单中是典型的使用UserTransaction进行事务管理的代码片段。

try {
    
    
	UserTransactionut=(UserTransaction)ctx.lookup("javax.transaction.UserTransaction");
	ut.begin();
	
  // 事务操作
  
	ut.commit();
} catch(NamingException e) {
    
    
	e.printStackTrace();
} catch(NotSupportedException e) {
    
    
	e.printStackTrace();
} catch(SystemException e) {
    
    
	e.printStackTrace();
} catch(SecurityException e) {
    
    
	e.printStackTrace();
} catch(IllegalStateException e) {
    
    
	e.printStackTrace();
} catch(RollbackException e) {
    
    
	e.printStackTrace();
} catch(HeuristicMixedException e) {
    
    }
	e.printStackTrace();
} catch(HeuristicRollbackException e) {
    
    
	e.printStackTrace();
}

如果是在EJB中使用UserTransaction的话,我们可以直接通过javax.ejb.EJBContext获得UserTransaction的引用,如下所示:

UserTransaction ut = ctx.getUserTransaction();

在EJB中直接编程使用UserTransaction,主要是在BMT(Bean Managed Transaction)的情况下。不过,在EJB中,有一种比直接使用编程方式更吸引人的事务管理方式,那就是在CMT(Containe rManaged Transacton)情况下,EJB容器提供的声明性事务管理。

2. JTA声明性事务管理

如果使用EJB进行声明性的分布式事务管理的话(限于CMT的情况),JTA的使用则只限于EJB容器内部,对于应用程序来说则完全就是透明的。现在唯一需要做的工作实际上就是在相应的部署描述符中指定相应的事务属性即可,如下方代码清单所示。

image-20220601180236293

我们通过<transaction-type>指定了让EJB容器来管理事务,并且<trans-attribute>规定所有方法执行都需要相应事务支持。现在,应用程序内部再也不用充斥着各种事务管理的相关代码了。

18.2.2 基于JCA的分布式事务管理

JCA规范主要面向EIS(Enterprise Information System)的集成,通过为遗留的EIS系统和JavaEE应用服务器指定统一的通信标准,二者就可以实现各种服务上的互通。

在应用服务器通过JCA将一些EIS系统集成进来之后,我们就可以让EIS系统中的事务资源也加入到JavaEB应用的全局事务中来。实际上,要在应用程序中控制这种跨越多个系统的分布式事务,我们最终还是通过JTA来进行的,JCA更多的是提供资源的集成。从这一点上来说,在Java平台上管理分布式事务,JTA是唯一的标准接口。

18.3 继续前行之前的反思

我们也看到了,Java平台提供的事务管理API足够丰富,可谓高中低档一应俱全。这非常有助于我们根据合适场景选用合适的事务管理API,但是在实际使用过程中,过多的事务管理策略的选择也会造成一些问题。

1. 局部事务的管理绑定到了具体的数据访问方式

使用JDBC进行数据访问,需要通过java.sql.Connection来控制局部事务。使用Hibernate进行数据访问,则需要通过org.hibernate.sessionorg.hibernate.Transaction来管理局部事务。使用其他的数据访问方式,自然也是要使用它们特定的数据访问API来控制局部事务。

这样导致的问题就是,事务管理代码与数据访问代码甚至业务逻辑代码相互混杂,因为局部事务场景下,我们使用的是数据访问API进行事务控制。实际使用中,如果不能通过合适的方式将事务管理的代码与数据访问代码或者业务逻辑代码进行逻辑上的隔离,将直接导致数据访问代码和业务逻辑代码的可重用性降低,甚至事务管理代码在数据访问层和业务服务层的到处散落。

当前的情况是,各种数据访问方式只提供了简单的事务API,但没有更高层次的抽象来帮助我们隔离事务与数据访问两个方面的过紧耦合。

2. 事务的异常处理事务处理

过程中出现的异常应该都是不可恢复的,所以,应该抛出unchecked exception,并且有一个统一的父类,便于客户端处理。但是现在的情况如下所述。

  • 没有一个统一的事务相关异常体系。使用特定API管理事务的时候,我们需要捕捉这些API特定的异常并进行处理。
  • 许多事务管理代码在使用过程中抛出的依然还是checked exception,这将强制客户端代码来捕捉并处理它。从UserTransaction的使用上我们可以看出,从JNDI查找到事务的开始和结束等操作,简单的事务界定操作,却引出七八个异常需要处理。任何人在使用UserTransaction进行编程式事务管理的时候,也不会认为这样的API设计很好用吧?

3. 事务处理API的多样性

对于开发人员来说,所谓对事务的管理,最多也就是界定一下事务的边界,规定事务在什么地方开始,在什么地方结束。可是,要达到这个目的,我们却要在各种数据访问API或者JTA之间徘徊。

各种事务管理API的存在给了我们更多选择,但没有一个统一的方式来抽象单一的事务管理需求,反而让这种多种选择的优势变得繁杂而不易管理。

4.CMT声明式事务的局限

EJB容器提供的CMT特性是比较受欢迎的事务界定管理方式,因为业务对象中不需要混杂任何事务管理的相关代码,所有的事务管理都通过一些简单的配置交由容器来管理。CMT提供的声明式的事务管理,很好地分离了事务管理与具体的数据资源访问之间的耦合,使得开发人员能够分别专心于业务逻辑和数据访问逻辑的开发。

但CMT形式的声明式事务管理有一个令人惋惜的限制,那就是,你必须借助于EJB容器才能得到这种声明式事务的好处。如果我们的应用程序想要使用声明式的事务管理,就不得不花费-些银子来购买应用服务器厂商的授权,或者,即使是使用开源的应用服务器,但我们的应用程序在此之前并没有特别强烈的必须使用应用服务器的需求,这些情况下,引入应用服务器的做法应该都不太容易让人接受。

总之,要使用CMT的声明式事务管理,强制要求引入EJB容器的支持(当然,也得以EJB的形式提供组件实现),在某些情况下是不合适的。鉴于这些问题,我们应该考虑对目前的状况进行改进,以便简化开发过程,提高整个过程中的开发效率。

  • 我们能否对众多的基于数据访问API的局部事务操作进行合理的抽象,以便隔离事务管理与数据资源访问之间的过分耦合?

  • 能否在合适的地方将各种场景下事务处理抛出的checked exception进行合适的转译,屏蔽事务处理过程中因使用不同的事务API所造成的差异性?

  • 既然对于我们来说,事务管理的需求很简单,基本上就是事物界定的工作,我们能否对事务的界定操作进行统一抽象,以屏蔽各种事务管理API的差异性,使得事务管理能够以统一的编程模型来进行?

  • 既然声明式的事务管理如此诱人,那么能否突破必须依赖EJB容器的限制,寻求一种能够为普通的Java对象(POJO)提供声明式事务的方式呢?

如果你也在思考这些问题,那么,恭喜你,你不用从头去探索这些问题的解决方法了,因为Spring的事务抽象框架正是我们所要寻找的解决方案。

18.4 小结

我们一起回顾了Java平台上事务管理的相关内容,分析了Java平台都为我们提供了哪些管理局部事务和全局事务的支持。我们也对平台上现有的事务管理支持进行了剖析,表达了我们想要改进的一些想法。接下来,我们将一起开始真正的Spring事务抽象层之旅,看一下Spring的事务抽象层是如何帮助我们“圆梦”的。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125088688