1. 程式人生 > >Java 處理事務精要

Java 處理事務精要

簡單事務的概念

  我不想從原理上說明什麼是事務,應為那太枯燥了。我只想從一個簡單的例子來說明什麼是事務。

  例如我們有一個訂單庫存管理系統,每一次生成訂單的同時我們都要消減庫存。通常來說訂單和庫存在資料庫裡是分兩張表來儲存的:訂單表,庫存表。每一次我們追加一個訂單實際上需要兩步操作:在訂單表中插入一條資料,同時修改庫存的資料。

  這樣問題來了,例如我們需要一個單位為10的訂單,庫存中有30件,理想的操作是我們在訂單表中插入了一條單位為10的訂單,之後將庫存表中的資料修改為20。但是有些時候事情並不是總是按照你的想法發生,例如:在你修改庫存的時候,資料庫突然由於莫名其妙的原因無法連線上了。也就是說庫存更新失敗了。但是訂單已經產生了,那麼怎麼辦呢?沒辦法,只有手動的修改。所以最好的方式是將訂單插入的操作和庫存修改的操作繫結在一起,必須同時成功或者什麼都不做。這就是事務。

  Java如何處理事務呢?

  我們從java.sql.Connection說起,Connection表示了一個和資料庫的連結,可以通過Connection來對資料庫操作。在通常情況是Connection的屬性是自動提交的,也就是說每次的操作真的更新了資料庫,真的無法回退了。針對上述的例子,一旦庫存更新失敗了,訂單無法回退,因為訂單真的插入到了資料庫中。這並不是我們希望的。

  我們希望的是:看起來成功了,但是沒有真的操作資料庫,知道我想讓他真的發生。可以通過Connection的setAutoCommit(false)讓Connection不自動提交你的資料,除非你真的想提交。那麼如何讓操作真的發生呢?可以使用Connection的commit方法。如何讓操作回退呢?使用rollback方法。

  例如:

  try{

  Connection conn = getConnection(); // 不管如何我們得到了連結

  conn.setAutoCommit(false);

  // 插入訂單

  // 修改庫存

  conn.commit(); // 成功的情況下,提交更新。

  } catch(SQLException ex) {

  conn.rollback(); // 失敗的情況下,回滾所有的操作

  } finally {

  conn.close();

  }

  這裡有一點非常重要,事務是基於資料庫連結的。所以在但資料庫的情況下,事務操作很簡單。

  那麼如果表分佈在兩個不同的資料庫中呢?

  例如訂單表在訂單庫中,庫存表在庫存庫中,那麼我們如何處理這樣的事務呢?

  需要注意,提交也可以遇到錯誤呀!

  try{

  Connection conn1 = getConnection1();

  Connection conn2 = getConnection2();

  // 基於conn1做插入操作

  // 基於conn2做更新操作

  try{

  conn1.commit()

  } catch(SQLExcetion ) {

  conn1.rollback();

  }

  try {

  conn2.commit();

  } catch(SQLException ) {

  conn2.rollbakc();

  // 保證肯定刪除剛才插入的訂單。

  }

  } catch(SQLException ex) {

  // 如果插入失敗,conn1.rollback

  // 如果更新失敗,conn1.rollback && conn2.rollback

  } finally {

  conn1.close();

  conn2.close();

  }

  看看上述的程式碼就知道,其實操作非常的複雜,甚至:保證肯定刪除剛才插入的訂單根本無法保證。

  在上述情況下的事務可以稱之為分散式事務,通過上述的程式碼中事務同時提交處理的部分我們可以得出,要想處理分散式事務,必須有獨立於資料庫的第三方的事務處理元件。

  幸運的是通常情況下,JavaEE相容的應用伺服器,例如:Weblogic,Websphere,JBoss,Glassfish等都有這種分散式事務處理的元件。

  如何使用應用伺服器的分散式事務管理器處理分散式事務?

  以galssfish為例

  1 建立對應兩個資料庫的XA(javax.sql.XADataSource)型別的資料來源。

  2 使用UserTransaction來保證分散式事務。

  try{

  Connection conn1 = datasource1.getConnection();

  Connection conn2 = datasource2.getConnection();

  UserTransaction ut = getUserTransaction();

  ut.begin();

  // 插入訂單

  // 修改庫存

  ut.commit(); // 成功的情況下,提交更新。

  } catch(SQLException ex) {

  ut.rollback(); // 失敗的情況下,回滾所有的操作

  } finally {

  conn.close();

  }

  如何獲取UserTransaction呢?可以使用如下方法

  UserTransaction tx = (UserTransaction)

  ctx.lookup("jndi/UserTransaction");