本文来介绍一下 J2EE 中和事务相关的内容, 在阅读本文之前, 希望读者对分布式有一定的了解.
Java 事务的类型有三种: JDBC 事务, JTA(Java Transaction API)事务, 容器事务. 常见的容器事务如 Spring 事务, 容器事务主要是 J2EE 应用服务器提供的, 容器事务大多是基于 JTA 完成, 这是一个基于 JNDI 的, 相当复杂的 API 实现. 所以本文暂不讨论容器事务. 本文主要介绍 J2EE 开发中两个比较基本的事务: JDBC 事务和 JTA 事务.
JDBC 事务
JDBC 事务, 就是在 Java 中用来控制数据库事务的. JDBC 的一切行为包括事务是基于一个 Connection 的, 在 JDBC 中是通过 Connection 对象进行事务管理. 在 JDBC 中, 常用的和事务相关的方法是: setAutoCommit,commit,rollback 等.
关于 JDBC 的事务, 相对来说比较简单, 主要就是通过 JDBC API 来控制数据库的事务执行.
下面看一个简单的 JDBC 事务代码:
public void JdbcTransfer() { java.sql.Connection conn = null; try{ conn = conn =DriverManager.getConnection("jdbc:oracle:thin:@host:1521:SID","username","userpwd"); // 将自动提交设置为 false, // 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交 conn.setAutoCommit(false); stmt = conn.createStatement(); // 将 A 账户中的金额减少 500 stmt.execute("update t_account set amount = amount - 500 where account_id ='A'"); // 将 B 账户中的金额增加 500 stmt.execute(" update t_account set amount = amount + 500 where account_id = 'B'"); // 提交事务 conn.commit(); // 事务提交: 转账的两步操作同时成功 } catch(SQLException sqle){ try{ // 发生异常, 回滚在本事务中的操做 conn.rollback(); // 事务回滚: 转账的两步操作完全撤销 stmt.close(); conn.close(); }catch(Exception ignore){ } sqle.printStackTrace(); } }
上面的代码实现了一个简单的转账功能, 通过事务来控制转账操作, 要么都提交, 要么都回滚.
JDBC 事务的优缺点
JDBC 为使用 Java 进行数据库的事务操作提供了最基本的支持. 通过 JDBC 事务, 我们可以将多个 SQL 语句放到同一个事务中, 保证其 ACID 特性. JDBC 事务的主要优点就是 API 比较简单, 可以实现最基本的事务操作, 性能也相对较好.
但是, JDBC 事务有一个局限: 一个 JDBC 事务不能跨越多个数据库!!! 所以, 如果涉及到多数据库的操作或者分布式场景, JDBC 事务就无能为力了.
JTA 事务
为什么需要 JTA
通常, JDBC 事务就可以解决数据的一致性等问题, 鉴于他用法相对简单, 所以很多人关于 Java 中的事务只知道有 JDBC 事务, 或者有人知道框架中的事务 (比如 Hibernate,Spring) 等. 但是, 由于 JDBC 无法实现分布式事务, 而如今的分布式场景越来越多, 所以, JTA 事务就应运而生.
如果, 你在工作中没有遇到 JDBC 事务无法解决的场景, 那么只能说你做的项目还都太小. 拿电商网站来说, 我们一般把一个电商网站横向拆分成商品模块, 订单模块, 购物车模块, 消息模块, 支付模块等. 然后我们把不同的模块部署到不同的机器上, 各个模块之间通过远程服务调用 (RPC) 等方式进行通信. 以一个分布式的系统对外提供服务.
一个支付流程就要和多个模块进行交互, 每个模块都部署在不同的机器中, 并且每个模块操作的数据库都不一致, 这时候就无法使用 JDBC 来管理事务. 我们看一段代码:
/ 支付订单处理 /@Transactional(rollbackFor = Exception.class)public void completeOrder() { orderDao.update(); // 订单服务本地更新订单状态 accountService.update(); // 调用资金账户服务给资金帐户加款 pointService.update(); // 调用积分服务给积分帐户增加积分 accountingService.insert(); // 调用会计服务向会计系统写入会计原始凭证 merchantNotifyService.notify(); // 调用商户通知服务向商户发送支付结果通知}
上面的代码是一个简单的支付流程的操作, 其中调用了五个服务, 这五个服务都通过 RPC 的方式调用, 请问使用 JDBC 如何保证事务一致性? 我在方法中增加了 @Transactional 注解, 但是由于采用调用了分布式服务, 该事务并不能达到 ACID 的效果.(关于分布式一致性的问题可以参考: 关于分布式一致性的探究)
JTA 事务比 JDBC 事务更强大. 一个 JTA 事务可以有多个参与者, 而一个 JDBC 事务则被限定在一个单一的数据库连接. 下列任一个 Java 平台的组件都可以参与到一个 JTA 事务中: JDBC 连接, JDO PersistenceManager 对象, JMS 队列, JMS 主题, 企业 JavaBeans(EJB), 一个用 J2EE Connector Architecture 规范编译的资源分配器.
JTA 的定义
Java 事务 API(Java Transaction API, 简称 JTA ) 是一个 Java 企业版 的应用程序接口, 在 Java 环境中, 允许完成跨越多个 XA 资源的分布式事务.
JTA 和它的同胞 Java 事务服务(JTS;Java TransactionService), 为 J2EE 平台提供了分布式事务服务. 不过 JTA 只是提供了一个接口, 并没有提供具体的实现, 而是由 J2EE 服务器提供商根据 JTS 规范提供的, 常见的 JTA 实现有以下几种:
1.J2EE 容器所提供的 JTA 实现(JBoss)
2. 独立的 JTA 实现: 如 JOTM,Atomikos. 这些实现可以应用在那些不使用 J2EE 应用服务器的环境里用以提供分布事事务保证. 如 Tomcat,Jetty 以及普通的 java 应用.
JTA 里面提供了 java.transaction.UserTransaction , 里面定义了下面几个方法
begin: 开启一个事务
commit: 提交当前事务
rollback: 回滚当前事务
setRollbackOnly: 把当前事务标记为回滚
setTransactionTimeout: 设置事务的事件, 超过这个事件, 就抛出异常, 回滚事务
这里, 值得注意的是, 不是使用了 UserTransaction 就能把普通的 JDBC 操作直接转成 JTA 操作, JTA 对 DataSource,Connection 和 Resource 都是有要求的, 只有符合 XA 规范, 并且实现了 XA 规范的相关接口的类才能参与到 JTA 事务中来, 关于 XA 规范, 请看我的另外一篇文章中有相关介绍. 这里, 提一句, 目前主流的数据库都支持 XA 规范.
要想使用用 JTA 事务, 那么就需要有一个实现 javax.sql.XADataSource , javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序. 一个实现了这些接口的驱动程序将可以参与 JTA 事务. 一个 XADataSource 对象就是一个 XAConnection 对象的工厂. XAConnection 是参与 JTA 事务的 JDBC 连接.
要使用 JTA 事务, 必须使用 XADataSource 来产生数据库连接, 产生的连接为一个 XA 连接.
XA 连接 (javax.sql.XAConnection) 和非 XA(java.sql.Connection)连接的区别在于: XA 可以参与 JTA 的事务, 而且不支持自动提交.
public void JtaTransfer() { javax.transaction.UserTransaction tx = null; java.sql.Connection conn = null; try{ tx = (javax.transaction.UserTransaction) context.lookup("java:comp/UserTransaction"); // 取得 JTA 事务, 本例中是由 Jboss 容器管理 javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup("java:/XAOracleDS"); // 取得数据库连接池, 必须有支持 XA 的数据库, 驱动程序 tx.begin(); conn = ds.getConnection(); // 将自动提交设置为 false, // 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交 conn.setAutoCommit(false); stmt = conn.createStatement(); // 将 A 账户中的金额减少 500 stmt.execute("update t_account set amount = amount - 500 where account_id ='A'"); // 将 B 账户中的金额增加 500 stmt.execute(" update t_account set amount = amount + 500 where account_id = 'B'"); // 提交事务 tx.commit(); // 事务提交: 转账的两步操作同时成功 } catch(SQLException sqle){ try{ // 发生异常, 回滚在本事务中的操做 tx.rollback(); // 事务回滚: 转账的两步操作完全撤销 stmt.close(); conn.close(); }catch(Exception ignore){ } sqle.printStackTrace(); } }
上面的例子就是一个使用 JTA 事务的转账操作, 该操作相对依赖于 J2EE 容器, 并且需要通过 JNDI 的方式获取 UserTransaction 和 Connection.
标准的分布式事务
一个分布式事务 (Distributed Transaction) 包括一个事务管理器 (transaction manager) 和一个或多个资源管理器 (resource manager). 一个资源管理器(resource manager) 是任意类型的持久化数据存储. 事务管理器 (transaction manager) 承担着所有事务参与单元者的相互通讯的责任.
JTA 的实现方式也是基于以上这些分布式事务参与者实现的, 具体的关于 JTA 的实现细节不是本文的重点, 感兴趣的同学可以阅读JTA 深度历险 - 原理与实现
看上面关于分布式事务的介绍是不是和 2PC 中的事务管理比较像?
的却, 2PC 其实就是符合 XA 规范的事务管理器协调多个资源管理器的一种实现方式.
我之前有几篇文章关于 2PC 和 3PC 的, 那几篇文章中介绍过分布式事务中的事务管理器是如何协调多个事务的统一提交或回滚的, 后面我还会有几篇文章详细的介绍一下和分布式事务相关的内容, 包括但不限于全局事务, DTP 模型, 柔性事务等.
JTA 的优缺点
JTA 的优点很明显, 就是提供了分布式事务的解决方案, 严格的 ACID. 但是, 标准的 JTA 方式的事务管理在日常开发中并不常用, 因为他有很多缺点:
实现复杂
通常情况下, JTA UserTransaction 需要从 JNDI 获取. 这意味着, 如果我们使用 JTA, 就需要同时使用 JTA 和 JNDI.
JTA 本身就是个笨重的 API
通常 JTA 只能在应用服务器环境下使用, 因此使用 JTA 会限制代码的复用性.
总结
Java 事务的类型有三种: JDBC 事务, JTA(Java Transaction API)事务, 容器事务, 其中 JDBC 的事务操作用法比较简单, 适合于处理同一个数据源的操作. JTA 事务相对复杂, 可以用于处理跨多个数据库的事务, 是分布式事务的一种解决方案.
这里还要简单说一下, 虽然 JTA 事务是 Java 提供的可用于分布式事务的一套 API, 但是不同的 J2EE 平台的实现都不一样, 并且都不是很方便使用, 所以, 一般在项目中不太使用这种较为负责的 API. 现在业内比较常用的分布式事务解决方案主要有异步消息确保型, TCC, 最大努力通知等.
欢迎工作一到五年的 Java 程序员朋友们加入 Java 架构开发: 744677563
本群提供免费的学习指导 架构资料 以及免费的解答
不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导
来源: http://blog.51cto.com/13732225/2131324