Seasar DI Container with AOP

S2Daoを使うと、SQLプログラマとJava開発者が完全に分業して作業できるようになります。SQL*PlusなどのSQLを扱うツールで実行して動作することを確かめたSQL文にコメントでJavaとのマッピングを書くことで、SQLのツールでもそのまま実行できるし、S2Daoで読み込んでJavaとマッピングをすることもできます。これを2Way SQLと呼んでいます。SQLのツールとJavaの開発環境の間をラウンドトリップしてSQLをチューニングすることが可能になります。SQLとJavaを1人で開発する人にとってもSQLのラウンドトリップ開発は有効でしょう。SQL文以外、Javaのロジックは一切書く必要がないので、開発効率が向上します。動的なSQL文もJavaのロジックを書かずに開発することが可能です。

最近のO/Rマッピングのフレームワークは、XML地獄といわれるほど、XMLにいろいろな情報を設定しなければなりませんが、S2Daoでは定数アノテーションという技術を使い、ソースコードにメタデータを記述するだけでよく、XMLは特に必要のないようになってます。開発者にとって易しくて優しいフレームワークになのです。

セットアップ

S2と同様にJDK1.4以上が必要です。S2DaoVx.x.x.jarを解凍してできたs2daoディレクトリをEclipseのJavaプロジェクトとして丸ごとインポートしてください。.classpathを上書きするか聞かれるので、すべてはいのボタンをクリックして、すべてを取り込みます。 これで、私と全く同じ環境になります。src/examples配下にサンプルもあります。

S2Daoとして必要なjarファイルは、s2dao/libにそろってます。簡単に機能を試すことができるように、RDBMSとしてHSQLDBを用意しています。機能を試す前にあらかじめHSQLDBを実行しておいてください。HSQLDBを実行するには、bin/runHsqldb.batをダブルクリック(Windowsの場合)します。lib/hsqldb.jarはHSQLDBを実行する上では必要ですが、本番では必要ありません。libのjarファイル(hsqldb.jar以外)とsrcのj2ee.dicon、log4j.propertiesをCLASSPATHにとおせば、S2Daoを実行できます。Eclipseにインポートして使う場合は設定は不要です。

Beanメタデータ

TABLEアノテーション

JavaBeansとテーブルとの関連付けはTABLEアノテーションを使います。例えば、EmployeeクラスがEMPテーブルにマッピングされる場合は次のように定義します。

public static final String TABLE = "EMP";

クラス名からパッケージ名を除いた名前がテーブル名と一致する場合は、TABLEアノテーションを定義する必要はありません。

COLUMNアノテーション

JavaBeansのプロパティとカラムとの関連付けは、COLUMNアノテーションを使います。 プロパティ名_COLUMNのように指定します。 例えば、employeeNoプロパティがEMPNOカラムにマッピングされる場合は次のように定義します。

public static final String employeeNo_COLUMN = "EMPNO";

プロパティ名とがカラム名が一致する場合は、COLUMNアノテーションを定義する必要はありません。テーブルに存在しないプロパティは、自動的に無視されるので、特に何か定義する必要はありません。

N:1マッピング

N:1マッピングとは、複数の従業員の行に1つの部署の行が関連付けられるような場合のマッピングです。 RELNO定数とRELKEYS定数を使います。RELNO定数は、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カラムなんだと認識されます。 例えば、departmentプロパティが関連番号0にマッピングされる場合は次のように定義します。

public static final int department_RELNO = 0;

N:1マッピングのキーはRELKEYS定数で指定します。例えば、EMPテーブルのDEPTNOカラムとDEPTテーブルのDEPTNOカラムで関連付けられている場合次のように定義します。

public static final String department_RELKEYS = "DEPTNO:DEPTNO";

コロンの手前がN側のテーブルのカラム名で、コロンの後が1側のテーブルのカラム名です。キーが複数ある場合には、カンマ(,)で区切ります。 1側のテーブルのカラム名がN側のテーブルのカラム名に等しい場合は、1側のテーブルのカラム名を省略できます。

public static final String department_RELKEYS = "DEPTNO";

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

プライマリーキー

テーブルのプライマリーキーは、テーブルの定義(JDBCのメタデータ)より自動的に取得されるので、設定する必要はありません。

Daoメタデータ

BEANアノテーション

Dao(Data Access Object)がどのJavaBeans(エンティティ)に関連付けられているのかはBEANアノテーションで指定します。 例えば、EmployeeDaoクラスがEmployeeエンティティに関連付けられる場合は次のように定義します。

