2006年11月16日

EJBトランザクション:BMT(JTA)

JTA トランザクション

  • JTA トランザクションは javax.transaction.UserTransaction#begin() メソッドを呼び出して開始する。
  • javax.transaction.UserTransaction インターフェースのメソッドによりトランザクション制御を行う。
メソッド 内容
void commit()

トランザクションをコミットする。

void rollback() トランザクションをロールバックする。


サンプル

銀行の現金出納を管理する。セッション Bean で直接 DB アクセスする。

データベース定義

テーブル作成 SQL スクリプト作成 - D:\teller.sql

DROP TABLE CHECKING;
DROP TABLE CASH_IN_MACHINE;
 
CREATE TABLE CHECKING (
    id          VARCHAR(3)    ,
    balance     DECIMAL(10,2) ,
    CONSTRAINT pk_checking
      PRIMARY KEY (id)
);
 
CREATE TABLE CASH_IN_MACHINE (
    amount      DECIMAL(10,2) ,
    time_stamp  DATE
);
 
INSERT INTO CHECKING VALUES ('123', 500.00);
INSERT INTO CASH_IN_MACHINE VALUES (10000.00, CURDATE());

データベース切替

以下 MySQL にログインして操作する。
warehouse.sql を保存したディレクトリに移動してからログインすること。

USE example_ejb

テーブル作成

SOURCE teller.sql

データベース接続定義

EJB:CMPSavingsAccountEJB と同じものを使用する。

プロジェクト構成

プロジェクト種別 EJB プロジェクト
プロジェクト

BMTJTAEJB

プロジェクト追加先 EAR BMTJTAApp
エンタープライズ Bean teller.TellerBean.java

コーディング完了後、XDoclet 実行。

teller.TellerBean
/**
 * 
 */
package teller;
 
import java.rmi.RemoteException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.SessionContext;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
 
 
/**
 *
 * <!-- begin-user-doc -->
 * A generated session bean
 * <!-- end-user-doc -->
 * *
 * <!-- begin-xdoclet-definition --> 
 * @ejb.bean name="Teller"    
 *           description="An EJB named Teller"
 *           display-name="Teller"
 *           jndi-name="Teller"
 *           type="Stateful" 
 *           transaction-type="Bean"
 *           view-type="remote"
 * 
 * <!-- end-xdoclet-definition --> 
 * @generated
 */
 
public abstract class TellerBean implements javax.ejb.SessionBean {
 
    private static final String dbName = "java:/MySqlDS";
    private String customerId;
    private double machineBalance;
    private SessionContext context;
    private Connection con;
 
    /** 
     *
     * <!-- begin-xdoclet-definition --> 
     * @ejb.create-method view-type="remote"
     * <!-- end-xdoclet-definition --> 
     * @generated
     *
     * //TODO: Must provide implementation for bean create stub
     */
    public void ejbCreate(String id) throws CreateException {
        customerId = id;
 
        try {
            machineBalance = selectMachine();
        } catch (Exception ex) {
            throw new CreateException(ex.getMessage());
        }
    }
 
    public void setSessionContext(SessionContext context)
            throws EJBException, RemoteException {
        this.context = context;
    }
 
    /** 
     *
     * <!-- begin-xdoclet-definition --> 
     * @ejb.interface-method view-type="remote"
     * <!-- end-xdoclet-definition --> 
     * @generated
     *
     * //TODO: Must provide implementation for bean method stub
     */
    public void withdrawCash(double amount) {
        UserTransaction ut = context.getUserTransaction();
 
        try {
            ut.begin();
            updateChecking(amount);
            machineBalance -= amount;
            insertMachine(machineBalance);
            ut.commit();
        } catch (Exception ex) {
            try {
                ut.rollback();
            } catch (SystemException syex) {
                throw new EJBException("Rollback failed: "
                        + syex.getMessage());
            }
            throw new EJBException("Transaction failed: "
                    + ex.getMessage());
        }
    }
 
    /**
     * @ejb.interface-method
     */
    public double getCheckingBalance() {
        try {
            return selectChecking();
        } catch (SQLException ex) {
            throw new EJBException("Unable to get balance: "
                    + ex.getMessage());
        }
    }
 
    private void makeConnection() {
        try {
            InitialContext ic = new InitialContext();
            DataSource ds = (DataSource) ic.lookup(dbName);
 
            con = ds.getConnection();
        } catch (Exception ex) {
            throw new EJBException("Unable to connect to database. "
                    + ex.getMessage());
        }
    }
 
    private void releaseConnection() {
        try {
            con.close();
        } catch (SQLException ex) {
            throw new EJBException("releaseConnection: "
                    + ex.getMessage());
        }
    }
 
    private void updateChecking(double amount) throws SQLException {
        makeConnection();
 
        String updateStatement = "UPDATE CHECKING " +
                "SET balance =  balance - ? WHERE id = ?";
 
        PreparedStatement prepStmt = con
                .prepareStatement(updateStatement);
 
        prepStmt.setDouble(1, amount);
        prepStmt.setString(2, customerId);
        prepStmt.executeUpdate();
        prepStmt.close();
        releaseConnection();
    }
 
