| S2Daoとは セットアップ Beanメタデータ TABLEアノテーション COLUMNアノテーション N:1マッピング プライマリーキー Daoメタデータ BEANアノテーション ARGSアノテーション QUERYアノテーション SQLファイルとメソッドの関連付け EntityManagerを使ったQueryの実行 SQLコメント バインド変数コメント IFコメント BEGINコメント 複数DBMS対応 メソッドの命名規則 更新SQLの自動生成 VersionNoによる排他制御 Timestampによる排他制御 バッチ更新 検索SQLの自動生成 S2DaoTestCase OracleのUnicode問題 SQL文を記述する場合のExample 自動で更新用SQL文を生成する場合のExample 自動で検索用SQL文を生成する場合のExample ## S2Daoとは 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は特に必要のないようになってます。 ## セットアップ Seasar2と同様にJDK1.4以上が必要です。S2DaoVx.x.x.zipを解凍してできたs2daoディレクトリをEclipseで、｢ファイル→インポート→既存のプロジェクトをワークスペースへ｣でインポートしてください。 これで、私と全く同じ環境になります。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、dao.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;
### ARGSアノテーション メソッドの引数をSQL文で参照できるように、ARGSアノテーションを使って、メソッドの引数名を指定します。メソッドの引数名は、リフレクションで取得できないためです。 例えば、public Employee getEmployee(int empno)の引数名は次のように指定します。
    public static final String getEmployee_ARGS = "empno";
引数が複数ある場合には、カンマで区切ります。 ### QUERYアノテーション QUERYアノテーションを使って、自動的に生成されるSELECT分にWHERE句やORDER BY句を追加できます。例えば、引数で給与の上限と下限を指定し、その間に含まれる従業員を抽出する場合、次のようにします。
    public static final String getEmployeesBySal_QUERY = "sal BETWEEN ? AND ? ORDER BY empno";
    ...
    public List getEmployeesBySal(Float minSal, Float maxSal);
引数の順とあわせて、バインド変数(?)をQUERYアノテーションに記述します。ARGSアノテーションは、必要ありません。ORDER BY句だけを記述するときは、ORDER BYで始めてください。 ## SQLファイルとメソッドの関連付け Daoのあるメソッドがどのファイルに記述されているSQL文に関連付けられているのかは、ファイル名より自動的に判断されます。 クラス名_メソッド名.sqlがメソッドに対応するSQLファイルになります。 例えば、examples.dao.EmployeeDao#getAllEmployees()に対応するSQLファイルは、 examples/dao/EmployeeDao_getAllEmployees.sqlになります。SQLファイルがない場合、S2DaoがSQL文を自動生成します。 ## EntityManagerを使ったQueryの実行 EntityManagerを使って、自動的に生成されるSELECT分にWHERE句やORDER BY句を追加できます。書き方は、QUERYアノテーションと同様です。主に、動的にQueryを組み立てたいときに使います。EntityManagerを使うためには、org.seasar.dao.impl.AbstractDaoを継承します。Daoのインターフェース名は、必ずDaoで終わるようにしてください。S2Daoは、AbstractDaoを継承したクラスが実装しているインターフェースの中で、クラス名がDaoで終わっているインターフェースをDaoインターフェースだと判断しているためです。 EntityManagerには、戻り値をjava.util.Listで返すfind()、配列で返すfindArray()、JavaBeansで返すfindBean()、count(*)の結果のような単独の値を返すfindObject()が用意されています。
    package examples.dao;

    import java.util.List;

    public interface Employee2Dao {

        public Class BEAN = Employee.class;

        public List getEmployees(String ename);
    }
    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 + "%");
        }
    }
    <?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>