public static final Class BEAN = Employee.class;

SQLファイルとメソッドの関連付け

Daoのあるメソッドがどのファイルに記述されているSQL文に関連付けられているのかは、ファイル名より自動的に判断されます。 クラス名_メソッド名.sqlがメソッドに対応する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
HSQL hsql

ARGSアノテーション

メソッドの引数名は、リフレクションで取得できないため、引数がある場合には、ARGSアノテーションを使い、メソッド名_ARGSで指定します。 例えば、public Employee getEmployee(int empno)の引数名は次のように指定します。

public static final String getEmployee_ARGS = "empno";

引数が複数ある場合には、カンマで区切ります。引数が1つの場合、ARGSアノテーションは省略できます。

バインド変数コメント

引数をSQL文で使うには、バインド変数コメント(/*引数名*/)を使います。

SELECT ... WHERE empno = /*empno*/7788

バインド変数コメントの右側のリテラルがバインド変数で置き換えられます。上記のSQL文は実行時には次のようなSQL文に置き換えられます。

SELECT ... WHERE empno = ?

バインド変数の部分には引数empnoの値が割り当てられます。引数がJavaBeansである場合、引数名.プロパティのように指定することも可能です。

UPDATE emp SET ename = /*employee.ename*/'SCOTT' WHERE empno = /*employee.empno*/7788

IN (...)の...の可変部分をjava.util.Listや配列の引数で置き換えることができます。

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

上記のように配列が用意されている場合、下記のようにしてIN句の可変部分に配列のバインド変数を割り当てられます。

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

上記のIN句は実行時には次のように解釈されバインド変数部分には、"SCOTT"、"SMITH"、"JAMES"が割り当てられます。

IN (?, ?, ?)

IFコメント

条件に応じてSQL文を変えたいときには、IFコメント(/*IF 条件*/ .../*END*/)を使います。

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

上記の場合、引数hogeがnull出ない場合にのみ、IFコメントで囲まれている部分(hoge = /*hoge*/'abc')が評価されます。 次のようにELSEコメントも使えます。

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

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

BEGINコメント

WHERE句内のすべてのELSEを含まないIFコメントがfalseになった場合に、WHERE句自体を出力したくない場合、 /*BEGIN*/WHRE句/*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も思いのままです。

メソッドの命名規則

S2DaoではメソッドのsignatureよりSQL文の中身を自動的に決定しています。そのためメソッドのsignatureはS2Daoの想定にあわせてもらう必要があります。

INSERT

メソッド名が、create,insert,addではじまる必要があります。戻り値はvoidあるいはintです。 intの場合、更新した行数がかえってきます。引数の型はエンティティの型と一致させます。

UPDATE

メソッド名が、update,modify,storeではじまる必要があります。戻り値はvoidあるいはintです。 intの場合、更新した行数がかえってきます。引数の型はエンティティの型と一致させます。

DELETE

メソッド名が、delete,removeではじまる必要があります。戻り値はvoidあるいはintです。 intの場合、更新した行数がかえってきます。引数の型はエンティティの型と一致させます。

SELECT

戻り値の型がjava.util.Listを実装している場合、SELECT文でエンティティのリストを返します。 戻り値の型がエンティティの型の場合、SELECT文でエンティティを返します。 それ以外の場合、SELECT count(*) FROM empのように1行で1のカラムの値を返すというようにS2Daoは想定します。

S2DaoTestCase

S2Daoで実行したSELECT文の結果(Bean or BeanList)を簡単にテストできるようにS2DaoTestCaseが用意されています。予想される結果は、Excelのシートに用意します。N:1のマッピングもベースとなるシートにカラム名_関連番号の名前で記述します。サンプルはgetAllEmployeesResult.xlsのようになります。S2DaoTestCase#assertEquals(String message, DataSet expected, Object actual)を使って簡単に結果を検証できます。最初の引数は省略できます。2つめのexpectedは、S2DaoTestCase#readXls(String path)の結果を指定します。3つめのactualはBeanまたはBeanのリストを指定します。

DataSet expected = readXls("getAllEmployeesResult.xls");
List actual = dao_.getAllEmployees();
assertEquals("1", expected, actual);

Example

package examples.dao;

import java.io.Serializable;

public class Employee implements Serializable {
    
    public static final String TABLE = "EMP";
    public static final int department_RELNO = 0;

    private long empno;

    private String ename;

    private String job;

    private Short mgr;

    private java.util.Date hiredate;

    private Float sal;

    private Float comm;

    private short deptno;
    
    private byte[] password;
    
    private String dummy;
    
    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 short getDeptno() {
        return this.deptno;
    }

    public void setDeptno(short deptno) {
        this.deptno = deptno;
    }
    
    public byte[] getPassword() {
        return this.password;
    }
    
    public void setPassword(byte[] password) {
        this.password = password;
    }
    
    public String getDummy() {
        return this.dummy;
    }
    
    public void setDummy(String dummy) {
        this.dummy = dummy;
    }
    
    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(department).append("}");
        return buf.toString();
    }

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

import java.io.Serializable;

public class Department implements Serializable {
    
    public static final String TABLE = "DEPT";

    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();
    }
}
package examples.dao;

import java.util.List;

public interface EmployeeDao {

    public Class BEAN = Employee.class;
    
    public List getAllEmployees();
    
    public String getEmployee_ARGS = "empno";

    public Employee getEmployee(int empno);
    
    public int getCount();
    
    public String getEmployeeByJobDeptno_ARGS = "job, deptno";
    
    public List getEmployeeByJobDeptno(String job, Integer deptno);
    
    public int update(Employee employee);
}

EmployeeDao_getAllEmployees.sql

SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE emp.deptno = dept.deptno

EmployeeDao_getCount.sql

SELECT count(*) FROM emp

EmployeeDao_getEmployee.sql

SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE empno = /*empno*/7788

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