    private void insertMachine(double amount) throws SQLException {
        makeConnection();
 
        String insertStatement = "INSERT INTO CASH_IN_MACHINE " +
                "VALUES ( ? , CURDATE() )";
 
        PreparedStatement prepStmt = con
                .prepareStatement(insertStatement);
 
        prepStmt.setDouble(1, amount);
        prepStmt.executeUpdate();
        prepStmt.close();
        releaseConnection();
    }
 
    private double selectMachine() throws SQLException {
        makeConnection();
 
        String selectStatement = "SELECT amount FROM CASH_IN_MACHINE " +
                "WHERE time_stamp = " +
                "(SELECT MAX(time_stamp) FROM CASH_IN_MACHINE)";
        PreparedStatement prepStmt = con
                .prepareStatement(selectStatement);
 
        ResultSet rs = prepStmt.executeQuery();
 
        if (rs.next()) {
            double result = rs.getDouble(1);
 
            prepStmt.close();
            releaseConnection();
 
            return result;
        } else {
            prepStmt.close();
            releaseConnection();
            throw new EJBException("Row for id " + customerId
                    + " not found.");
        }
    }
 
    private double selectChecking() throws SQLException {
        makeConnection();
 
        String selectStatement = "SELECT balance FROM CHECKING " +
                "WHERE id = ?";
        PreparedStatement prepStmt = con
                .prepareStatement(selectStatement);
 
        prepStmt.setString(1, customerId);
 
        ResultSet rs = prepStmt.executeQuery();
 
        if (rs.next()) {
            double result = rs.getDouble(1);
 
            prepStmt.close();
            releaseConnection();
 
            return result;
        } else {
            prepStmt.close();
            releaseConnection();
            throw new EJBException("Row for id " + customerId
                    + " not found.");
        }
    }
}
参考 - ejb-jar.xml (抜粋)
<?xml version="1.0" encoding="UTF-8"?>
 
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
      http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"
  version="2.1">
  <display-name>BMTJTAEJB</display-name>
 
  <enterprise-beans>
    <session>
      <display-name>Teller</display-name>
 
      <ejb-name>Teller</ejb-name>
 
      <home>teller.TellerHome</home>
      <remote>teller.Teller</remote>
      <ejb-class>teller.TellerSession</ejb-class>
      <session-type>Stateful</session-type>
      <transaction-type>Bean</transaction-type>
 
    </session>
 
  </enterprise-beans>
 
  <assembly-descriptor></assembly-descriptor>
 
</ejb-jar>

プロジェクト構成 - Java アプリケーションクライアント

プロジェクト種別 Java プロジェクト
プロジェクト BMTJTAClient
ビルド・パス 追加プロジェクト BMTJTAEJB
ビルド・パス 追加ライブラリ C:\jboss\client\jbosall-client.jar
クラス TellerClient.java
ファイル BMTJTAClient/jndi.properties
TellerClient
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
 
import teller.Teller;
import teller.TellerHome;
 
public class TellerClient {
 
  public static void main(String[] args) {
    try {
      Context initial = new InitialContext();
      Object objref = initial.lookup("Teller");
 
      TellerHome home = (TellerHome) PortableRemoteObject.narrow(
          objref, TellerHome.class);
 
      Teller duke = home.create("123");
 
      System.out.println("checking = "
          + duke.getCheckingBalance());
      duke.withdrawCash(60.00);
      System.out.println("checking = "
          + duke.getCheckingBalance());
      duke.remove();
 
      System.exit(0);
    } catch (Exception ex) {
      System.err.println("Caught an exception.");
      ex.printStackTrace();
    }
  }
}
jndi.properties
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost:1099
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces

実行結果

BMTJTAApp をサーバーで実行する。

サーバービューの JBoss の [状況] が「始動済み」になったら、BMTJTAClient プロジェクトについて Java アプリケーション実行を行う。

実行結果はコンソールに次のとおり出力される。

コンソール表示結果 - TellerClient

checking = 500.0
checking = 440.0

詳細

クラス: teller.TellerBean

public void withdrawCash(double amount)

  • 出金メソッド。
  • javax.ejb.EJBContext#getUserTransaction() で UserTransaction オブジェクトを取得。トランザクション制御可能となる。
  • トランザクションを開始し、DB 更新する。
  • 全ての更新が成功したらトランザクションをコミットする。
  • 例外をキャッチしたらトランザクションをロールバックする。
    • 現金がなくなってもこのロジックでは例外が発生するわけではないので、現金が足りない場合は例外をスローするように改良が必要。
クラス: TellerClient

public class TellerClient

  • EJB に現金残の照会を行い、コンソールに出力する。
  • 出金を行い、再度現金残の照会を行う。
posted by T at 07:17| Comment(0) | TrackBack(0) | EJBトランザクション | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。