Seasar DI Container with AOP

S2Dao-tigerリファレンス

セットアップ

S2Dao-tigerを利用するにはJDK1.5以上が必要です。 また、バージョン番号が同じS2Daoも必要となります。

s2-dao-x.x.x.zipを解凍してできたs2daoディレクトリをEclipseで、 「ファイル→インポート→既存のプロジェクトをワークスペースへ」でインポートしてください。

同様にしてs2-dao-tiger-x.x.x.zipを解凍してできたs2daoディレクトリをEclipseで、 「ファイル→インポート→既存のプロジェクトをワークスペースへ」でインポートしてください。 これで、私と全く同じ環境になります。src/examples配下にサンプルもあります。

S2Dao-tigerとして必要なjarファイルは、S2Dao本体とSeasar2で必要なjarファイル全てとSeasar2の(s2-framework/s2-extension)本体です。 libのjarファイル(hsqldb.jar以外)とsrcのj2ee.dicon(Seasar2.4の場合はjdbc.dicon)、dao.dicon(※後述)をCLASSPATHにとおせば、S2Daoを実行できます。 Eclipseにインポートして使う場合は設定は不要です。

dao.dicon

dao.diconはS2Daoの動作を設定するファイルです。 設定を変更したい場合はdao.diconのリファレンスを参照してください。

j2ee.dicon

S2Pagerの機能を使用する場合には、j2ee.diconを修正する必要があります。 具体的な設定内容はS2Pagerのドキュメントを ご覧ください。

作成すべきファイル

S2Dao-tigerとはS2daoの定数アノテーションの変わりにJDK1.5から利用可能になったアノテーションを利用できるようにしたものです。

従って、s2dao-tigerを利用するにあたり必要なファイルはS2Daoと変わりありません。

するにあたり、JavaBeans、Dao(.java)、diconファイル、SQLファイル(.sql)の作成が必要となります。
各ファイルの関連イメージ図は次のようになります。

JavaBeansはテーブル、DaoはJavaBeans、diconファイルはDao、SQLファイルはDaoとそれぞれ関連しています。
各ファイルの実装・設定方法の詳細は次のようになります。

JavaBeans

JavaBeansはテーブルとの関連付けに使用します。 JavaBeansにテーブルの関連付けを行うには、以下の定数宣言とメソッドの実装が必要になります。

JavaBeansの構成と説明内で使用するテーブルは以下の通りです。

テーブル:EMP
カラム名 NotNull 主キー
EMPNO NUMBER
ENAME VARCHAR

DEPTNUM NUMBER


テーブル:DEPT
カラム名 NotNull 主キー
DEPTNO NUMBER
DNAME VARCHAR

Beanアノテーション

org.seasar.dao.annotation.tiger.Beanアノテーションによってテーブルとの関連づけ、 永続化されないカラムの指定、排他制御用のプロパティの指定を行う事ができます。

Beanとテーブルの関連付けは以下の形式で行うことができます。

@Bean(table="テーブル名")

EMPテーブルの場合以下のようになります。

@Bean(table="EMP")

スキーマの定義をすることも可能です。スキーマ名が"SCOTT"の場合は以下のようになります。

@Bean(table="SCOTT.EMP")
※クラス名からパッケージ名を除いた名前がテーブル名と一致する場合は、Beanアノテーションを定義する必要はありません。 また、dao.diconでorg.seasar.dao.impl.DecamelizeTableNamingを指定している場合、クラス名からパッケージ名を除いて、単語の区切りに_をつけて大文字にした名前がテーブル名と一致する場合は、Beanアノテーションを定義する必要はありません。(1.0.44以降)

Beanとテーブルの関連付けは以下の形式で行うことができます。

永続化されないカラム

カラムが永続化の対象かどうかという情報は、テーブルの定義(JDBCのメタデータ)より自動的に取得されます。 また、明示的にNO_PERSISTENT_PROPSを使って永続化したくないカラムを指定することもできます。 NO_PERSISTENT_PROPSに空文字を指定する とJDBCのメタデータのメタデータを使わずにすべてのプロパティを永続化の対象とみなします。

@Bean(noPersistentProperty={"dummy1","dummy2"})
VERSION_NO_PROPERTYアノテーション

versionNoによる排他制御用のプロパティ名をデフォルトのversionNoから変えるときに使うのがVERSION_NO_PROPERTYアノテーションです。次のように使います。

@Bean(versionNoProperty="myVersionNo")
TIMESTAMP_PROPERTYアノテーション

timestampによる排他制御用のプロパティ名をデフォルトのtimestampから変えるときに使うのがTIMESTAMP_PROPERTYアノテーションです。次のように使います。

@Bean(versionNoProperty="myTimestamp")
COLUMNアノテーション

テーブルのカラムとインスタンス変数の関連付けにはorg.seasar.dao.annotation.tiger.Columnアノテーションを使用します。
Columnアノテーションは以下の形式で定数を宣言します。

@Column(カラム名)

employeeNoというプロパティにEMPNOカラムを関連付けする場合以下のようになります。
@Column("EMPNO")
public String getEmployeeNo(){
	return employeeNo_;
};
※プロパティ名とカラム名から_(アンダースコア)を除いた名前が一致する場合は、COLUMNアノテーションを定義する必要はありません。 テーブルに存在しないプロパティは、自動的に無視されるので、特に何か定義する必要はありません。
N:1マッピング

N:1マッピングとは、複数の従業員の行に1つの部署の行が関連付けられるような場合のマッピングです。
N:1マッピングを使用するには、org.seasar.dao.annotation.tiger.Relationアノテーションを宣言する必要があります。
RELNO定数は以下の形式になります。

- @Relation(relationNo=数値,relationKey="N側のテーブルのカラム名: 1側のテーブルのカラム名")

relationNoは、N:1マッピングの連番です。
例えば、AAAのテーブルにBBB,CCCのテーブルがN:1マッピングされるとするとBBBのRELNOは0、CCCのRELNOは1になります。
RELNOは結果セットに含まれているカラムが、どのテーブルに所属しているのかを判定することに使われます。
例えば、SELECT ..., BBB.HOGE AS HOGE_0, ... FROM AAA, BBB ...のようなSELECT文があった場合、 HOGE_0はBBBテーブルに含まれているHOGEカラムであると認識されます。

N:1マッピングのキーはrelationKeyで指定します。 キーが複数ある場合には、カンマ( , )で区切ります。例えば、mykey1:yourkey1, mykey2:yourkey2のようにします。
EMPテーブルのDEPTNUMカラムとDEPTテーブルのDEPTNOを関連付ける場合は以下のようになります。

@Relation(
	relationNo = 0,
	relationKey = "DEPTNUM:DEPTNO"
)

1側のテーブルのカラム名がN側のテーブルのカラム名に等しい場合は、1側のテーブルのカラム名を省略することができます。 その場合以下のように定義することが出来ます。

@Relation(
	relationNo = 0,
	relationKey = "DEPTNUM"
)

また1側のテーブルのカラム名とN側のテーブルのカラム名に等しく、1側のテーブルのカラム名がプライマリーキーの場合、RELKEYS定数を省略することができます。

IDの自動生成

ID(プライマリーキー)をRDBMSに自動生成させて、自動生成された値をBeanに自動的に設定することが出来ます。 そのために使うのが、org.seasar.dao.annotation.tiger.Idアノテーションです。 バージョン1.0.47からは複数のプロパティにIDアノテーションを指定できるようになりました。 複数の指定をした場合、Beanは複合主キーを持ったテーブルと関連づいているとみなされます。

IDアノテーションは、次のように指定します。

@Id(IdType)

IdTypeはenumのorg.seasar.dao.annotation.tiger.IdTypeのうちから1つ選んで指定します。

IdType.IDENTITYを指定するとID(プライマリーキー)をRDBMSに自動生成させてBeanに自動的に設定します。

@Id(value=IdType.IDENTITY)
public long getEmpno() {
	return this.empno;
}

SEQUENCEを使うことも出来ます。myseqの部分は、実際のSEQUENCEに置き換えてください。

@Id(value=IdType.SEQUENCE, sequenceName="myseq")
public long getEmpno() {
	return this.empno;
}

SEQUENCEを使う場合、バージョン1.0.47以降ではallocationSizeを指定することもできます。 allocationSizeを指定すると、SEQUENCEにアクセスする回数が減るためINSERTのパフォーマンスが向上します。 allocationSizeに指定する値はSEQUENCEの増分値より小さいか同じ値でなければいけません。

@Id(value=IdType.SEQUENCE, sequenceName="myseq", allocationSize=50)
public long getEmpno() {
	return this.empno;
}

手動でIDを設定する場合は、何も指定する必要がありません。テーブルのプライマリーキーだという情報は、テーブルの定義(JDBCのメタデータ)より自動的に取得されます。また、明示的にassignedを指定することもできます。

@Id(value=IdType.ASSIGNED)
public long getEmpno() {
	return this.empno;
}

IDアノテーションを、RDBMSごとに切り替えて使うことができます(1.0.41以降)。例えば、Identityにのみ対応するMySQLではIdentityを使い、SEQUENCEにのみ対応するOracleではSEQUENCEを使うことができます。

@Ids( {
	@Id(value = IdType.IDENTITY, dbms = "mysql"),
	@Id(value = IdType.SEQUENCE, sequenceName = "myseq", dbms = "oracle"),
})

指定したRDB以外のデフォルト値を指定することもできます。(1.0.41以降)

@Ids( {
	@Id(value = IdType.IDENTITY, dbms = "oracle"),
	@Id(value = IdType.SEQUENCE, sequenceName = "myseq") 
})

RDBMSのサフィックスと、使用できるIDの取得方法は、表の通りです。

DBMS サフィックス Identity SEQUENCE
Oracle oracle ×
DB2 db2
MSSQLServer mssql ×
MySQL mysql ×
PostgreSQL postgre ×
Firebird firebird ×
MaxDB maxdb ×
HSQL hsql
Derby derby
H2 h2
その他DB なし × ×
VALUE_TYPEアノテーション

Javaのプロパティとデータベースのカラムの型のマッピングはS2Daoにより自動で行われますが、org.seasar.dao.annotation.tiger.ValueTypeアノテーションを使うことで、明示的に指定することも可能です。 ValueTypeアノテーションに指定するコンポーネントは自作することも可能ですが、よく利用されるものはorg.seasar.dao.types.ValueTypesクラスに定数としてあらかじめ定義されています。 ValueTypesクラスの定数を以下の表に示します。

