S2Daoのサンプルアプリ作成(トランザクション制御モード)

サンプルアプリの概要(トランザクションありの場合)
トランザクション制御を用いて、SAMPLE1テーブルへの挿入、更新、削除処理を行う簡単なサンプルアプリです。」

SAMPLE1テーブル

ID(PK) NUMBER(2)
NAME VARCHAR2(10)

MASTER1テーブル

MASTER_ID(PK) NUMBER(2)
SALARY NUMBER(6)
UTIWAKE VARCHAR2(16)


[開発環境]

  • JDK1.5.0_13
  • eclipse3.2
  • Seasar2 2.4.20
    • S2.4.20.zipを解凍し、各jarファイルにクラスパスを通す(詳細設定は他サイトを参考にして下さい)
  • S2Dao 1.0.47
    • s2-dao-1.0.47.zipを解凍し、各jarファイルにクラスパスを通す

[用意するもの]

  • データベースに上記テーブルSAMPLE1、MASTER1をCREATE
  • Seasar2コンテナからDaoを取り出しDBの参照を行うメインクラスDBQuery.java
  • テーブルとの関連付けに使用するJavaBeans(SAMPLE1テーブル用)…MemberInfo.java
  • テーブルとの関連付けに使用するJavaBeans(MASTER1テーブル用)…MasterInfo.java
  • Daoインタフェース(SAMPLE1テーブル用)…MemberInfoDao.javaインタフェース
  • Daoラッピングクラスのインターフェース…TransactionInterface.java
  • Daoをラッピングしたクラス(diconでこのクラスにDaoを注入します)…TransactionWrapper.java

以下のdiconファイルはクラスパスの通っている場所に配備すること

  • j2ee.dicon(データベースとの接続設定を記述)
  • 自作.dicon(Daoの注入に使用)

[処理の流れその1]
入力値チェックを行う場合

1.メインクラスのDBQueryThreadでSeasar2コンテナからSAMPLE1テーブル更新用の
Daoラッパーコンポーネント(TransactionWrapper)を取得する。

2.トランザクション制御のもと、Daoのインタフェース経由でSAMPLE1テーブルのデータを更新する。
もし途中でSQLExceptionが発生した場合はすべての処理がロールバックされる。

DBQuery.java

package jp.co.companyname.app;

import java.sql.SQLException;
import java.util.List;

import jp.co.companyname.dao.MemberInfo;
import jp.co.companyname.dao.MemberInfoDao;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;
import org.seasar.framework.exception.SQLRuntimeException;

/**
 * S2コンテナとS2Daoを使用して、データベースの更新を行います。
 * 2008/1/16
 * @author shimabukurot
 *
 */
public class DBQuery {

	public static void main(String[] args) {

		// ■複数SQLのトランザクション制御
		System.out.println(Thread.currentThread() + " --------- S2Dao複数SQLトランザクション制御テスト開始");
		S2Container container = S2ContainerFactory.create("自作.dicon");
		TransactionInterface daoWrapper = (TransactionInterface) container.getComponent("TransactionWrapperDesu");
		
		try {
			daoWrapper.doTransactionInsert();
		} catch (SQLRuntimeException e) {
			// ロールバックはSeasar2フレームワークが自動で行ってくれます。(設定は自作.diconファイルで行います)
			System.out.println("daoWrapper呼び出し側でtry-catch");
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread() + " --------- S2Dao複数SQLトランザクション制御テスト終了");
	}
}

MemberInfo.java

package jp.co.companyname.dao;

public class MemberInfo {

	/* 
	 * [構文]
	 * スキーマ名.テーブル名
	 * テーブル名だけでも可
	 */
	public static final String TABLE = "スキーマ名.SAMPLE1";
	
	// 定数アノテーション
	//public static final String id_COLUMN   = "ID";
	//public static final String name_COLUMN = "NAME";
	
	/**
	 * 
	 * RELNOをString型にするとjava.lang.IllegalArgumentExceptionで落ちます。
	 * RELNO定数は、N:1マッピングの連番になるので以下のように0から付けていく必要があります。
	 */
	public static final int masterInfo_RELNO   = 0;
	
	/**
	 * [構文]
	 * 
	 * エンティティ名(JavaBeansのクラス名:先頭は小文字)_RELKEYS = "N側のテーブルのカラム名: 1側のテーブルのカラム名";
	 * ※エンティティ名を間違うとテーブルの結合ができません。
	 */
	public static final String masterInfo_RELKEYS = "ID:MASTER_ID";
	
	private int id;
	private String name;
	
	private MasterInfo abc;// 結合対象のテーブルを表すエンティティ(変数名は任意でよい)
	
	// コンストラクタ
	public MemberInfo(){}
	
	public MemberInfo(int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	public MemberInfo(int id, String name, int salary, MasterInfo masterInfo) {
		this.id = id;
		this.name = name;
		this.abc = masterInfo;
	}
	
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	public MasterInfo getMasterInfo() {
		return abc;
	}

	public void setMasterInfo(MasterInfo masterInfo) {
		this.abc = masterInfo;
	}
}

MasterInfo.java

package jp.co.companyname.dao;

public class MasterInfo {