EmployeeDao.dicon

<components>
<include path="j2ee.dicon"/>
<component class="examples.dao.EmployeeDao">
<aspect>
<component class="org.seasar.dao.interceptors.S2DaoInterceptor"/>
</aspect>
</component>
</components>
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-06-26 21:45:29,425 [main] SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE emp.deptno = dept.deptno
DEBUG 2004-06-26 21:45:29,705 [main] 物理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:29,705 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,356 [main] 論理的なコネクションを閉じました
7369, SMITH, CLERK, 7902, 1980-12-17 00:00:00.0, 800.0, 0.0, 20 {20, RESEARCH, DALLAS}
7499, ALLEN, SALESMAN, 7698, 1981-02-20 00:00:00.0, 1600.0, 300.0, 30 {30, SALES, CHICAGO}
7521, WARD, SALESMAN, 7698, 1981-02-22 00:00:00.0, 1250.0, 500.0, 30 {30, SALES, CHICAGO}
7566, JONES, MANAGER, 7839, 1981-04-02 00:00:00.0, 2975.0, 0.0, 20 {20, RESEARCH, DALLAS}
7654, MARTIN, SALESMAN, 7698, 1981-09-28 00:00:00.0, 1250.0, 1400.0, 30 {30, SALES, CHICAGO}
7698, BLAKE, MANAGER, 7839, 1981-05-01 00:00:00.0, 2850.0, 0.0, 30 {30, SALES, CHICAGO}
7782, CLARK, MANAGER, 7839, 1981-06-09 00:00:00.0, 2450.0, 0.0, 10 {10, ACCOUNTING, NEW YORK}
7788, SCOTT, ANALYST, 7566, 1982-12-09 00:00:00.0, 3000.0, 0.0, 20 {20, RESEARCH, DALLAS}
7839, KING, PRESIDENT, 0, 1981-11-17 00:00:00.0, 5000.0, 0.0, 10 {10, ACCOUNTING, NEW YORK}
7844, TURNER, SALESMAN, 7698, 1981-09-08 00:00:00.0, 1500.0, 0.0, 30 {30, SALES, CHICAGO}
7876, ADAMS, CLERK, 7788, 1983-01-12 00:00:00.0, 1100.0, 0.0, 20 {20, RESEARCH, DALLAS}
7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, 0.0, 30 {30, SALES, CHICAGO}
7902, FORD, ANALYST, 7566, 1981-12-03 00:00:00.0, 3000.0, 0.0, 20 {20, RESEARCH, DALLAS}
7934, MILLER, CLERK, 7782, 1982-01-23 00:00:00.0, 1300.0, 0.0, 10 {10, ACCOUNTING, NEW YORK}
DEBUG 2004-06-26 21:45:30,416 [main] SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
WHERE empno = 7788
DEBUG 2004-06-26 21:45:30,416 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,476 [main] 論理的なコネクションを閉じました
7788, SCOTT, ANALYST, 7566, 1982-12-09 00:00:00.0, 3000.0, 0.0, 20 {20, ACCOUNTING, NEW YORK}
DEBUG 2004-06-26 21:45:30,476 [main] SELECT count(*) FROM emp
DEBUG 2004-06-26 21:45:30,476 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,486 [main] 論理的なコネクションを閉じました
count:14
DEBUG 2004-06-26 21:45:30,516 [main] SELECT * FROM emp