ValueTypesクラスの定数 説明
CLOB JavaのStringとデータベースのCLOB型をマッピングします。
BLOB Javaのbyte[]とデータベースのBLOB型をマッピングします。
SERIALIZABLE_BYTE_ARRAY JavaのSerializableとデータベースのバイナリ型(データベースにより型名が異なります)をマッピングします。
SERIALIZABLE_BLOB JavaのSerializableとデータベースのBLOB型をマッピングします。
POSTGRE_BLOB Javaのbyte[]とPostgreSQLのOID型をマッピングします
POSTGRE_SERIALIZABLE_BLOB JavaのSerializableとPostgreSQLのOID型をマッピングします。

この定数の利用法については、以下のCLOB型とのマッピング例を参照ください。

CLOB型とのマッピング例

JavaBeansのString型プロパティをテーブルのCLOB型と対応付けるには、次のようにValueTypeアノテーションを使用します。

// JavaBeans
@ValueType("stringClobType")
private String aaa;
public String getAaa() {
    return aaa;
}
public void setAaa(String aaa) {
    this.aaa = aaa;
}

diconファイルでは、OGNL式を使用して、ValueTypesクラスの定数を参照します。 コンポーネント名とValueTypeアノテーションの値(ここでは"stringClobType")は一致させる必要があります。

<!-- xxx.dicon -->
<component name="stringClobType">
    @org.seasar.dao.types.ValueTypes@CLOB
</component>
カラムに対応するインスタンス変数の宣言
カラムの値はBeanのインスタンス変数で表します。インスタンス変数の宣言の方法には2種類あります。
JavaBeansのプロパティとして宣言する方法

テーブルのカラムに対応したインスタンス変数とアクセッサメソッドが必要です。 アクセッサメソッドの名前はJavaBeansの命名規約に従い以下の形式になります。

getterメソッド

- public getプロパティ名()

setterメソッド

- public void setプロパティ名(引数)