## SQLコメント S2Daoでは、メソッドの引数とSQL文のバインド変数の対応付けを/**/や--などのコメントを使って行います。コメントなので、対応付けをした後でも、SQL*PlusなどのSQLのツールでそのまま実行することができます。最初、SQLのツールでSQL文を実行して思い通りの結果を出力するようになったら、それに対して、コメントを埋め込んでいくと良いでしょう。 ### バインド変数コメント 引数を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 (?, ?, ?)
LIKEを使う場合は、次のようにします。
    ename LIKE /*ename*/'hoge'
ワイルドカードを使いたい場合は、バインド変数の値に埋め込みます。
    employeeDao.findEmployees("%COT%");
### 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も思いのままです。 ## 複数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
## メソッドの命名規則 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 count(*) FROM empのように1行で1のカラムの値を返すというようにS2Daoは想定します。 ## 更新SQLの自動生成 メソッドのsignatueより、S2Daoに自動的に更新用のSQL文を生成させることもできます。SELECT文の自動生成については後で説明します。S2DaoにSQL文を自動生成させるには、メソッド名を命名規則にあわせ、Beanを1つ引数に持つメソッドを定義するだけです。SQLファイルはもちろん不要です。 ### VersionNoによる排他制御 VersionNoによる排他制御もS2Daoが自動的にやってくれます。int型でversionNoという名前のプロパティを定義するだけなので簡単です。 ### Timestampによる排他制御 Timestampによる排他制御もS2Daoが自動的にやってくれます。Timestamp型でtimestampという名前のプロパティを定義するだけなので簡単です。Timestamp用のカラムの値にnullが設定されていると比較に失敗するので注意しましょう。 ### バッチ更新 更新系のメソッドで次のように引数をエンティティのクラスの配列またはListにすると自動的に更新用のSQL文を生成し、バッチ更新をすることができます。
    int insertBatch(Employee[] employees)
## 検索SQL文の自動生成 メソッドの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文を組み立てます。 引数に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);
## 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);
## SQL文を記述する場合のExample
    package examples.dao;

    import java.io.Serializable;
    import java.sql.Timestamp;

    public class Employee implements Serializable {

        public static final String TABLE = "EMP";

        public static final int department_RELNO = 0;

        public static final String timestamp_COLUMN = "tstamp";

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

        public Timestamp getTimestamp() {
            return this.timestamp;
        }

        public void setTimestamp(Timestamp timestamp) {
            this.timestamp = timestamp;
        }

        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();
        }
    }
    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
### dao.dicon
    <components namespace="dao">
        <include path="j2ee.dicon"/>
        <component name="interceptor" class="org.seasar.dao.interceptors.S2DaoInterceptor"/>
    </components>
### EmployeeDao.dicon
    <components>
        <include path="dao.dicon"/>
        <component class="examples.dao.EmployeeDao">
            <aspect>dao.interceptor</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文を生成する場合の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="dao.dicon"/>
        <component class="examples.dao.DepartmentDao">
            <aspect>dao.interceptor</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] 物理的なコネクションを閉じました