DEBUG 2004-06-26 21:45:30,516 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,576 [main] 論理的なコネクションを閉じました
DEBUG 2004-06-26 21:45:30,576 [main] SELECT * FROM emp
WHERE
  job = 'CLERK'
  

DEBUG 2004-06-26 21:45:30,576 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,636 [main] 論理的なコネクションを閉じました
DEBUG 2004-06-26 21:45:30,636 [main] SELECT * FROM emp
WHERE
  
  deptno = 20

DEBUG 2004-06-26 21:45:30,636 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,646 [main] 論理的なコネクションを閉じました
DEBUG 2004-06-26 21:45:30,646 [main] SELECT * FROM emp
WHERE
  job = 'CLERK'
  AND deptno = 20

DEBUG 2004-06-26 21:45:30,646 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,656 [main] 論理的なコネクションを閉じました
DEBUG 2004-06-26 21:45:30,697 [main] UPDATE emp SET ename = 'SCOTT'
WHERE empno = 7788
DEBUG 2004-06-26 21:45:30,697 [main] 論理的なコネクションを取得しました
DEBUG 2004-06-26 21:45:30,697 [main] 論理的なコネクションを閉じました
updatedRows:1
DEBUG 2004-06-26 21:45:30,707 [main] 物理的なコネクションを閉じました

EmployeeDaoTest.java

package test.examples.dao;

import java.util.List;

import org.seasar.dao.unit.S2DaoTestCase;
import org.seasar.extension.dataset.DataSet;

import examples.dao.EmployeeDao;

public class EmployeeDaoTest extends S2DaoTestCase {

    private EmployeeDao dao_;

    public EmployeeDaoTest(String arg0) {
        super(arg0);
    }

    public static void main(String[] args) {
        junit.textui.TestRunner.run(EmployeeDaoTest.class);
    }
    
    public void setUp() {
        include("examples/dao/EmployeeDao.dicon");
    }

    public void testGetAllEmployee() throws Exception {
        DataSet expected = readXls("getAllEmployeesResult.xls");
        List actual = dao_.getAllEmployees();
        assertEquals("1", expected, actual);
    }
}

更新SQLの自動生成

メソッドのsignatueより、 S2Daoに自動的に更新用のSQL文を生成させることもできます。SELECT文の自動生成についてはで説明します。S2DaoにSQL文を自動生成させるには、メソッド名を命名規則にあわせ、Beanを1つ引数に持つメソッドを定義するだけです。SQLファイルはもちろん不要です。

VersionNoによる排他制御

VersionNoによる排他制御もS2Daoが自動的にやってくれます。int型でversionNoという名前のプロパティを定義するだけなので簡単です。

Example

DepartmentDao

package examples.dao;

public interface DepartmentDao {

    public Class BEAN = Department.class;
    
    public void insert(Department department);
    
    public void update(Department department);
    
    public void delete(Department department);
}

DepartmentDao.dicon

<components>
<include path="j2ee.dicon"/>
<component class="examples.dao.DepartmentDao">
<aspect>
<component class="org.seasar.dao.interceptors.S2DaoInterceptor"/>
</aspect>
</component>
</components>

DepartmentDaoClient

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();
        }

    }
}

実行結果

versionNoを自動的に更新していることが分かると思います。

DEBUG 2004-07-04 20:28:03,621 [main] 物理的なコネクションを取得しました
DEBUG 2004-07-04 20:28:03,621 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-04 20:28:03,982 [main] INSERT INTO DEPT(deptno, dname, versionNo, loc)
  VALUES(99, 'foo', 0, null)
DEBUG 2004-07-04 20:28:04,162 [main] 論理的なコネクションを閉じました
before update versionNo:0
DEBUG 2004-07-04 20:28:04,162 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-04 20:28:04,162 [main] UPDATE DEPT SET dname = 'bar', versionNo = versionNo + 1,
  loc = null WHERE deptno = 99 AND versionNo = 0
DEBUG 2004-07-04 20:28:04,182 [main] 論理的なコネクションを閉じました
after update versionNo:1
DEBUG 2004-07-04 20:28:04,182 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-04 20:28:04,182 [main] DELETE FROM DEPT WHERE deptno = 99 AND versionNo = 1
DEBUG 2004-07-04 20:28:04,202 [main] 論理的なコネクションを閉じました
DEBUG 2004-07-04 20:28:04,212 [main] 物理的なコネクションを閉じました