NUMBER型のEMPNOというカラムの場合、プロパティとアクセッサは以下のように記述できます。(関連:COLUMNアノテーション
private long empno;

public long getEmpno() {
    return empno;
}

public void setEmpno(long empno) {
    this.empno = empno;
}

※カラムがNull可の場合、プリミティブ型を指定するとNullの際、0(ゼロ)が返されるのでNullを扱う場合は、 ラッパークラス(intならjava.lang.Integer)を指定して下さい。

以上の設定を行ったEMPテーブルに関連付くBeanは次のようになります。

@Bean(table="EMP")
public class Employee {
    private long empno;

    private String ename;

    private Short deptnum;

    private Department department;

    public Employee() {
    }

    @Relation(
        relationNo = 0,
        relationKey = "DEPTNUM:DEPTNO"
    )
    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public Short getDeptnum() {
        return deptnum;
    }

    public void setDeptnum(Short deptnum) {
        this.deptnum = deptnum;
    }

    public long getEmpno() {
        return empno;
    }

    public void setEmpno(long empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }
}
publicフィールドとして宣言する方法

バージョン1.0.47から、Seasar2.4と組み合わせた場合に、カラムに対応したインスタンス変数をpublicフィールドとして宣言できるようになりました。 アクセッサが不要になるため、Beanの記述が簡単になります。

この方法を使用した場合、EMPテーブルに関連付くBeanは次のように記述できます。

@Bean(table="EMP")
public class Employee {
    public long empno;

    public String ename;

    public Short deptnum;

    @Relation(
        relationNo = 0,
        relationKey = "DEPTNUM:DEPTNO"
    )
    public Department department;
}

Dao(Data Access Object)

Daoはインターフェースとして作成します。永続化されるデータとロジックを分離して、Dao本来の目的であるBeanの永続化を行います。 JavaBeansとは1:1の関係にあるので、一つのJavaBeansに対して一つのDaoを作成することになります。 Daoのメソッドを呼ぶことにより、メソッドに対応したSQLファイルに記述されているSQLが実行されます。 Daoを作成するには、以下の点が必要になります。

またメソッドの引数をSQL文で参照したり、WHERE句、ORDER句を追加、更新のSQLに含めない、あるいは含めるプロパティを指定する場合には、以下のアノテーションを使います。

メソッドの命名規約

Daoのメソッドの名前は下の表にあげた名前で始まっている必要があります。 命名規約を変更したい場合は、dao.diconのリファレンスの「DaoNamingConventionImpl」を参照してください。

処理 名称
挿入 insert,add,create
更新 update,modify,store
削除 delete,remove
検索 上記以外
S2Daoアノテーション

DaoがどのJavaBeans(エンティティ)に関連付けられているのかはorg.seasar.dao.annotation.tiger.S2Daoアノテーションで指定します。 バージョン1.0.46以降では、PROCEDUREアノテーションやSQLファイルを利用するメソッドのみが定義されたDaoに限りこのアノテーションの指定は不要です。
S2Daoアノテーションは以下の形式で定数を宣言します。

- @S2Dao(JavaBeans名.class)

EmployeeDaoクラスがEmployeeエンティティに関連付けられる場合は次のように定義します。

@S2Dao(bean = Employee.class)
public interface EmployeeDao{
}
Argumentsアノテーション

メソッドの引数をSQL文で参照できるように、org.seasar.dao.annotation.tiger.Argumentsアノテーションを使用し、メソッドの引数名を指定します。メソッドの引数名は、リフレクションで取得できないためです。
Argumentsアノテーションは以下の形式で定数を宣言します。

- @Arguments("引数名")

public Employee getEmployee(int empno)というメソッドがDaoに定義されていた場合の引数名は次のように定義します。

@Arguments("empno")
public Employee getEmployee(int empno);

メソッドの引数がテーブルのカラム名に対応している場合、引数名にはテーブルのカラム名を指定します。例えば、メソッドの引数名がempnoで、テーブルのカラム名がemployeenoの場合は、employeenoを指定するということです。 引数が複数ある場合には、文字列の配列で指定します。

@Arguments( { "job", "deptno" })
public List getEmployeeByJobDeptno(String job, Integer deptno);

引数がDTOの場合にはArgumentsアノテーションを省略できます。また、SQLファイル、Sqlアノテーション、Queryアノテーションのいずれかを使用するメソッドで、引数が1つの場合もArgumentsアノテーションを省略できます。

Queryアノテーション

自動的に生成されるSELECT文やDELETE文(DELETE文は1.0.44以降)にWHERE句やORDER BY句を追加するには、Queryアノテーションを使用します。
QUERYアノテーションは以下の形式で定数を宣言します。

- @Query("WHERE句ORDER BY句")

引数で給与の上限と下限を指定し、その間に含まれる従業員を抽出する場合、次のようにします。

@Query("sal BETWEEN ? AND ? ORDER BY empno")
public List getEmployeesBySal(Float minSal, Float maxSal);

上記例の“?”をバインド変数と言います。バインド変数をQUERYアノテーションに記述することにより、メソッドの引数の値が順に”?”の部分に代入されます。 ARGSアノテーションは、必要ありません。ORDER BY句だけを記述するときは、ORDER BYで始めてください。SQLコメントも記述することが出来ます。 SQLコメントを使用したサンプルは以下のとおりです。

@Query("job = /*job*/'CLERK'/*IF deptno != null*/ AND deptno = /*deptno*/20/*END*/")

上記サンプルは、引数deptnoがnullでない場合、deptnoが引数の値と一致するという条件を追加します。SQLコメントについての詳しい説明は、SQLコメントの項目を参照して下さい。

メソッドの定義

Daoに定義したメソッドを呼ぶことにより、対応するSQLファイルに記述されているSQLが実行されますが、 更新(INSERT, UPDATE, DELETE)、検索処理ごとにメソッドの命名規約があります。S2Daoではメソッドの命名規約よりSQL文の中身を自動的に決定しています。 また、定義するメソッドのオーバーロードはサポートしていません。

  • INSERT処理
  • INSERT処理を行なうメソッドの名前が、insert,add,createではじまる必要があります。 戻り値はvoidあるいはintを指定してください。 intの場合、更新した行数が戻り値となります。引数の型はエンティティの型と一致させます。
    メソッドの定義例は以下のようになります。

    public void insert(Department department);
    public int addDept(Department department);
    public void createDept(Department department);
    
  • UPDATE処理
  • UPDATE処理を行うメソッドの名前が、update,modify,storeではじまる必要があります。 戻り値はvoidあるいはintです。intの場合、更新した行数が戻り値となります。引数の型はエンティティの型と一致させます。
    メソッドの定義例は以下のようになります。

    public int update(Department department);
    public int modifyDept(Department department);
    public void storeDept(Department department);
    

    v1.0.37からはメソッド名の末尾にUnlessNullが付いているUPDATE処理の場合、引数に渡されたBeanのフィールドの内、nullでは無い値のみを更新対象とします(バッチ処理の場合はこの機能は使えません)。 UnlessNullは、dao.diconファイルの設定により変更することができます。 設定を変更するにはdao.diconのリファレンスの「DaoNamingConventionImpl」を参照してください。
    メソッドの定義例は以下のようになります。

    public int updateUnlessNull(Department department);
    

    v1.0.40からはメソッド名の末尾にModifiedOnlyが付いているUPDATE処理の場合、 引数に渡されたBeanのフィールドのうち、変更した(setterを呼んで変更した)フィールドのみが更新対象となります。(バッチ処理の場合はこの機能は使えません)
    この機能を利用するには、ModifiedOnlyを使うメソッドに渡すBeanがgetModifiedPropertyNamesを実装している必要があります。 getModifiedPropertyNamesメソッドを実装する方法は、以下の二通りがあります。

    • Daoの検索メソッドからBeanを取得する
      S2Daoが戻り値のBeanに対してAOPを使ってメソッドを自動的に実装します。 この方法を使うには、dao.diconに記述されているorg.seasar.dao.impl.NullBeanEnhancerをorg.seasar.dao.impl.BeanEnhancerImplへ入れ替える必要があります。 カスタマイズの仕方は、dao.diconのリファレンスの「UPDATE時、修正したプロパティのみを更新対象とする」を参照してください。

      ただし、この方法を使った場合、Daoから取得したBeanのインスタンスはシリアライズできますがデシリアライズできないことがあります。 たとえば、Teeda Extensionにオブジェクトをシリアライズ/デシリアライズするItemsSaveという機能がありますが、エンハンスされたBeanのインスタンスに対してこの機能は利用できません。 Beanをシリアライズ/デシリアライズする場合はBeanEnhancerImplを使用しないことを検討してください。
    • BeanにgetModifiedPropertyNamemメソッドを直に実装する
      この場合、Beanの定義例は以下のようになります。
      public class Emp {
      
          public static final String TABLE = "EMP";
      
          private long empno;
      
      	(略)
      
          private java.util.Set _modifiedPropertySet = new java.util.HashSet();
      
          public long getEmpno() {
              return this.empno;
          }
      
          public void setEmpno(long empno) {
              _modifiedPropertySet.add("empno");
              this.empno = empno;
          }
      
      (略)
          public java.util.Set getModifiedPropertyNames() {
              return _modifiedPropertySet;
          }
      }
      				
    ModifiedOnlyは、dao.diconファイルの設定により変更することができます。 設定を変更するにはdao.diconのリファレンスの「DaoNamingConventionImpl」を参照してください。 メソッドの定義例は以下のようになります。
    public int updateModifiedOnly(Department department);
    

  • DELETE処理
  • DELETE処理を行うメソッドの名前が、delete,removeではじまる必要があります。 戻り値はvoidあるいはintです。 intの場合、更新した行数が戻り値となります。引数の型はエンティティの型と一致させます。
    メソッドの定義例は以下のようになります。

    public void delete(Department department);
    public int removeDept(Department department);
    
  • 検索(SELECT)処理
  • 検索処理を行ないたい場合は、戻り値の型を指定します。戻り値の型がjava.util.Listを実装している場合、SELECT文でエンティティのリストを返します。 戻り値がエンティティ型の配列である場合、エンティティ配列を返します。戻り値の型がエンティティの型の場合、エンティティを返します。 それ以外の場合、SELECT count(*) FROM empのように1行で1のカラムの値を返すというようにS2Daoは想定します。

NO_PERSISTENT_PROPSアノテーション

更新するときに、このプロパティは、SQLに含めて欲しくないという場合もあります。 そのような場合は、org.seasar.dao.annotation.tiger.NoPersistentPropertyアノテーションを使います。

@NoPersistentProperty({"sal", "comm"})

上記のように指定すると、insertメソッドで、salとcommプロパティは永続化の対象になりません。

PERSISTENT_PROPSアノテーション

更新するときに、このプロパティだけをSQLに含めたいという場合もあります。 そのような場合は、org.seasar.dao.annotation.tiger.PersistentPropertyアノテーションを使います。

@PersistentProperty("deptno")

上記のように指定すると、insertメソッドで、プライマリーキー、versionNo、timestampのプロパティに加えて、 PERSISTENT_PROPSアノテーションで指定したプロパティが永続化の対象になります。

SQLアノテーション

バージョン1.0.28より、org.seasar.dao.annotation.tiger.Sqlアノテーションを使用することが可能です。機能はSQLファイルと同様で、アノテーションにてSQL及びSQLコメントを使用することが可能です。

  • SQLファイルとDaoに定義したメソッドの関連付け
  • examples.dao.EmployeeDao#getAllEmployees()に対応するSQLアノテーションは以下のようになります。
    @Sql("SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
     WHERE emp.deptno = dept.deptno ORDER BY emp.empno;")
    public List getAllEmployees();
    
  • 複数DBMS対応
  • DBMSごとに使用するSQLアノテーションを指定することができます。(1.0.41以降) どのDBMSを使っているのかはjava.sql.DatabaseMetadata#getDatabaseProductName()に応じて、S2Daoが自動的に判断しています。 S2DaoのほうでDBMSごとにサフィックスを決めているので、SQLアノテーションにサフィックスを追加します。 例えばオラクルの場合、サフィックスはoracleで、MySQLのサフィックスはmysqlなので、

    @Sql(value = "SELECT * FROM BBB", dbms = "oracle")}
    
    または
    @Sqls( { 
    	@Sql(value = "SELECT * FROM BBB", dbms = "oracle"),
    	@Sql("SELECT * FROM DDD"), dbms = "mysql" })
    
    というSQLアノテーションになります。

    指定したRDB以外のデフォルト値を指定することもできます。(1.0.41以降)

    @Sqls( { 
    	@Sql(value = "SELECT * FROM BBB", dbms = "oracle"),
        @Sql("SELECT * FROM DDD") })
    
    DBMSとサフィックスの関係は以下の通りです。
    DBMS サフィックス
    Oracle oracle
    DB2 db2
    MSSQLServer mssql
    MySQL mysql
    PostgreSQL postgre
    Firebird firebird
    HSQL hsql
    H2 h2
Procedureアノテーション

バージョン1.0.31より、org.seasar.dao.annotation.tiger.Procedureアノテーションを使用することによりStoredProcedureやStoredFunctionを実行することができます。PROCEDUREアノテーションは以下の形式のうちいずれかを指定します。

- @Procedure("カタログ名.スキーマ名.プロシージャ名")

- @Procedure("スキーマ名.プロシージャ名")

- @Procedure("パッケージ名.プロシージャ名")(Oracle/1.0.41以降)

- @Procedure("スキーマ名.パッケージ名.プロシージャ名")(Oracle/1.0.41以降)

  • サポートされるStoredProcedureの範囲について
  • PROCEDUREアノテーションでは、戻り値のあるStoredProcedure、IN,OUT,INOUTパラメータを複数持つプロシージャーをサポートしています(バージョン1.0.47以降では代わりにPROCEDURE_CALLアノテーションを使用することをお勧めします)。 ただし、OUTやINOUTパラメータが複数ある場合はメソッドの返り値がMapでなければなりません。

    また、DBMSやJDBCドライバの実装によっては、利用できない場合があります。

    DBMS 制限事項
    Oracle すべてのパターンが利用できます。
    DB2 -
    MSSQLServer -
    MySQL StoredFunctionがサポートされていません
    PostgreSQL 2つ以上のOUTやINOUTパラメータを持つStoredFunctionがサポートされていません
    Firebird -
    HSQL サポートされていません
PROCEDURE_CALLアノテーション

バージョン1.0.47で導入されました。このアノテーションはPROCEDUREアノテーションを使用したプロシージャ呼び出しの問題点を解消しています。

PROCEDUREアノテーションを使用したプロシージャ呼び出しには次の問題点がありました。

  • データベースのメタデータを元にプロシージャを実行しているため、RDBMSがメタデータが正しく返さない場合に呼び出しができない。
  • OUTパラメータやINOUTパラメータなどプロシージャからの戻り値が複数ある場合、これらの値をMapに詰めるため型情報が失われる。取り出すには明示的にキャストが必要になる。

PROCEDURE_CALLアノテーションを使用したプロシージャ呼び出しは上記の問題に対し次のように対応しています。

  • データベースのメタデータは使用しない。プロシージャの名前とパラメータの型の情報を使いプロシージャを呼び出す。基本的にRDBMSがCallableStatementによる呼び出しを認めていれば実行可能である。
  • プロシージャへのパラメータはINパラメータでもOUTパラメータでもDTOのフィールドに記述する。戻り値はDTOのフィールドに戻されるため型情報は失われない。

PROCEDURE_CALLアノテーションは次のように指定してください。

- @ProcedureCall("プロシージャ名");

@ProcedureCall("hogeProcedure")
public Map hoge();
@ProcedureCall("fooProcedure")
public void foo(FooDto dto);

メソッドの引数は「なし」もしくは「DTOひとつ」でなければいけません。プロシージャがResultSetを返す場合には、戻り値を任意のDTOやDTOのList、Map等にすることができます。 ResultSetが返されない場合はvoidにしてください。 引数のDTOにはプロシージャのパラメータに対応するフィールドとパラメータのタイプを示すPROCEDURE_PARAMETERアノテーションを指定する必要があります。

PROCEDURE_PARAMETERアノテーション

バージョン1.0.47で導入されました。このアノテーションはPROCEDURE_CALLアノテーションが指定されたメソッドの引数として使用されるDTO内に指定します。PROCEDURE_PARAMETERアノテーションは次のように指定してください。

- @ProcedureParameter(ParameterType列挙型);

public static class FooDto {
  @ProcedureParameter(ParameterType.RETURN)
  public double aaa;
  @ProcedureParameter(ParameterType.IN)
  public double bbb;
  @ProcedureParameter(ParameterType.INOUT)  
  public double ccc;
  //...
}

@ProcedureParameterにはParameterTypeの列挙型であるRETURNINOUTINOUTのいずれかを指定してください。 ParameterTypeの列挙型はプロシージャのパラメータのタイプを表します。 アノテーションを指定するフィールドの数と定義順序はプロシージャのパラメータと一致させる必要があります。 フィールドのアクセス修飾子に制限はありません。privateでもpublicでも可です。

この機能では、ソースコード上に記述したフィールドの順番と、 コンパイルされた.classファイル内のフィールドの順番が同じになることを前提としていますが、 これはJavaの仕様では保証されていません。 SunのJDKやEclipseではソースコード上と.classファイル内のフィールド順は同じになっていますが、 フィールドの順番が変わってしまう環境ではストアドの呼び出しが失敗します。 フィールドの順番が変わってしまう環境があった場合はSeasar-userメーリングリストまでお知らせください。

CHECK_SINGLE_ROW_UPDATEアノテーション

バージョン1.0.47からCheckSingleRowUpdateアノテーションを使うことで、自動生成した更新SQLの実行後に、 更新行数のチェックをするかしないか選択できます。 CheckSingleRowUpdateアノテーションにfalseを指定すると、1行も更新できなかったとしてもNotSingleRowUpdateRuntimeExceptionを発生させないように設定できます。 CheckSingleRowUpdateアノテーションは、Dao全体またはメソッド単位に指定可能です。

// Dao全体の更新行数チェックをoffにする例

@CheckSingleRowUpdate(false)
public interface HogeDao {
    // ...
public interface FugaDao {

    // メソッド単位で更新行数チェックをoffにする例
    
    @CheckSingleRowUpdate(false)
    insert(Fuga fuga);
    // ...

diconファイル

diconファイルはDaoをコンテナにコンポーネント登録します。Dao機能を使用するには、登録したDaoに対して、AOPを適用する必要があります。 diconファイルはどこに配置してもいいのですが、通常Daoと同じ場所に配置します。なお、1.0.36以降はjarファイル内にdao.diconが含まれているため、デフォルトで使用する場合はdao.diconを配置する必要はないです。 diconファイルの詳しい設定方法については、DIContainerを参照して下さい。

S2DaoInterceptorの適用

Dao機能を使用するにはorg.seasar.dao.interceptors.S2DaoInterceptorを登録したDaoに対してAOPを適用します。
AOPについては、AOPのページを参照して下さい。
以下はDao(example.dao.EmployeeDao)をコンポーネント登録するサンプル

EmployeeDao.dicon
<components>
    <include path="dao.dicon"/>
    <component class="example.dao.EmployeeDao">
        <aspect>dao.interceptor</aspect>
    </component>
</components>

dao.diconはS2Daoの動作を設定するファイルです。 詳細はdao.diconのリファレンスを参照してください。

SQLファイル

検索、更新処理等を行うSQL文を記述します。 Daoに定義したメソッドを呼び出すと、対応するSQLファイルに記述されているSQL文が発行されます。 作成したSQLファイルはDaoと同じ場所に配置してください。 ※S2DaoにはSQLを自動で生成する機能が用意されているので、SQLファイルがない場合、S2DaoがSQL文を自動生成します。

SQLファイル名

S2DaoにはSQLのファイル名にも命名規約があります。

  • SQLファイルとDaoに定義したメソッドの関連付け
  • 作成したSQLファイルとDaoに定義したメソッドの関連付けをするには、SQLファイルのファイル名を以下の形式にする必要があります。

    - Daoのクラス名_メソッド名.sql

    examples.dao.EmployeeDao#getAllEmployees()に対応するSQLファイルは以下のようになります。
    examples/dao/EmployeeDao_getAllEmployees.sql	
  • 複数DBMS対応
  • DBMSごとに使用するSQLファイルを指定することができます。 どのDBMSを使っているのかはjava.sql.DatabaseMetadata#getDatabaseProductName()に応じて、S2Daoが自動的に判断しています。 S2DaoのほうでDBMSごとにサフィックスを決めているので、SQLファイル名にサフィックスを追加します。 例えばオラクルの場合、サフィックスはoracleなので、「EmployeeDao_getAllEmployees_oracle.sql」というファイル名になります。

    DBMSとサフィックスの関係は以下の通りです。
    DBMS サフィックス
    Oracle oracle
    DB2 db2
    MSSQLServer mssql
    MySQL mysql
    PostgreSQL postgre
    Firebird firebird
    MaxDB maxdb
    HSQL hsql
    Derby derby
    H2 h2
SQL文の記述

SQLファイルには、”SELECT * FROM EMP”, “DELETE FROM EMP WHERE EMPNO = 7788”といった、普通のSQL文を記述することが可能です。 また、WHERE句の条件の値などを動的に変化させることも可能です。詳しくは、SQLコメントを参照して下さい。

SQL_FILEアノテーション

バージョン1.0.43よりSqlFileアノテーションが導入されました。 SQLファイルを利用するDaoのメソッドにこのアノテーションを付けると、対応するSQLファイルが見つからない場合にS2Daoが例外を投げます。 記述の不備やSQLファイルの配置し忘れなどのミスを検出するために使用してください。

例えば、DaoのgetAllEmployeesメソッドがSQLファイルを利用する場合は次のように記述します。

@SqlFile
public List<Employee> getAllEmployees();
SQLファイルのパス指定

バージョン1.0.47よりSqlFileアノテーションにSQLファイルのパスを指定できるようになりました。 これにより、Daoとは別の場所にファイルを配置することが出来るようになります。 パスは、絶対パスではなくクラスパス配下の相対パスを指定してください。

@SqlFile("resource/sqlfile/employee_all.sql")
public List getAllEmployees();

また、SQLファイルの複数DBMS対応もそのまま使えます。 例えば上の例でOracleに接続している場合、 Oracle用のSQLファイルである"resource/sqlfile/employee_all_oracle.sql"があれば、 そちらが優先的に利用されます。

SQLコメント

S2Daoでは、メソッドの引数とSQL文のバインド変数の対応付けを/**/や--などのコメントを使って行います。 コメントなので、対応付けをした後でも、SQL*PlusなどのSQLのツールでそのまま実行することができます。 最初、SQLのツールでSQL文を実行して思い通りの結果を出力するようになったら、 それに対して、コメントを埋め込んでいくと良いでしょう。

また、SQL文に対しての説明の意味でのコメントを使用したい場合は、/*の後にスペースを入れることにより、 普通のコメントを使用することが出来ます。例として、/* hoge*/となります。/*の後にスペースが入っているので、実行時には無視されます。

バインド変数コメント

Daoに定義したメソッドの引数の値をSQL文で使用する場合は、SQL文にバインド変数コメントを記述します。 バインド変数コメントの右側のリテラルに引数の値が自動的に置換され実行されます。 バインド変数コメントは、以下のように記述します。

- /*引数名*/リテラル

引数がJavaBeansの場合は以下のように記述します。

- /*引数名.プロパティ名*/リテラル

引数名はDaoに設定したARGSアノテーションの値と揃える必要があります。 (ただし引数が一つの場合は、その制約はありません。)

@Arguments("empno")
public Employee getEmployee(int empno);

Daoに上記のメソッドを定義した場合、SQLファイル(EmploeeDao_getEmployee.sql)は次のようにバインド変数を使用することが可能です。自動的にgetEmployeeメソッドの引数の値が設定されます。

SELECT * FROM emp WHERE empno = /*empno*/7788

IN句にバインド変数を適用したい場合は以下のようにすることができます。

- IN /*引数名*/(...)

IN /*names*/('aaa', 'bbb')

引数はjava.util.Listや配列の引数となります。上記のIN句の場合は、以下のように引数を用意します。

String[] names = new String[]{"SCOTT", "SMITH", "JAMES"};

String配列namesが自動的にバインド変数の部分に置換されます。

LIKEを使用する場合は、次のようにします。

ename LIKE /*ename*/'hoge'

ワイルドカードを使いたい場合は、メソッドの引数の値に埋め込みます。 「"COT"を含む」という条件を指定する場合は、以下のように引数の値にワイルドカードを埋め込みます。

employeeDao.findEmployees("%COT%");

埋め込み変数コメント

Daoに定義したメソッドの引数の値をSQL文に文字列として直接埋め込む場合は、SQL文に埋め込み変数コメントを記述します。 埋め込み変数コメントの右側のリテラルに引数の値が自動的に置換され実行されます。 埋め込み変数コメントは、以下のように記述します。

- /*$引数名*/リテラル

引数がJavaBeansの場合は以下のように記述します。

- /*$引数名.プロパティ名*/リテラル

埋め込み変数コメントを使用する場合、SQLインジェクションに対する対策はDaoの呼び出し側で行う必要があります。

IFコメント

IFコメントでは、条件に応じて実行するSQL文を変えることが可能です。IFコメントは以下の形式で記述します。

- /*IF 条件*/ .../*END*/

サンプルは以下のとおりです。

/*IF hoge != null*/hoge = /*hoge*/'abc'/*END*/

IFコメントは、条件が真の場合、/*IF*/と/*END*/に囲まれた部分が評価されます。 上記の場合、引数hogeがnull出ない場合にのみ、IFコメントで囲まれている部分(hoge = /*hoge*/'abc')が評価されます。
また偽の場合の処理としてELSEコメントというものが用意されています。 条件が偽となった場合は、”ELSE”の後に記述した部分が評価されます。ELSEコメントは以下のように記述します。

/*IF hoge != null*/hoge = /*hoge*/'abc'
  -- ELSE hoge is null
/*END*/

条件がfalseになると-- ELSEの後の部分(hoge is null)が評価されます。

BEGINコメント

BEGINコメントは、WHERE句内のすべてのELSEを含まないIFコメントがfalseになった場合に、 WHERE句自体を出力したくない場合に使います。BEGINコメントはIFコメントと併せて使用します。
BEGINコメントは以下の形式で記述します。

- /*BEGIN*/WHERE句/*END*/

サンプルは以下の通りです。
/*BEGIN*/WHERE
  /*IF job != null*/job = /*job*/'CLERK'/*END*/
  /*IF deptno != null*/AND deptno = /*deptno*/20/*END*/
/*END*/

上記の場合、job,deptnoがnullの場合は、WHERE句は出力されません。 job == null,deptno != nullの場合は、WHERE depno = ?、 job != null,deptno == nullの場合は、WHERE job = ?、 job != null,deptno != nullの場合は、WHERE job = ? AND depno = ?のようになります。動的SQLも思いのままです。

EntityManagerを使用したQueryの実行

EntityManagerを使用し、自動的に生成されるSELECT文にWHERE句やORDER BY句を追加できます。書き方は、QUERYアノテーションと同様です。 主に、動的にQueryを組み立てたいときに使用します。EntityManagerを使用するには、以下のクラスを継承します。

- org.seasar.dao.impl.AbstractDao

Daoのインターフェース名は、必ず"Dao"で終わるようにしてください。S2Daoは、AbstractDaoを継承したクラスが実装しているインターフェースの中で、 クラス名が"Dao"で終わっているインターフェースをDaoインターフェースだと判断しているためです。

EntityManagerには、以下のメソッドが用意されています。
find()メソッド
戻り値をjava.util.Listで返します。引数の種類は以下の通りです。
public List find(String query);
public List find(String query, Object arg1);
public List find(String query, Object arg1, Object arg2);
public List find(String query, Object arg1, Object arg2, Object arg3);
public List find(String query, Object[] args);

findArray()メソッド
戻り値を配列で返します。引数の種類は以下の通りです。
public Object[] findArray(String query);
public Object[] findArray(String query, Object arg1);
public Object[] findArray(String query, Object arg1, Object arg2);
public Object[] findArray(String query, Object arg1, Object arg2, Object arg3);
public Object[] findArray(String query, Object[] args);

findBean()メソッド
戻り値をJavaBeansで返します。引数の種類は以下の通りです。
public Object findBean(String query);
public Object findBean(String query, Object arg1);
public Object findBean(String query, Object arg1, Object arg2);
public Object findBean(String query, Object arg1, Object arg2, Object arg3);
public Object findBean(String query, Object[] args);

findObject()メソッド
戻り値をcount(*)の結果のような単独の値を返します。引数の種類は以下の通りです。
public Object findObject(String query);
public Object findObject(String query, Object arg1);
public Object findObject(String query, Object arg1, Object arg2);
public Object findObject(String query, Object arg1, Object arg2, Object arg3);
public Object findObject(String query, Object[] args);
引数は、QUERYアノテーションと同様に記述します。Object型の引数が4つ以上になるの場合は、Object型の配列を使用します。

AbstractDaoを継承したクラスの基本的な実装方法

  1. org.seasar.dao.impl.AbstractDaoの継承
  2. Daoをimplementsする

  3. implementsするDaoのインターフェース名の最後は"Dao"で終了している必要があります。
  4. コンストラクタの実装

  5. org.seasar.dao.DaoMetaDataFactoryを引数とし、super(org.seasar.dao.DaoMetaDataFactory)を呼び出します。
  6. Daoに定義したメソッドの実装

  7. EntityManagerで提供しているメソッドを使用する場合は、getEntityManager().find(...);のように、getEntityManager()メソッドを使用し、EntityManagerを取得し呼び出すことが出来ます。
AbstractDaoを継承したクラスのサンプルは以下の通りです。
package examples.dao;

import java.util.List;

import org.seasar.dao.DaoMetaDataFactory;
import org.seasar.dao.impl.AbstractDao;

public class Employee2DaoImpl extends AbstractDao implements Employee2Dao {

    public Employee2DaoImpl(DaoMetaDataFactory daoMetaDataFactory) {
        super(daoMetaDataFactory);
    }

    public List getEmployees(String ename) {
        return getEntityManager().find("ename LIKE ?", "%" + ename + "%");
    }
}

詳しい使用方法はEntityManagerを使用したExampleを参照して下さい。

更新SQLの自動生成

更新SQL文を自動生成させるには、メソッド名を命名規約にあわせ、JavaBeansを1つ引数に持つメソッドを定義するだけです。 SQLファイルの作成は不要です。例としてInsertの場合、命名規約に合わせ、以下のように定義します。

public int insert(Department department);

VersionNoによる排他制御

S2Daoは排他制御も自動的に行うことができます。
設定方法は、テーブルに排他制御用のカラムを用意し、JavaBeansにint型(もしくはInteger型)でversionNoと定義するだけで、versionNoによる排他制御を自動的に行ってくれます。

例えば、2人のユーザがversionNo値0の同一データを取得して更新しようとした場合、 先に更新したユーザは正常に登録することができます。そのとき自動でversionNoはインクリメントされ、DBのversionNoは1となります。 次にもう1人のユーザがデータを更新しようとすると、ユーザが保持しているversionNoの値(0)と、 実際にDB格納されているversionNoの値(1)が異なることになり、NotSingleRowUpdatedRuntimeExceptionが発生し更新失敗することになります。

VersionNoの値によりinsertされたばかりのオブジェクトをとってきたものなのか、新規にnewされた永続化前のオブジェクトなのかを判断したい場合は、 JavaBeans上でversionNoを定義する時に初期値として-1を設定してください。 そうすることによってversionNo == -1が永続化前のオブジェクト、versionNo >= 0が永続化済みのオブジェクトという風に判断できるようになります。

Timestampによる排他制御

VersionNoの他にTimestampによる排他制御もS2Daoが自動的に行うことができます。 Timestamp型でtimestampという名前のプロパティを定義するだけで、自動的に行ってくれます。 DaoからInsertする時に、new java.sql.Timestamp()をtimestampプロパティへセットしてInsert文を発行します。 更新(Update・Delete)時に、JavaBeansのtimestampプロパティ値とレコードのtimestampカラム値を比較し、 異なっている場合にはVersionNoと同様にNotSingleRowUpdatedRuntimeExceptionが投げられます。 更新時にTimestamp用のカラムの値にnullが設定されていると比較に失敗するので注意してください。

バッチ更新

更新系のメソッドで次のように引数をエンティティのクラスの配列またはListにすると自動的に更新用のSQL文を生成し、バッチ更新をすることができます。 なお、バッチ更新の場合、IDの自動生成を行うことはできません。

また、versionNotimestampの設定がしてあっても、エンティティの値は更新後のデータベースと同じ値にはなりません。 バッチ更新後は、最新のエンティティをデータベースから再取得するようにしてください。

int insertBatch(Employee[] employees)

バージョン1.0.47から以下のように戻り値の型をint[]にすることで、エンティティ毎に更新したレコード数を取得できます。 ただし、この戻り値はJDBCドライバが返す値そのものなので、ドライバによってはjava.sql.Statement#SUCCESS_NO_INFOしか取れないこともあります。

int[] insertBatch2(Employee[] employees)

検索SQLの自動生成

メソッドのsignatueより、 S2Daoに自動的にSELECT文を生成させることもできます。ARGSアノテーションにカラム名を指定することで、引数の値によってWHERE句が変わるような動的なSQL文も自動生成できます。

SELECT * FROM emp
/*BEGIN*/WHERE
  /*IF job != null*/job = /*job*/'CLERK'/*END*/
  /*IF deptno != null*/AND deptno = /*deptno*/20/*END*/
/*END*/

上記SQL文に相当するSQL文を自動生成するには以下のように定義します。上記SQLの/**/などについては、SQLコメントを参照してください。

@Arguments({"job", "deptno"})
public List getEmployeeByJobDeptno(String job, Integer deptno);

N:1でマッピングされているカラムを指定する場合には、「カラム名_関連番号」で指定します。 N:1でマッピングされているBeanは左外部結合を使って1つのSQL文で取得されます。左外部結合をサポートしていないRDBMSはSELECT文自動生成の対象外です。 オラクルのように左外部結合が標準と異なる場合も、S2DaoがRDBMSがオラクルであると自動的に判断して適切なSQL文を組み立てます。

引数にDTO(Data Transter Object)を指定することもできます。その場合、ARGSアノテーションを指定してはいけません。 S2Daoは、引数が1つで、ARGSアノテーションが指定されていない場合、引数をDTOとみなし、DTOのプロパティを使って自動的にSQL文を組み立てます。 プロパティ名とカラム名が異なる場合は、COLUMNアノテーションを使ってカラム名を指定します。N:1でマッピングされているカラムを指定する場合には、カラム名_関連番号で指定します。 テーブルに存在しないプロパティ(カラム)は自動的に無視されます。プロパティの値によって、WHERE句が変わるような動的SQL文を自動生成します。
動的SQL文の自動生成とORDER BYではじまるQUERYアノテーションは併用することが出来ます。

package examples.dao;

public class EmployeeSearchCondition {

    public static final String dname_COLUMN = "dname_0";
    private String job;
    private String dname;
    ...
}
List getEmployeesBySearchCondition(EmployeeSearchCondition dto);

また同様の指定方法で引数にEntityを使用することも出来ます。DTOの詳しい使用方法は、自動で検索用SQL文を生成する場合のExampleを参照して下さい。

FetchHandlerによる検索結果のフェッチ

バージョン1.0.48からFetchHandlerを使ったフェッチ機能に対応しました。 CSVファイル出力やバッチ処理などのように大量のデータを検索する場合に、 OutOfMemoryErrorなどのエラーが発生するのを避けることが出来ます。

フェッチ機能を使うには、まず検索用メソッドの一番最後の引数にFetchHandlerを指定します。

import org.seasar.dao.tiger.FetchHandler;

@S2Dao(bean = Employee.class)
public interface EmployeeDao {

    @Arguments("deptno")
    public int fetchEmployeeByDeptNo(int deptno, FetchHandler<Employee> fetchHandler);

}

そして、FetchHandlerを実装したクラスを用意します。無名クラスを使うことも出来ます。 FetchHandlerを実装したクラスのexecuteメソッドが、 データベースから1レコード読み込まれるたびに呼び出されます。 executeメソッドの戻り値は、フェッチを継続するかどうかを表します。例えば100レコード読み込んだ時点でフェッチを止めたい場合は、 100レコード目を処理したあとfalseを返すことで実現できます。

import org.seasar.dao.tiger.FetchHandler;

public class EmployeeFileWriter implements FetchHandler<Employee> {

    // 中略...

    public boolean execute(Employee emp) {
        Department dept = emp.getDepartment();
        StringBuilder sb = new StringBuilder(255);
        sb.append(emp.getEmpno()).append(",");
        sb.append(emp.getEname()).append(",");
        sb.append(emp.getJob()).append(",");
        sb.append(emp.getMgr()).append(",");
        sb.append(emp.getHiredate()).append(",");
        sb.append(emp.getSal()).append(",");
        sb.append(emp.getComm()).append(",");
        sb.append(dept.getDeptno()).append(",");
        sb.append(dept.getDname()).append(",");
        sb.append(dept.getLoc());
        
        // CSVファイルへ書き出す。
        printWriter.println(sb.toString());
        
        rowCount++;
        if (rowCount >= 100) {
            // 最大100件出力したら、そこでやめる。
            return false;
        }
        return true;
    }

}

実際にデータを抽出するには、このように用意したFetchHandlerを以下のようにDaoのメソッドに渡すことで実行することが出来ます。

    EmployeeFileWriter handler = new EmployeeFileWriter("example.csv");
    employeeDao.fetchEmployeeByDeptNo(20, handler);

S2Daoの実行

Daoを実行する基本的な方法は以下のようになります。

  1. 作成したdiconファイルのパスを引数にS2Containerを生成
  2. 生成したS2ContainerからgetComponentを呼び出し、登録したDaoを取得する
  3. 取得したDaoのメソッドを実行する

S2Daoではトランザクション制御は行なっていません、トランザクションについてはトランザクションの自動制御を参照して下さい。

実行クラスサンプル
package examples.dao;

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

public class EmployeeDaoClient {

    private static final String PATH = "examples/dao/EmployeeDao.dicon";

    public static void main(String[] args) {

        S2Container container = S2ContainerFactory.create(PATH); /* 手順1 */
        container.init();
        try {
            EmployeeDao dao = (EmployeeDao) container.getComponent(EmployeeDao.class);/* 手順2 */
            System.out.println(dao.getAllEmployee(7788));/* 手順3 */
        } finally {
            container.destroy();
        }
    }
}

S2JUnit4を利用したテスト

S2JUnit4は、JUnit4を拡張したテスティングフレームワークです。 S2Tigerの2.4.18以上のバージョンと組み合わせるとJUnit4で簡単にS2DaoのDaoやBeanをテストできます。

S2JUnit4を使ったテストをするにはs2junit4.diconがCLASSPATH上に存在している必要があります。 S2Dao用の設定がなされたs2junit4.diconがs2-dao-x.x.x.zipのresourcesフォルダに含まれているのでこれを利用してください。

  • s2junit4.dicon
    SMART deployを使用し app.diconが存在する場合は、app.diconを自動インクルードするように変更するとよいでしょう。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
        "http://www.seasar.org/dtd/components24.dtd">
    <components namespace="s2junit4">
        
        <component name="context" class="org.seasar.framework.unit.impl.SimpleInternalTestContext">
            <property name="jtaEnabled">true</property>
            <property name="ejb3Enabled">false</property>
        </component>
    
        <component class="org.seasar.framework.unit.impl.SimpleDataAccessor"/>
    
        <component class="org.seasar.framework.unit.impl.ConfigFileIncluderImpl">
            <initMethod name="addConfigFile">
                <arg>"dao.dicon"</arg><!-- ここをapp.diconに変更する-->
            </initMethod>
            <initMethod name="addConfigFile">
                <arg>context.testClassShortName + ".dicon"</arg>
            </initMethod>
        </component>
            
        <component class="org.seasar.framework.unit.impl.TestDataPreparerImpl">
            <initMethod name="addTestDataXlsPath">
                <arg>context.testClassShortName + "_" + context.testMethodName + ".xls"</arg>
            </initMethod>
            <initMethod name="addTestDataXlsPath">
                <arg>context.testClassShortName + ".xls"</arg>
            </initMethod>
        </component>
    
        <component class="org.seasar.framework.unit.impl.ExpectedDataReaderImpl">
            <initMethod name="addExpectedDataXlsPath">
                <arg>context.testClassShortName + "_" + context.testMethodName 
                + "_Expected" + ".xls"</arg>
            </initMethod>
        </component>
    </components>
    

    利用例

    S2Dao用のBeanやBeanのListが期待値と等しいことを表明(アサート)するためにはorg.seasar.dao.unit.S2DaoAssertを使ってください。 このクラスに定義されたメソッドはテストメソッド以外から呼び出さないでください。 S2DaoAssertはstatic import宣言をして使用すると便利です。

  • S2JUnit4を利用したテスト
  • import static org.seasar.dao.unit.S2DaoAssert.assertBeanEquals;
    
    @RunWith(Seasar2.class)
    public class EmployeeDaoTest {
    
      TestContext context;
    
      EmployeeDao dao;
    
      public void getAllEmployees() throws Exception {
        DataSet expected = context.getExpected();
        assertBeanEquals(expected, dao.getAllEmployees());
      }
    }
    
  • これまでのS2DaoTestCaseを利用したテストコード
    上記のコードはS2DaoTestCaseを利用した次のような記述に相当します。
  • public class EmployeeDaoTest extends S2DaoTestCase {
    
      EmployeeDao dao;
    
      protected void setUp() throws Exception {
        super.setUp();
        include("app.dicon");
      }
    
      public void testGetAllEmployeesTx() throws Exception {
        readXlsWriteDb("EmployeeDaoTest_getAllEmployees.xls");
        DataSet expected = readXls("EmployeeDaoTest_getAllEmployees_Expected.xls");
        assertBeanListEquals("", expected, dao.getAllEmployees());
      }
    }
    

Example

このExampleは前提条件として以下のテーブル、JavaBeans、dao.diconを使用します。

テーブル:EMP
カラム名 論理名 NotNull 主キー
EMPNO 従業員番号 NUMBER
ENAME 従業員名 VARCHAR

JOB 仕事 VARCHAR

MGR 上司 NUMBER

HIREDATE 雇用日 DATE

SAL 給料 NUMBER

COMM 手数料 NUMBER

DEPTNO 部署番号 NUMBER

TSTAMP タイムスタンプ TIMESTAMP


テーブル:DEPT
カラム名 論理名 NotNull 主キー
DEPTNO 部署番号 NUMBER
DNAME 部署名 VARCHAR

LOC ロケーション VARCHAR

VERSIONNO バージョン番号 NUMBER


EMPテーブルに関連付くJavaBeansは次の通りです。

package examples.dao;

import java.io.Serializable;
import java.sql.Timestamp;
@Bean(table="EMP")
public class Employee implements Serializable {

    private long empno;

    private String ename;

    private String job;

    private Short mgr;

    private java.util.Date hiredate;

    private Float sal;

    private Float comm;

    private int deptno;

    private Timestamp timestamp;

    private Department department;

    public Employee() {
    }

    public Employee(long empno) {
        this.empno = empno;
    }

    public long getEmpno() {
        return this.empno;
    }

    public void setEmpno(long empno) {
        this.empno = empno;
    }

    public java.lang.String getEname() {
        return this.ename;
    }

    public void setEname(java.lang.String ename) {
        this.ename = ename;
    }

    public java.lang.String getJob() {
        return this.job;
    }

    public void setJob(java.lang.String job) {
        this.job = job;
    }

    public Short getMgr() {
        return this.mgr;
    }

    public void setMgr(Short mgr) {
        this.mgr = mgr;
    }

    public java.util.Date getHiredate() {
        return this.hiredate;
    }

    public void setHiredate(java.util.Date hiredate) {
        this.hiredate = hiredate;
    }

    public Float getSal() {
        return this.sal;
    }

    public void setSal(Float sal) {
        this.sal = sal;
    }

    public Float getComm() {
        return this.comm;
    }

    public void setComm(Float comm) {
        this.comm = comm;
    }

    public int getDeptno() {
        return this.deptno;
    }

    public void setDeptno(int deptno) {
        this.deptno = deptno;
    }
	@Column("tstamp")
    public Timestamp getTimestamp() {
        return this.timestamp;
    }

    public void setTimestamp(Timestamp timestamp) {
        this.timestamp = timestamp;
    }
	@Relation(relationNo=0)
    public Department getDepartment() {
        return this.department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public boolean equals(Object other) {
        if (!(other instanceof Employee))
            return false;
        Employee castOther = (Employee) other;
        return this.getEmpno() == castOther.getEmpno();
    }

    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append(empno).append(", ");
        buf.append(ename).append(", ");
        buf.append(job).append(", ");
        buf.append(mgr).append(", ");
        buf.append(hiredate).append(", ");
        buf.append(sal).append(", ");
        buf.append(comm).append(", ");
        buf.append(deptno).append(", ");
        buf.append(timestamp).append(" {");
        buf.append(department).append("}");
        return buf.toString();
    }

    public int hashCode() {
        return (int) this.getEmpno();
    }
}

DEPTテーブルに関連付くJavaBeansは次の通りです。

package examples.dao;

import java.io.Serializable;

@Bean(table="DEPT")
public class Department implements Serializable {

    private int deptno;

    private String dname;

    private String loc;

    private int versionNo;

    public Department() {
    }

    public int getDeptno() {
        return this.deptno;
    }

    public void setDeptno(int deptno) {
        this.deptno = deptno;
    }

    public java.lang.String getDname() {
        return this.dname;
    }

    public void setDname(java.lang.String dname) {
        this.dname = dname;
    }

    public java.lang.String getLoc() {
        return this.loc;
    }

    public void setLoc(java.lang.String loc) {
        this.loc = loc;
    }

    public int getVersionNo() {
        return this.versionNo;
    }

    public void setVersionNo(int versionNo) {
        this.versionNo = versionNo;
    }

    public boolean equals(Object other) {
        if (!(other instanceof Department))
            return false;
        Department castOther = (Department) other;
        return this.getDeptno() == castOther.getDeptno();
    }

    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append(deptno).append(", ");
        buf.append(dname).append(", ");
        buf.append(loc).append(", ");
        buf.append(versionNo);
        return buf.toString();
    }

    public int hashCode() {
        return (int) this.getDeptno();
    }
}

各Exampleでincludeしているdao.diconは以下の通りです。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components namespace="dao">
    <include path="j2ee.dicon"/>
    <component
        class="org.seasar.dao.impl.DaoMetaDataFactoryImpl"/>
    <component name="interceptor"
        class="org.seasar.dao.interceptors.S2DaoInterceptor"/>
</components>

SQL文を記述する場合のExample

SQLファイルを作成し、Daoから記述したSQL文を実行する演習です。
作成するファイルは以下のとおりです。

  • Dao(EmployeeDao.java)
  • SQLファイル(EmployeeDao_getAllEmployees.sql, EmployeeDao_getEmployee.sql, EmployeeDao_getCount.sql, EmployeeDao_getEmployeeByJobDeptno.sql, EmployeeDao_update.sql)
  • diconファイル(EmployeeDao.dicon)
  • 実行クラス(EmployeeDaoClient.java)
Daoの作成
  • EMPテーブルと対応するJavaBeansと関連付けをします。
  • メソッドを定義します。
    全件検索するメソッド(getAllEmployees()メソッド)
    従業員番号を引数として、一致する従業員を検索するメソッド(getEmployee(int empno)メソッド)
    従業員をカウントするメソッド(getCount()メソッド) 仕事と部署番号を引数として、一致する従業員を検索するメソッド(getEmployeeByJobDeptno(String job, Integer deptno)メソッド)
    従業員を更新するメソッド(update(Employee employee)メソッド)
  • SQL文とメソッドの引数を関連付けするにはARGSアノテーションを使用します。
  • EMPテーブルの件数を取得するgetCount()メソッドは、1行でテーブルの件数が返ってくるので戻り値をintとします。
package examples.dao;

import java.util.List;
import org.seasar.dao.annotation.tiger.*;

@S2dao(bean=Employee.class)
public interface EmployeeDao {

    public List getAllEmployees();

	@Arguments("empno")
    public Employee getEmployee(int empno);

    public int getCount();

	@Arguments({"job", "deptno"})
    public List getEmployeeByJobDeptno(String job, Integer deptno);

    public int update(Employee employee);
}
SQLファイルの作成
  • Daoに定義したメソッドに対応するSQLファイルをそれぞれ作成します。
  • ファイル名は「クラス名_メソッド名.sql」とします。
  • 動的なSQLはSQLコメントを使用して作成します。
EmployeeDao_getAllEmployees.sql
SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE emp.deptno = dept.deptno ORDER BY emp.empno
EmployeeDao_getEmployee.sql
SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE empno = /*empno*/7788 AND emp.deptno = dept.deptno
EmployeeDao_getCount.sql
SELECT count(*) FROM emp
EmployeeDao_getEmployeeByJobDeptno.sql
SELECT * FROM emp
/*BEGIN*/WHERE
  /*IF job != null*/job = /*job*/'CLERK'/*END*/
  /*IF deptno != null*/AND deptno = /*deptno*/20/*END*/
/*END*/
EmployeeDao_update.sql
UPDATE emp SET ename = /*employee.ename*/'SCOTT'
WHERE empno = /*employee.empno*/7788
diconファイルの作成
  • dao.diconをincludeします。
  • 作成したDaoのコンポーネント定義します。
  • Daoにdao.interceptor(S2DaoInterceptor)を適用します。
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
   <include path="dao.dicon"/>
   <component class="examples.dao.EmployeeDao">
       <aspect>dao.interceptor</aspect>
   </component>
</components>
実行ファイルの作成
  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(EmployeeDao.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(EmployeeDao.class)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。
package examples.dao;

import java.util.List;

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

public class EmployeeDaoClient {

    private static final String PATH = "examples/dao/EmployeeDao.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        container.init();
        try {
            EmployeeDao dao = (EmployeeDao) container
                    .getComponent(EmployeeDao.class);
            List employees = dao.getAllEmployees();
            for (int i = 0; i < employees.size(); ++i) {
                System.out.println(employees.get(i));
            }

            Employee employee = dao.getEmployee(7788);
            System.out.println(employee);

            int count = dao.getCount();
            System.out.println("count:" + count);

            dao.getEmployeeByJobDeptno(null, null);
            dao.getEmployeeByJobDeptno("CLERK", null);
            dao.getEmployeeByJobDeptno(null, new Integer(20));
            dao.getEmployeeByJobDeptno("CLERK", new Integer(20));

            System.out.println("updatedRows:" + dao.update(employee));
        } finally {
            container.destroy();
        }

    }
}
実行結果
DEBUG 2004-10-12 11:07:01,117 [main] 物理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:01,133 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:01,914 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:07:02,742 [main] SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE emp.deptno = dept.deptno ORDER BY emp.empno
DEBUG 2004-10-12 11:07:02,758 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:02,867 [main] 論理的なコネクションを閉じました
7369, SMITH, CLERK, 7902, 1980-12-17 00:00:00.0, 800.0, null, 20, 1980-12-17 00:00:00.0
{0, RESEARCH, DALLAS, 0}
7499, ALLEN, SALESMAN, 7698, 1981-02-20 00:00:00.0, 1600.0, 300.0, 30, 1980-12-17 00:00:00.0
{0, SALES, CHICAGO, 0}
7521, WARD, SALESMAN, 7698, 1981-02-22 00:00:00.0, 1250.0, 500.0, 30, 1980-12-17 00:00:00.0
{0, SALES, CHICAGO, 0}
7566, JONES, MANAGER, 7839, 1981-04-02 00:00:00.0, 2975.0, null, 20, 1980-12-17 00:00:00.0
{0, RESEARCH, DALLAS, 0}
7654, MARTIN, SALESMAN, 7698, 1981-09-28 00:00:00.0, 1250.0, 1400.0, 30, 1980-12-17 00:00:00.0
{0, SALES, CHICAGO, 0}
7698, BLAKE, MANAGER, 7839, 1981-05-01 00:00:00.0, 2850.0, null, 30, 1980-12-17 00:00:00.0
{0, SALES, CHICAGO, 0}
7782, CLARK, MANAGER, 7839, 1981-06-09 00:00:00.0, 2450.0, null, 10, 1980-12-17 00:00:00.0
{0, ACCOUNTING, NEW YORK, 0}
7788, SCOTT, ANALYST, 7566, 1982-12-09 00:00:00.0, 3000.0, null, 20, 2004-10-12 10:15:54.914
{0, RESEARCH, DALLAS, 0}
7839, KING, PRESIDENT, null, 1981-11-17 00:00:00.0, 5000.0, null, 10, 1980-12-17 00:00:00.0
{0, ACCOUNTING, NEW YORK, 0}
7844, TURNER, SALESMAN, 7698, 1981-09-08 00:00:00.0, 1500.0, 0.0, 30, 1980-12-17 00:00:00.0
{0, SALES, CHICAGO, 0}
7876, ADAMS, CLERK, 7788, 1983-01-12 00:00:00.0, 1100.0, null, 20, 1980-12-17 00:00:00.0
{0, RESEARCH, DALLAS, 0}
7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, null, 30, 1980-12-17 00:00:00.0
{0, SALES, CHICAGO, 0}
7902, FORD, ANALYST, 7566, 1981-12-03 00:00:00.0, 3000.0, null, 20, 1980-12-17 00:00:00.0
{0, RESEARCH, DALLAS, 0}
7934, MILLER, CLERK, 7782, 1982-01-23 00:00:00.0, 1300.0, null, 10, 1980-12-17 00:00:00.0
{0, ACCOUNTING, NEW YORK, 0}
DEBUG 2004-10-12 11:07:02,883 [main] SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE empno = 7788 AND emp.deptno = dept.deptno
DEBUG 2004-10-12 11:07:02,883 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:02,914 [main] 論理的なコネクションを閉じました
7788, SCOTT, ANALYST, 7566, 1982-12-09 00:00:00.0, 3000.0, null, 20, 2004-10-12 10:15:54.914
{0, RESEARCH, DALLAS, 0}
DEBUG 2004-10-12 11:07:02,914 [main] SELECT count(*) FROM emp
DEBUG 2004-10-12 11:07:02,914 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:02,914 [main] 論理的なコネクションを閉じました
count:14
DEBUG 2004-10-12 11:07:02,929 [main] SELECT * FROM emp

DEBUG 2004-10-12 11:07:02,929 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:02,945 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:07:02,945 [main] SELECT * FROM emp
WHERE
  job = 'CLERK'


DEBUG 2004-10-12 11:07:02,945 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:02,961 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:07:02,961 [main] SELECT * FROM emp
WHERE

  deptno = 20

DEBUG 2004-10-12 11:07:02,961 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:02,961 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:07:02,961 [main] SELECT * FROM emp
WHERE
  job = 'CLERK'
  AND deptno = 20

DEBUG 2004-10-12 11:07:02,961 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:03,008 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:07:03,023 [main] UPDATE emp SET ename = 'SCOTT'
WHERE empno = 7788
DEBUG 2004-10-12 11:07:03,023 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:07:03,023 [main] 論理的なコネクションを閉じました
updatedRows:1
DEBUG 2004-10-12 11:07:03,023 [main] 物理的なコネクションを閉じました

"updatedRows"の値から更新された件数を確認することができます。
この演習は、s2dao/src/examples/dao以下に用意されています。

自動で更新用SQL文を生成する場合のExample

自動で更新処理(UPDATE, INSERT, DELETE)のSQL文の生成とVersionNoによる排他制御の演習です。SQLファイルの作成は不要です。
作成するファイルは以下のとおりです。

  • Dao(DepartmentDao.java)
  • diconファイル(DepartmentDao.dicon)
  • 実行クラス(DepartmentDaoClient.java)
Daoの作成
  • DEPTテーブルと対応するJavaBeansと関連付けをします。
  • 更新処理を行うメソッドを定義します。
    部署を追加するメソッド(insert(Department department)メソッド)
    部署を更新するメソッド(update(Department department)メソッド)
    部署を削除するメソッド(delete(Department department)メソッド)
package examples.dao;

@S2Dao(bean=Department.class)
public interface DepartmentDao {

    public void insert(Department department);

    public void update(Department department);

    public void delete(Department department);
}
diconファイルの作成
  • dao.diconをincludeします。
  • 作成したDaoのコンポーネント定義します。
  • Daoにdao.interceptor(S2DaoInterceptor)を適用します。
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
  <include path="dao.dicon"/>
  <component class="examples.dao.DepartmentDao">
    <aspect>dao.interceptor</aspect>
  </component>
</components>
実行ファイルの作成
  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(DepartmentDao.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(DepartmentDao.class)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。
package examples.dao;

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

public class DepartmentDaoClient {

    private static final String PATH = "examples/dao/DepartmentDao.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        container.init();
        try {
            DepartmentDao dao = (DepartmentDao) container
                    .getComponent(DepartmentDao.class);

            Department dept = new Department();
            dept.setDeptno(99);
            dept.setDname("foo");
            dao.insert(dept);
            dept.setDname("bar");

            System.out.println("before update versionNo:" + dept.getVersionNo());
            dao.update(dept);
            System.out.println("after update versionNo:" + dept.getVersionNo());
            dao.delete(dept);
        } finally {
            container.destroy();
        }
    }
}
実行結果
DEBUG 2004-09-09 19:22:10,588 [main] 物理的なコネクションを取得しました
DEBUG 2004-09-09 19:22:10,588 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-09 19:22:11,447 [main] 論理的なコネクションを閉じました
DEBUG 2004-09-09 19:22:11,603 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-09 19:22:11,603 [main] INSERT INTO DEPT (deptno, dname, versionNo, loc)
  VALUES(99, 'foo', 0, null)
DEBUG 2004-09-09 19:22:11,666 [main] 論理的なコネクションを閉じました
before update versionNo:0
DEBUG 2004-09-09 19:22:11,666 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-09 19:22:11,666 [main] UPDATE DEPT SET dname = 'bar',
  versionNo = versionNo + 1, loc = null   WHERE deptno = 99 AND versionNo = 0
DEBUG 2004-09-09 19:22:11,666 [main] 論理的なコネクションを閉じました
after update versionNo:1
DEBUG 2004-09-09 19:22:11,666 [main] 論理的なコネクションを取得しました
DEBUG 2004-09-09 19:22:11,666 [main] DELETE FROM DEPT WHERE deptno = 99 AND versionNo = 1
DEBUG 2004-09-09 19:22:11,681 [main] 論理的なコネクションを閉じました
DEBUG 2004-09-09 19:22:11,681 [main] 物理的なコネクションを閉じました

出力結果を見ると、自動的にSQL文が発行されていることが分かります。またJavaBeans(Department)にはint型のプロパティversionNoが定義してあるので、 自動でversionNoの値が+1され、この値によって排他制御されていることがわかります。 updateメソッドを呼ぶ以前では、versionNoの値は0ですが、updateメソッドを呼び出した後では、値が1になります。
この演習は、s2dao/src/examples/dao以下に用意されています。

自動で検索用SQL文を生成する場合のExample

自動でSELECT文の生成とTimestampによる自動排他制御を行う演習です。SQLファイルの作成は不要です。また引数にDTOを使用するメソッドも定義してみましょう。 作成するファイルは以下のとおりです。

  • Dao(EmployeeAutoDao.java)
  • DTO(EmployeeSearchCondition.java)
  • diconファイル(EmployeeAutoDao.dicon)
  • 実行クラス(EmployeeAutoDaoClient.java)
Daoの作成
  • EMPテーブルと対応するJavaBeansと関連付けをします。
  • メソッドを定義します。
    全件検索するメソッド(getAllEmployees()メソッド)
    仕事と部署番号を引数として、一致する従業員を検索するメソッド(getEmployeeByJobDeptno(String job, Integer deptno)メソッド)
    従業員番号を引数として、一致する従業員を検索するメソッド(getEmployeeByEmpno(int empno)メソッド)
    指定した給料の間に含まれる従業員を検索するメソッド(getEmployeesBySal(float minSal, float maxSal)メソッド)
    指定した部署と一致する従業員を検索するメソッド(getEmployeeByDname(String dname)メソッド)
    DTOを引数とした従業員の検索をするメソッド(getEmployeesBySearchCondition(EmployeeSearchCondition dto)メソッド)
    従業員を更新するメソッド(update(Employee employee)メソッド)
package examples.dao;

import java.util.List;

@S2Dao(Employee.class)
public interface EmployeeAutoDao {

    public List getAllEmployees();

    @Arguments({"job", "deptno"})
    public List getEmployeeByJobDeptno(String job, Integer deptno);

    @Arguments("empno")
    public Employee getEmployeeByEmpno(int empno);

    @Query("sal BETWEEN ? AND ? ORDER BY empno")
    public List getEmployeesBySal(float minSal, float maxSal);

    @Arguments("dname_0")
    public List getEmployeeByDname(String dname);

    public List getEmployeesBySearchCondition(EmployeeSearchCondition dto);

    public void update(Employee employee);
}
DTOの作成
package examples.dao;

public class EmployeeSearchCondition {

    private String job;
    private String dname;
    
    @Column("dname_0")
    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}
diconファイルの作成
  • dao.diconをincludeします。
  • 作成したDaoのコンポーネント定義します。
  • Daoにdao.interceptor(S2DaoInterceptor)を適用します。
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
  <include path="dao.dicon"/>
  <component class="examples.dao.EmployeeAutoDao">
    <aspect>dao.interceptor</aspect>
  </component>
</components>
実行ファイルの作成
  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(EmployeeAutoDao.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(EmployeeAutoDao.class)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。
package examples.dao;

import java.util.List;

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

public class EmployeeAutoDaoClient {

    private static final String PATH = "examples/dao/EmployeeAutoDao.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        container.init();
        try {
            EmployeeAutoDao dao = (EmployeeAutoDao) container
                    .getComponent(EmployeeAutoDao.class);

            dao.getEmployeeByJobDeptno(null, null);
            dao.getEmployeeByJobDeptno("CLERK", null);
            dao.getEmployeeByJobDeptno(null, new Integer(20));
            dao.getEmployeeByJobDeptno("CLERK", new Integer(20));

            List employees = dao.getEmployeesBySal(0, 1000);
            for (int i = 0; i < employees.size(); ++i) {
                System.out.println(employees.get(i));
            }

            employees = dao.getEmployeeByDname("SALES");
            for (int i = 0; i < employees.size(); ++i) {
                System.out.println(employees.get(i));
            }

            EmployeeSearchCondition dto = new EmployeeSearchCondition();
            dto.setDname("RESEARCH");
            employees = dao.getEmployeesBySearchCondition(dto);
            for (int i = 0; i < employees.size(); ++i) {
                System.out.println(employees.get(i));
            }

            Employee employee = dao.getEmployeeByEmpno(7788);
            System.out.println("before timestamp:" + employee.getTimestamp());
            dao.update(employee);
            System.out.println("after timestamp:" + employee.getTimestamp());
        } finally {
            container.destroy();
        }

    }
}
実行結果
DEBUG 2004-10-12 11:35:22,054 [main] 物理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:22,069 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:22,897 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:35:23,726 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno
DEBUG 2004-10-12 11:35:23,726 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,866 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:35:23,866 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE  EMP.job = 'CLERK'
DEBUG 2004-10-12 11:35:23,866 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,882 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:35:23,882 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE EMP.deptno = 20
DEBUG 2004-10-12 11:35:23,882 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,913 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:35:23,913 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE  EMP.job = 'CLERK' AND EMP.deptno = 20
DEBUG 2004-10-12 11:35:23,913 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,929 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-12 11:35:23,929 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE sal BETWEEN 0.0 AND 1000.0 ORDER BY empno
DEBUG 2004-10-12 11:35:23,929 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,944 [main] 論理的なコネクションを閉じました
7369, SMITH, CLERK, 7902, 1980-12-17 00:00:00.0, 800.0, null, 20, 1980-12-17 00:00:00.0
{20, RESEARCH, DALLAS, 0}
7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, null, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
DEBUG 2004-10-12 11:35:23,944 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE  department.dname = 'SALES'
DEBUG 2004-10-12 11:35:23,944 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,960 [main] 論理的なコネクションを閉じました
7499, ALLEN, SALESMAN, 7698, 1981-02-20 00:00:00.0, 1600.0, 300.0, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
7521, WARD, SALESMAN, 7698, 1981-02-22 00:00:00.0, 1250.0, 500.0, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
7654, MARTIN, SALESMAN, 7698, 1981-09-28 00:00:00.0, 1250.0, 1400.0, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
7698, BLAKE, MANAGER, 7839, 1981-05-01 00:00:00.0, 2850.0, null, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
7844, TURNER, SALESMAN, 7698, 1981-09-08 00:00:00.0, 1500.0, 0.0, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, null, 30, 1980-12-17 00:00:00.0
{30, SALES, CHICAGO, 0}
DEBUG 2004-10-12 11:35:23,960 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE  department.dname = 'RESEARCH'
DEBUG 2004-10-12 11:35:23,976 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,976 [main] 論理的なコネクションを閉じました
7369, SMITH, CLERK, 7902, 1980-12-17 00:00:00.0, 800.0, null, 20, 1980-12-17 00:00:00.0
{20, RESEARCH, DALLAS, 0}
7566, JONES, MANAGER, 7839, 1981-04-02 00:00:00.0, 2975.0, null, 20, 1980-12-17 00:00:00.0
{20, RESEARCH, DALLAS, 0}
7788, SCOTT, ANALYST, 7566, 1982-12-09 00:00:00.0, 3000.0, null, 20, 2004-10-12 10:15:54.914
{20, RESEARCH, DALLAS, 0}
7876, ADAMS, CLERK, 7788, 1983-01-12 00:00:00.0, 1100.0, null, 20, 1980-12-17 00:00:00.0
{20, RESEARCH, DALLAS, 0}
7902, FORD, ANALYST, 7566, 1981-12-03 00:00:00.0, 3000.0, null, 20, 1980-12-17 00:00:00.0
{20, RESEARCH, DALLAS, 0}
DEBUG 2004-10-12 11:35:23,976 [main] SELECT EMP.tstamp, EMP.empno, EMP.ename, EMP.job, EMP.mgr,
EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, department.dname AS dname_0, department.deptno AS deptno_0,
department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP LEFT OUTER JOIN DEPT department
ON EMP.deptno = department.deptno WHERE  EMP.empno = 7788
DEBUG 2004-10-12 11:35:23,991 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,991 [main] 論理的なコネクションを閉じました
before timestamp:2004-10-12 10:15:54.914
DEBUG 2004-10-12 11:35:23,991 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-12 11:35:23,991 [main] UPDATE EMP SET tstamp = '2004-10-12 11.35.23', ename = 'SCOTT',
job = 'ANALYST', mgr = 7566, hiredate = '1982-12-09 00.00.00', sal = 3000.0, comm = null, deptno = 20
WHERE empno = 7788 AND tstamp = '2004-10-12 10.15.54'
DEBUG 2004-10-12 11:35:24,054 [main] 論理的なコネクションを閉じました
after timestamp:2004-10-12 11:35:23.991
DEBUG 2004-10-12 11:35:24,054 [main] 物理的なコネクションを閉じました

出力されているログからSQL文が自動的に生成されていることがわかります。
また更新前と更新後ではTimestampの値が変化していることに気付くと思います。この値で排他制御を行っていることがわかります。
この演習は、s2dao/src/examples/dao以下に用意されています。

EntityManagerを使用したExample

EntityManagerを使用して、指定した文字列を名前に含む従業員を検索する演習です。
作成するファイルは以下のとおりです。

  • Dao(Employee2Dao.java)
  • AbstractDaoを継承したクラス(Employee2DaoImpl.java)
  • diconファイル(Employee2Dao.dicon)
  • 実行クラス(Employee2DaoClient.java)
Daoの作成
  • なお、インターフェース名は"Dao"で終わらす必要があります。
  • EMPテーブルと対応するJavaBeansと関連付けをします。
  • 検索処理を行うメソッドを定義します。
    従業員を検索するメソッド(getEmployees(String ename))
package examples.dao;

import java.util.List;

@S2Dao(bean=Employee.class)
public interface Employee2Dao {

    public List getEmployees(String ename);
}
AbstractDaoを継承したクラスの作成
  • org.seasar.dao.impl.AbstractDaoを継承します。
  • Employee2Daoをimplementsします。
  • getEmployeesメソッドを実装します。指定した文字列を名前に含む検索をします。
package examples.dao;

import java.util.List;

import org.seasar.dao.DaoMetaDataFactory;
import org.seasar.dao.impl.AbstractDao;

public class Employee2DaoImpl extends AbstractDao implements Employee2Dao {

    public Employee2DaoImpl(DaoMetaDataFactory daoMetaDataFactory) {
        super(daoMetaDataFactory);
    }

    public List getEmployees(String ename) {
        return getEntityManager().find("ename LIKE ?", "%" + ename + "%");
    }
}
diconファイルの作成
  • dao.diconをincludeします。
  • AbstractDaoを継承したクラスをコンポーネント定義します。
  • 登録したコンポーネントにdao.interceptor(S2DaoInterceptor)を適用します。
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <include path="dao.dicon"/>
    <component class="examples.dao.Employee2DaoImpl">
        <aspect>dao.interceptor</aspect>
    </component>
</components>
実行ファイルの作成
  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(Employee2Dao.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(Employee2Dao.class)を指定してコンポーネントを取得します。
  • "CO"を名前に含むという条件を指定します。
  • Daoに定義したメソッドを実行します。
package examples.dao;

import java.util.List;

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

public class Employee2DaoClient {

    private static final String PATH = "examples/dao/Employee2Dao.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        container.init();
        try {
            Employee2Dao dao = (Employee2Dao) container
                    .getComponent(Employee2Dao.class);
            List employees = dao.getEmployees("CO");
            for (int i = 0; i < employees.size(); ++i) {
                System.out.println(employees.get(i));
            }
        } finally {
            container.destroy();
        }

    }
}
実行結果
DEBUG 2004-10-01 10:14:39,333 [main] 物理的なコネクションを取得しました
DEBUG 2004-10-01 10:14:39,333 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-01 10:14:40,379 [main] 論理的なコネクションを閉じました
DEBUG 2004-10-01 10:14:41,254 [main] SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate,
  EMP.sal, EMP.comm, EMP.deptno, department.deptno AS deptno_0, department.dname AS dname_0,
  department.loc AS loc_0, department.versionNo AS versionNo_0 FROM EMP
  LEFT OUTER JOIN DEPT department ON EMP.deptno = department.deptno WHERE ename LIKE '%CO%'
DEBUG 2004-10-01 10:14:41,270 [main] 論理的なコネクションを取得しました
DEBUG 2004-10-01 10:14:41,426 [main] 論理的なコネクションを閉じました
7788, SCOTT, ANALYST, 7566, 1982-12-09 00:00:00.0, 3000.0, null, 20 {20, RESEARCH, DALLAS, 0}
DEBUG 2004-10-01 10:14:41,442 [main] 物理的なコネクションを閉じました

この演習は、s2dao/src/examples/dao以下に用意されています。