	/* 
	 * [構文]
	 * スキーマ名.テーブル名
	 * テーブル名だけでも可。
	 */
	public static final String TABLE = "スキーマ名.MASTER1";
	
	private int masterId;
	private int salary;
	private String utiwake;
	
	// 空コンストラクタ(DIコンテナがインスタンス生成を行うために必須)
	public MasterInfo(){};
	
	public MasterInfo(int id, int salary) {
		this.masterId = id;
		this.salary = salary;
	}
	

	public int getSalary() {
		return salary;
	}
	public void setSalary(int salary) {
		this.salary = salary;
	}

	public int getMasterId() {
		return masterId;
	}

	public void setMasterId(int masterId) {
		this.masterId = masterId;
	}

	public String getUtiwake() {
		return utiwake;
	}

	public void setUtiwake(String utiwake) {
		this.utiwake = utiwake;
	}
}

MemberInfoDao.javaインタフェース

package jp.co.companyname.dao;

import java.util.List;

/*
 * MemberInfoから情報を取得するDao。
 * 実装はDIコンテナが行います。
 * よって自分で実装クラスの
 * 宣言をする必要はありません。
 */
public interface MemberInfoDao {
	
	public static final Class BEAN = MemberInfo.class;
	
	public int insert(MemberInfo mInfo);
	
	public int update(MemberInfo mInfo);
	
	public int delete(MemberInfo mInfo);
	
	public void doSomething();
	
	public String getText();
	
	public List findAll();

}

TransactionInterface.javaインターフェース

package jp.co.companyname.app;

public interface TransactionInterface {
	public void doTransactionInsert();
	public void doTransactionUpdate();
}

TransactionWrapper.java

package jp.co.companyname.app;

import org.seasar.framework.exception.SQLRuntimeException;

import jp.co.companyname.dao.MemberInfo;
import jp.co.companyname.dao.MemberInfoDao;

public class TransactionWrapper implements TransactionInterface {

	private MemberInfoDao dao = null;
	
	public TransactionWrapper(){};
	
	// Daoはdiconを用いたコンストラクタインジェクション(Dependency Injection)で注入させる(設定は自作.diconファイルに記述)
	public TransactionWrapper(MemberInfoDao d) {
		this.dao = d;
	}
	
	public void doTransactionInsert() {
		
		System.out.println("doTransactionInsert開始");
		
		/*
		 * [注意点]
		 * ここでtry-catch節を記述してしまうと、ロールバックされずに、SQLException発生前の正常なSQLの実行分がコミットされてしまうので注意!
		 * 下のコードで言うと、五番、六番がコミットされ、七番xxxxはSQLExceptionのため無効になります。
		 * トランザクション制御を正しく動作させるにはdoTransactionInsertメソッドの呼び出し元で
		 * try-catch句を記述する。
		 * 
		 * S2コンテナが呼び出し元クラスからDaoをインタフェースとして、トランザクション管理を行っているのが
		 * 原因だと思われます。
		 */
		dao.insert(new MemberInfo(5, "五番"));
		dao.insert(new MemberInfo(6, "六番"));
		dao.insert(new MemberInfo(7, "七番xxxxxxxxxxxxxxxxxx"));//ここで意図的にSQLExceptionを発生させ、ロールバックを起こさせる。
		dao.insert(new MemberInfo(8, "八番"));

		System.out.println("doTransactionInsert終了");
	}
	
	public void doTransactionUpdate() {
		this.dao.update(new MemberInfo(4, "四番update"));
	}
}

j2ee.dicon(抜粋)

<!-- for Oracle -->
<component name="xaDataSource"
	class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
	<property name="driverClassName">
		"oracle.jdbc.driver.OracleDriver"
	</property>
	<property name="URL">
		"jdbc:oracle:thin:@ホスト名:ポート番号:サービス名"
	</property>
	<property name="user">"TEST"</property>
	<property name="password">"TEST"</property>
	<initMethod name="addProperty">
		<arg>"includeSynonyms"</arg>
		<arg>"true"</arg>
	</initMethod>
</component>

自作.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.3//EN"
"http://www.seasar.org/dtd/components23.dtd">
<components>
    <include path="dao.dicon" />
    
      <component name="MemberInfoDaoDesu" class="jp.co.companyname.dao.MemberInfoDao">
         <aspect>dao.interceptor</aspect>
      </component>

      <component name="TransactionWrapperDesu" class="jp.co.infonic.app.TransactionWrapper">
        <aspect>j2ee.requiredTx</aspect>
        
        ★上記コンポーネントMemberInfoDaoDesuをコンストラクタインジェクション aspectタグではなくargタグである点に注意!
        <arg>MemberInfoDaoDesu</arg>
      </component>

</components>