SELECT文の自動生成

メソッドのsignatueより、 S2Daoに自動的にSELECT文を生成させることもできます。ARGSアノテーションにカラム名を指定することで、引数の値によってWHERE句が変わるような動的なSQL文も自動生成できます。以前出てきたgetEmployeeByJobDeptno.sqlに相当するSQL文を自動生成させてみましょう。必要なのは次の定義だけです。

public static final String getEmployeeByJobDeptno_ARGS = "job, deptno";
public List getEmployeeByJobDeptno(String job, Integer deptno);

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

ORDERアノテーション

ORDERアノテーションを使って、自動的に生成されるSELECT分のソート順を指定できます。例えば、jobは昇順(ASC)、deptnoは降順(DESC)の場合次のように指定します。昇順の場合、ASCを省略できます。

public static final String getEmployeeByJobDeptno_ORDER = "job ASC, deptno DESC";

Example

package examples.dao;

import java.util.List;

public interface EmployeeAutoDao {

    public Class BEAN = Employee.class;
    
    public String getEmployeeByJobDeptno_ARGS = "job, deptno";
    public List getEmployeeByJobDeptno(String job, Integer deptno);
    
    public String getEmployeeByDname_ARGS = "dname_0";
    public List getEmployeeByDname(String dname);
}

EmployeeAutoDao.dicon

<components>
<include path="j2ee.dicon"/>
<component class="examples.dao.EmployeeDao">
<aspect>
<component class="org.seasar.dao.interceptors.S2DaoInterceptor"/>
</aspect>
</component>
</components>
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.getEmployeeByDname("SALES");
            for (int i = 0; i < employees.size(); ++i) {
                System.out.println(employees.get(i));
            }
        } finally {
            container.destroy();
        }

    }
}

実行結果

DEBUG 2004-07-07 20:48:08,605 [main] 物理的なコネクションを取得しました
DEBUG 2004-07-07 20:48:08,605 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-07 20:48:11,049 [main] SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate,
  EMP.sal, EMP.comm, EMP.deptno, 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
DEBUG 2004-07-07 20:48:12,050 [main] 論理的なコネクションを閉じました
DEBUG 2004-07-07 20:48:12,050 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-07 20:48:12,050 [main] SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate,
  EMP.sal, EMP.comm, EMP.deptno, 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 EMP.job = 'CLERK'
DEBUG 2004-07-07 20:48:12,210 [main] 論理的なコネクションを閉じました
DEBUG 2004-07-07 20:48:12,210 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-07 20:48:12,220 [main] SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate,
  EMP.sal, EMP.comm, EMP.deptno, 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 EMP.deptno = 20
DEBUG 2004-07-07 20:48:12,280 [main] 論理的なコネクションを閉じました
DEBUG 2004-07-07 20:48:12,280 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-07 20:48:12,290 [main] SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate,
  EMP.sal, EMP.comm, EMP.deptno, 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 EMP.job = 'CLERK' AND EMP.deptno = 20
DEBUG 2004-07-07 20:48:12,320 [main] 論理的なコネクションを閉じました
DEBUG 2004-07-07 20:48:12,320 [main] 論理的なコネクションを取得しました
DEBUG 2004-07-07 20:48:12,330 [main] SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate,
  EMP.sal, EMP.comm, EMP.deptno, 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 department.dname = 'SALES'
DEBUG 2004-07-07 20:48:12,361 [main] 論理的なコネクションを閉じました
7499, ALLEN, SALESMAN, 7698, 1981-02-20 00:00:00.0, 1600.0, 300.0, 30 {30, SALES, CHICAGO, 0}
7521, WARD, SALESMAN, 7698, 1981-02-22 00:00:00.0, 1250.0, 500.0, 30 {30, SALES, CHICAGO, 0}
7654, MARTIN, SALESMAN, 7698, 1981-09-28 00:00:00.0, 1250.0, 1400.0, 30 {30, SALES, CHICAGO, 0}
7698, BLAKE, MANAGER, 7839, 1981-05-01 00:00:00.0, 2850.0, null, 30 {30, SALES, CHICAGO, 0}
7844, TURNER, SALESMAN, 7698, 1981-09-08 00:00:00.0, 1500.0, 0.0, 30 {30, SALES, CHICAGO, 0}
7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, null, 30 {30, SALES, CHICAGO, 0}
DEBUG 2004-07-07 20:48:12,371 [main] 物理的なコネクションを閉じました