## 自動で検索用SQL文を生成する場合のExample
    package examples.dao;

    public class EmployeeSearchCondition {

        public static final String dname_COLUMN = "dname_0";
        private String job;
        private String dname;

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

    import java.util.List;

    public interface EmployeeAutoDao {

        public Class BEAN = Employee.class;

        public List getAllEmployees();

        public String getEmployeeByJobDeptno_ARGS = "job, deptno";

        public List getEmployeeByJobDeptno(String job, Integer deptno);

        public String getEmployeeByEmpno_ARGS = "empno";

        public Employee getEmployeeByEmpno(int empno);

        public String getEmployeesBySal_QUERY = "sal BETWEEN ? AND ? ORDER BY empno";

        public List getEmployeesBySal(float minSal, float maxSal);

        public String getEmployeeByDname_ARGS = "dname_0";

        public List getEmployeeByDname(String dname);

        public List getEmployeesBySearchCondition(EmployeeSearchCondition dto);

        public void update(Employee employee);
    }
### EmployeeAutoDao.dicon
    <components>
        <include path="dao.dicon"/>
        <component class="examples.dao.EmployeeDao">
            <aspect>dao.interceptor</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.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-03 20:13:07,856 [main] 物理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:07,856 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,252 [main] 論理的なコネクションを閉じました
    DEBUG 2004-10-03 20:13:12,443 [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-03 20:13:12,443 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,633 [main] 論理的なコネクションを閉じました
    DEBUG 2004-10-03 20:13:12,633 [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-03 20:13:12,643 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,763 [main] 論理的なコネクションを閉じました
    DEBUG 2004-10-03 20:13:12,773 [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-03 20:13:12,773 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,823 [main] 論理的なコネクションを閉じました
    DEBUG 2004-10-03 20:13:12,823 [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-03 20:13:12,823 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,843 [main] 論理的なコネクションを閉じました
    DEBUG 2004-10-03 20:13:12,843 [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-03 20:13:12,843 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,853 [main] 論理的なコネクションを閉じました
    7369, SMITH, CLERK, 7902, 1980-12-17 00:00:00.0, 800.0, null, 20, 2000-01-01 00:00:00.0
      {20, RESEARCH, DALLAS, 0}
    7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, null, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    DEBUG 2004-10-03 20:13:12,863 [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-03 20:13:12,903 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,923 [main] 論理的なコネクションを閉じました
    7499, ALLEN, SALESMAN, 7698, 1981-02-20 00:00:00.0, 1600.0, 300.0, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    7521, WARD, SALESMAN, 7698, 1981-02-22 00:00:00.0, 1250.0, 500.0, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    7654, MARTIN, SALESMAN, 7698, 1981-09-28 00:00:00.0, 1250.0, 1400.0, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    7698, BLAKE, MANAGER, 7839, 1981-05-01 00:00:00.0, 2850.0, null, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    7844, TURNER, SALESMAN, 7698, 1981-09-08 00:00:00.0, 1500.0, 0.0, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    7900, JAMES, CLERK, 7698, 1981-12-03 00:00:00.0, 950.0, null, 30, 2000-01-01 00:00:00.0
      {30, SALES, CHICAGO, 0}
    DEBUG 2004-10-03 20:13:12,943 [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-03 20:13:12,943 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,963 [main] 論理的なコネクションを閉じました
    7369, SMITH, CLERK, 7902, 1980-12-17 00:00:00.0, 800.0, null, 20, 2000-01-01 00:00:00.0
      {20, RESEARCH, DALLAS, 0}
    7566, JONES, MANAGER, 7839, 1981-04-02 00:00:00.0, 2975.0, null, 20, 2000-01-01 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-03 19:14:55.615
      {20, RESEARCH, DALLAS, 0}
    7876, ADAMS, CLERK, 7788, 1983-01-12 00:00:00.0, 1100.0, null, 20, 2000-01-01 00:00:00.0
      {20, RESEARCH, DALLAS, 0}
    7902, FORD, ANALYST, 7566, 1981-12-03 00:00:00.0, 3000.0, null, 20, 2000-01-01 00:00:00.0
      {20, RESEARCH, DALLAS, 0}
    DEBUG 2004-10-03 20:13:12,973 [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-03 20:13:12,973 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:12,983 [main] 論理的なコネクションを閉じました
    before timestamp:2004-10-03 19:14:55.615
    DEBUG 2004-10-03 20:13:12,983 [main] 論理的なコネクションを取得しました
    DEBUG 2004-10-03 20:13:13,034 [main] UPDATE EMP SET tstamp = '2004-10-03 20.13.12',
      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-03 19.14.55'
    DEBUG 2004-10-03 20:13:13,044 [main] 論理的なコネクションを閉じました
    after timestamp:2004-10-03 20:13:12.983
    DEBUG 2004-10-03 20:13:13,044 [main] 物理的なコネクションを閉じました
| © Copyright The Seasar Project and the others 2004, all rights reserved.