Seasar DI Container with AOP

S2Dao指南

安装

与Seasar2同样,S2Dao需要JDK1.4以上的系统环境。将s2-dao-x.x.x.zip解压缩后存入到某个目录下,启动Eclipse,选择「文件→导入→现有项目到工作区」操作,将S2Dao引入到Eclipse里。必要的Seasar2版本,请参阅Wiki的说明。 在s2-dao-examples/src/main/java目录下有一些例子。

S2Dao必需的jar文件,1.0.41版本以后全部存放在lib目录下。在此之前的版本,则需要Seasar2的所有必须的jar文件和Seasar2本体(s2-framework/s2-extension)。为了能够简单的体验数据库功能,提供了HSQLDB作为RDBMS。 lib/hsqldb.jar在运行HSQLDB时是必须的,但是在实际环境中则不需要。 lib/s2-framework-2.3.xx-sources.jar和s2-extension-2.3.xx-sources.jar,可以用于在Eclipse上查看源程序,在S2Dao的运行中并不是必须的库文件。 只要CLASSPATH里包含有lib目录下的jar文件(hsqldb.jar除外)和src目录下的j2ee.dicon(使用Seasar2.4的话则是jdbc.dicon),S2Dao就能够运行。如果是导入到Eclipse的场合则不需要任何设定。

dao.dicon

dao.dicon是S2Dao的设定文件。想要更改设定的时候请参阅 dao.dicon指南

j2ee.dicon

使用S2Pager功能的时候,必须修改j2ee.dicon的设定。(v1.0.36以后缺省设定为有效配置)。 具体的设定内容请参阅S2Pager的资料

需要作成的文件

使用S2Dao机能的时候,必须作成JavaBeans,Dao(.java),dicon文件,SQL文件(.sql)。
各文件之间的关系如下图所示。

JavaBeans和表,Dao和JavaBeans,dicon文件和Dao,SQL文件和Dao相互之间是有关联的。
各文件的实装以及设定方法的详细情况如下。

JavaBeans

JavaBeans用来和表进行关联。 为了将JavaBeans和表进行关联,需要进行以下的常量声明和方法的实装。

JavaBeans的构成和说明中使用的表如下所示。

表:EMP
列名 类型 NotNull 主键(key)
EMPNO NUMBER
ENAME VARCHAR

DEPTNUM NUMBER


表:DEPT
列名 类型 NotNull 主键(key)
DEPTNO NUMBER
DNAME VARCHAR

TABLE注释

要和表进行关联,使用TABLE注释。 TABLE注释使用以下的形式进行常量声明。

- public static final String TABLE = “表名”;

EMP表的声明为如下形式。

public static final String TABLE = "EMP";

这也可以用来定义schema。schema名为"SCOTT"的场合,声明如下所示。

public static final String TABLE = "SCOTT.EMP";
※如果从类名中除去包名得到的名字和表名一致的话,则没必要定义TABLE注释。
还有,使用dao.dicon来指定org.seasar.dao.impl.DecamelizeTableNaming的场合,从类名中除去包名的名字,如果是以大写字母定义并且单词之间使用_为分隔符,其与表名一致的话,也不必定义TABLE注释(1.0.44以后)。
COLUMN注释

要和表的列项进行关联,使用COLUMN注释。
COLUMN注释使用以下的形式进行常量声明。

- public static final String 属性名_COLUMN = "列名";

employeeNo属性和EMPNO列名进行关联的场合,声明如下所示。
public static final String employeeNo_COLUMN = "EMPNO";
※从列名中除去_(下划线)之后的名字和属性名一致的话,则不必定义COLUMN注释。 表中不存在的属性会自动被忽略,无需做任何定义。
N:1映射

N:1映射指的是多条雇员纪录与一个部署纪录进行关联时的映射。
使用N:1映射的时候,必须各自声明RELNO常量和RELKEYS常量。
RELNO常量的声明如下所示。

- public static final int 属性名_RELNO = 数值;

RELNO常量是N:1映射的序列号。
例如,假定AAA表和BBB表,CCC表之间具有N:1映射关系,那么BBB的序列号为0,CCC的序列号为1。
在判断结果集合中的列名属于哪一个表时将会用到RELNO。
例如,像SELECT ..., BBB.HOGE AS HOGE_0, ... FROM AAA, BBB ...之类的SELECT指令中,HOGE_0指的就是BBB表中的HOGE列项。
RELKEYS常量的声明如下所示。

- public static final String 属性名_RELKEYS = "N侧的表之列名: 1侧的表之列名";

N:1映射的键由RELKEYS常量来指定。 如果有复数个键的场合,用逗号( , )进行分隔。例如,mykey1:yourkey1, mykey2:yourkey2,像这样来表示。
EMP表的DEPTNUM列项とDEPT表的DEPTNO列项之间进行关联时如下所示。

public static final int department_RELNO = 0;
public static final String department_RELKEYS = "DEPTNUM:DEPTNO";

1侧的表之列名和N侧的表之列名相等的场合,1侧的表之列名可以省略。 这种场合的定义如下所示。

public static final String department_RELKEYS = "DEPTNO";

还有,如果1侧的表的列名和N侧的表的列名相等,而且1侧的表的列名是主键(primaryKey)的场合,RELKEYS常量的定义可以省略。

ID的自动生成

使用ID注释,可以由RDBMS自动生成ID(主键),自动生成的主键值被自动地设定到Bean里。 从1.0.47版本开始可以对复数项属性名指定ID注释。 复数指定的场合,Bean可以被视为与拥有复合主键的表相关联。

ID注释的语法定义为,属性名_ID = "identity"这样的形式。

public static final String id_ID = "identity";

也可以使用SEQUENCE,请将myseq的部分,换成实际使用的SEQUENCE。

public static final String id_ID = "sequence, sequenceName=myseq";

使用SEQUENCE的场合,1.0.47版本以后还可以指定allocationSize参数。 一旦指定了allocationSize参数,访问SEQUENCE的回数将会减少,由此提高了INSERT的性能。 对allocationSize参数进行赋值时,所指定的数值,必须小于或等于SEQUENCE的增量值。

public static final String id_ID = "sequence, sequenceName=myseq, allocationSize=10";

手动设定ID值的场合,不必指定任何值。表的主键信息,可以自动从表的定义(JDBC的元数据metadata)得到。另外,还可以显式(explicitly)赋值为assigned。

public static final String id_ID = "assigned";

使用不同的RDBMS时,可以相应地切换所用到的ID注释(1.0.41版本以后)。比如,Identity只用于与MySQL对应,SEQUENCE只用于与Oracle对应。

public static final String id_mysql_ID = "identity";
public static final String id_oracle_ID = "sequence, sequenceName=myseq";

指定的RDB以外的缺省值也能被指定。(1.0.41版本以后)

public static final String id_mysql_ID = "identity";
public static final String id_ID = "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
Sybase(1.0.43版本以后) sybase ×
其他的DB 无后缀 × ×
对CLOB类型的映射

使用VALUE_TYPE注释,可以将JavaBeans的String类型的属性和表的CLOB类型进行关联。 以下是将"aaa"属性和CLOB类型进行关联的例子。

// JavaBeans
public static String aaa_VALUE_TYPE = "stringClobType";
private String aaa;
public String getAaa() {
    return aaa;
}
public void setAaa(String aaa) {
    this.aaa = aaa;
}
<!-- xxx.dicon -->
<component name="stringClobType" class="org.seasar.extension.jdbc.types.StringClobType" />
dicon指定的"StringClobType.class",是Java和RDBMS之间进行类型变换用的类(class)。 在VALUE_TYPE注释里,记述dicon中设定的组件(component)名。
非持久化列项(column)

从表的定义(JDBC的元数据)可以自动得到某列项是否是持久化的对象的信息。另外,也可以用NO_PERSISTENT_PROPS进行显式(explicitly)赋值,指定非持久化的列项。如果NO_PERSISTENT_PROPS参数被赋值为空文字,那么不需使用JDBC的元数据,所有的属性都被视为持久化的对象。

public static final String NO_PERSISTENT_PROPS = "dummy1, dummy2";
VERSION_NO_PROPERTY注释

VERSION_NO_PROPERTY用于将缺省的versionNo改变成使用versionNo进行排他控制的属性名。如下所示。

public static final String VERSION_NO_PROPERTY = "myVersionNo";
TIMESTAMP_PROPERTY注释

TIMESTAMP_PROPERTY用于将缺省的timestamp值改变成使用timestamp进行排他控制的属性名。如下所示。

public static final String TIMESTAMP_PROPERTY = "myTimestamp";
与列项(column)相对应的实例(instance)变量声明
列项的值用Bean的实例变量表示。实例变量的声明方式有两种。
作为JavaBeans的属性的声明方式

此方式要求声明与表的列项对应的实例变量以及访问方法(access method)。 访问方法的名字要遵循JavaBeans的命名规则,形式如下。

getter method

- public 类型 get属性名()

setter method

- public void set属性名(参数)

NUMBER类型的EMPNO列项的场合,属性和访问方法(access method)的定义如下所示。(相关阅读:COLUMN注释)
private long empno;

public long getEmpno() {
    return empno;
}

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

※列项允许为Null的场合,如果变量是基本类型(primitive),那么当列项为Null的时候,返回值为0。如果想得到Null,请使用包装类(假如是int,那么就用java.lang.Integer类)。

与以上定义的EMP表相关联的Bean如下所示。

public class Employee {
    public static final String TABLE = "EMP";

    public static final int department_RELNO = 0;

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

    private long empno;

    private String ename;

    private Short deptnum;

    private Department department;

    public Employee() {
    }

    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字段来声明。 因为此方式不需要访问方法(access method),Bean的记述变得相当简单。

使用这种方式的场合,与EMP表进行关联的Bean如下所示。

public class Employee {
    public static final String TABLE = "EMP";

    public static final int department_RELNO = 0;

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

    public long empno;

    public String ename;

    public Short deptnum;

    public Department department;
}

Dao(Data Access Object)

Dao作为接口而作成。Dao本来的目的,就是通过把持久化的数据和处理逻辑相分离,来维持Bean的持久化。 Dao和JavaBeans的关系是1:1的关系,也即,有一个JavaBeans,就要作成一个Dao。 通过调用Dao的方法(method),来执行与方法(method)相对应的SQL文件中的SQL指令。 在作成Dao的时候,必须注意以下几点。

还有,SQL指令中用到的方法(method)的参数,WHERE语句,ORDER语句的追加,更新指令中包含的或者是没有包含的属性被指定的场合,使用以下的注释。

方法(method)的命名规约

Dao的方法(method)名必须以下表中的列出的单词开头。 如果想要更改命名规则,请参阅dao.dicon指南中的「DaoNamingConventionImpl」章节。

处理 名称
插入 insert,add,create
更新 update,modify,store
删除 delete,remove
检索 以上各单词之外
BEAN注释

BEAN注释用来指定Dao和哪一个JavaBeans(实体Entity)相关联。 1.0.46版本以后,如果Dao里仅仅定义了PROCEDURE注释,或者是利用SQL文件的方法(method),不需指定BEAN注释。
BEAN注释以下列形式来声明常量。

- public static final Class BEAN = JavaBeans名.class;

EmployeeDao类和Employee实体(Entity)进行关联的场合,定义如下。

public static final Class BEAN = Employee.class;
ARGS注释

使用ARGS注释指定方法(method)的参数名,这样就可以在SQL指令中引用方法(method)的参数。因为方法(method)的参数名,不能从反射(reflection)中得到。
ARGS注释以下列形式来声明常量。

- public static final String 方法名_ARGS = "参数名";

public Employee getEmployee(int empno) 方法在Dao中被定义的场合,参数名的定义如下。

public static final String getEmployee_ARGS = "empno";

方法(method)的参数与表的列项名称相对应的场合,在参数名中指定表的列项名称。例如,方法(method)的参数名是empno,表的列项名称是employeeno的场合,就指定为employeeno。 如果是有复数个参数的场合,则用逗号分隔。只有一个参数的场合,ARGS注释可以省略。

QUERY注释

使用QUERY注释,可以在自动生成的SELECT指令或者DELETE指令(DELETE指令的生成要在1.0.44版本以后)之后,追加WHERE语句或者ORDER BY语句。
QUERY注释以下列形式来声明常量。

- public static final String 方法名_QUERY = "WHERE语句ORDER BY语句";

用参数设定工资的上限和下限,从中抽出符合该条件的公司员工名单的例子如下所示。

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

public List getEmployeesBySal(Float minSal, Float maxSal);

上例中的“?”被称为绑定(bind)参数。根据QUERY注释中的记述,方法中的参数值将按顺序被代入到绑定(bind)参数”?”的部分。 在这里不需要ARGS注释。如果只记述ORDER BY语句的话,请用ORDER BY语句开始。还可以使用SQL注解(comment)。 使用SQL注解(comment)的例子如下所示。

public static final String getEmployees_QUERY =
                      "job = /*job*/'CLERK'/*IF deptno != null*/ AND deptno = /*deptno*/20/*END*/";

上面的例子中,追加了参数值deptno不为Null,并且deptno等于参数值的条件。有关SQL注解(comment)的详细说明,请参阅SQL注解(comment)项

方法的定义

虽然通过调用Dao里定义的方法(method),可以执行相应的SQL文件中记述的SQL指令,但是在更新处理和检索处理中需要遵循各自的方法命名规约。 在S2Dao里,根据方法的命名规约将自动决定SQL指令的内容。 还有,S2Dao不支持定义方法的重载(Overload)。

  • INSERT处理
  • 进行INSERT处理的方法名,必须以insert,add,create开头。 返回值可以指定为void或者int。 int的场合,返回值为更新的行数。参数类型与实体(Entity)的类型要一致。
    方法的定义例如下所示。

    public void insert(Department department);
    public int addDept(Department department);
    public void createDept(Department department);
    
  • UPDATE处理
  • 进行UPDATE处理的方法名,必须以update,modify,store开头。 返回值可以指定为void或者int。int的场合,返回值为更新的行数。参数类型与实体(Entity)的类型要一致。
    方法的定义例如下所示。

    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方法(method)的实装,有以下两种方式。

    • 从Dao的检索方法(method)得到Bean
      在S2Dao里,对于返回值的Bean,使用AOP自动进行方法(method)的实装。 要使用这一方法,必须将dao.dicon里面记述的org.seasar.dao.impl.NullBeanEnhancer,换成org.seasar.dao.impl.BeanEnhancerImpl。 改造方法,请参阅dao.dicon指南的「UPDATE处理时、只把已被修改的属性值当作更新对象」章节。

      不过使用这一方法的场合,从Dao得到的Bean的实例(instance)能够序列化(serialize),但是不能反序列化(deserialize)。 例如,在Teeda Extension里面,有一个对对象(object)进行序列化/反序列化处理的ItemsSave功能,但是对于已经被扩展(enhance)了的Bean的实体,则不能使用此功能。 要对Bean进行序列化/反序列化处理的场合,就要考虑不使用BeanEnhancerImpl这一功能。
    • 在Bean里直接实装getModifiedPropertyNames方法(method)
      这种场合下的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) {
              this.modifiedPropertySet.add("empno");
              this.empno = empno;
          }
      
          (略)
      
          public java.util.Set getModifiedPropertyNames() {
              return this.modifiedPropertySet;
          }
      }
      				

    ModifiedOnly可以根据dao.dicon的设定来改变。 要想更改设定,请参阅dao.dicon指南的「DaoNamingConventionImpl」章节。
    方法的定义例如下所示。

    public int updateModifiedOnly(Emp emp);
    

  • DELETE处理
  • 进行DELETE处理的方法名,必须以delete,remove开头。 返回值可以为void或者int。 int的场合,返回值为更新的行数。参数类型与实体(Entity)的类型要一致。
    方法的定义例如下所示。

    public void delete(Department department);
    public int removeDept(Department department);
    
  • 检索(SELECT)处理
  • 进行检索处理的场合,要指定返回值的类型。返回值的类型是java.util.List的实装的场合,SELECT指令将返回实体(Entity)的列表(List)。返回值是实体(Entity)型的数组(array)的场合,将返回实体(Entity)数组(array)。返回值的类型是实体(Entity)的场合,将返回实体(Entity)。

    public List selectList(int deptno);
    public Department[] selectArray(int deptno);
    

    从v1.0.43开始,除了实体(Entity)以外,还可以利用DTO或者Map作为检索处理的返回值。 返回值为DTO类型的列表(List<Dto>)的场合,将返回DTO的列表(List)。 返回值为DTO类型的数组(Dto[])的场合,将返回DTO的数组(array)。 返回值为Map类型的列表(List<Map>)的场合,将返回Map的列表(List)。 返回值为Map类型的数组(Map[])的场合,将返回Map的数组(array)。

    public List<EmpDto> selectAsDtoList(int deptno);
    public EmpDto[] selectAsDtoArray(int deptno);
    public List<Map> selectAsMapList(int deptno);
    public Map[] selectAsMapArray(int deptno);
    

    除此以外的场合,S2Dao还想定了这样一种情况,也即,像SELECT count(*) FROM emp这样的,返回值为一个列项的指令的情况。

    public int selectCountAll();
    
NO_PERSISTENT_PROPS注释

有时候在更新的时候,会发生不希望在SQL指令中包含某个属性的情况。 在这种场合,就可以使用NO_PERSISTENT_PROPS注释。

public static final String insert_NO_PERSISTENT_PROPS = "sal, comm";

像上面这样指定的话,在insert方法(method)中,sal和comm属性就不能成为持久化的对象。

PERSISTENT_PROPS注释

有时候在更新的时候,会发生希望在SQL指令中只包含某个属性的情况。 在这种场合,就可以使用PERSISTENT_PROPS注释。

public static final String insert_PERSISTENT_PROPS = "deptno";

像上面这样指定的话,在insert方法(method)中,PERSISTENT_PROPS注释所指定的属性,将和主键(primary key),versionNo,timestamp的属性一起成为持久化的对象。

SQL注释

从1.0.28版本开始,可以使用SQL注释。此功能和SQL文件同样,在注释(annotation)中能使用SQL指令和SQL注解(comment)。

SQL注释里有命名规约。

  • 在SQL文件和Dao里定义的方法(method)之间的关联
  • 要将SQL注释和Dao里定义的方法(method)进行关联,必须使用如下形式的SQL注释。

    - 方法名_SQL

    与examples.dao.EmployeeDao#getAllEmployees()方法相对应的SQL注释如下所示。
    public static final String getAllEmployees_SQL = "SELECT
    emp.*, dept.dname dname_0, dept.loc loc_0 FROM emp, dept
     WHERE emp.deptno = dept.deptno ORDER BY emp.empno;";
  • 复数DBMS的对应
  • 可以指定不同的SQL注释来对应不同的DBMS。 根据java.sql.DatabaseMetadata#getDatabaseProductName(),S2Dao可以自动判断应该使用哪一个DBMS。 在S2Dao里,与各DBMS相对应,有不同的后缀,可以在SQL注释中追加后缀。 例如,Oracle的场合,因为后缀是oracle,所以有「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
    Sybase(1.0.43以后) sybase
PROCEDURE注释

从1.0.31版本开始,使用PROCEDURE注释可以执行StoredProcedure或者StoredFunction(1.0.47版本以后,推荐使用PROCEDURE_CALL注释)。PROCEDURE注释可以用以下的任意一种形式指定。

- public static final String 方法名_PROCEDURE = "catalog名.schema名.procedure名";

- public static final String 方法名_PROCEDURE = "schema名.procedure名";

- public static final String 方法名_PROCEDURE = "procedure名";

- public static final String 方法名_PROCEDURE = "package名.procedure名";(Oracle/1.0.41以后)

- public static final String 方法名_PROCEDURE = "schema名.package名.procedure名";(Oracle/1.0.41以后)

  • 可支持的StoredProcedure的范围
  • PROCEDURE注释支持以下的StoredProcedure

    • 有返回值的StoredProcedure
    • 有复数个IN,OUT,INOUT参数的StoredProcedure
    • 返回ResultSet的StoredProcedure
    不过,有复数OUT或者INOUT参数的场合,方法(method)的返回值必须是Map类型。

    还有,根据DBMS或者JDBC驱动(driver)的实装情况,有可能发生不能利用的情况。

    DBMS 限制事项
    Oracle 所有的类型(pattern)均可利用。
    DB2 -
    MSSQLServer -
    MySQL 不支持StoredFunction
    PostgreSQL 不支持有2个以上的OUT或者INOUT参数的StoredFunction
    Firebird -
    HSQLDB 不支持
    Derby 不支持StoredFunction
PROCEDURE_CALL注释

从1.0.47版本开始导入此功能。这一注释可以解决在使用PROCEDURE注释调用PROCEDURE时产生的问题。

使用PROCEDURE注释调用PROCEDURE时产生的问题如下所示。

  • 由于是基于数据库的元数据(metadata)来执行Procedure,所以如果RDBMS不返回正确的元数据(metadata)就不能调用。
  • 像OUT参数或者是INOUT参数等,如果执行PROCEDURE之后,有复数个返回值存在的话,这些值被存贮到Map中从而失去了类型信息。取出时必须进行显式的cast变换处理。

使用PROCEDURE_CALL注释调用PROCEDURE时对以上问题,有如下的对应处理。

  • 不使用数据库的元数据(metadata)。使用PROCEDURE名和参数类型信息来调用PROCEDURE。基本上只要RDBMS允许进行CallableStatement调用就可以执行。
  • 交给PROCEDURE的参数,无论是IN参数还是OUT参数,全部记录在DTO的字段里。返回值也被存贮到DTO的字段里,所以不会失去类型信息。

PROCEDURE_CALL注释的语法定义如下所示。

- public static final String 方法名_PROCEDURE_CALL = "Procedure名";

public static String hoge_PROCEDURE_CALL = "hogeProcedure";
public static String foo_PROCEDURE_CALL = "fooProcedure";

public Map hoge();
public void foo(FooDto dto);

方法的参数必须为空或者是一个「DTO」类型。Procedure返回ResultSet的场合,返回值可以是DTO类型或者是DTO的List,Map类型。 不返回ResultSet的场合请设定为void类型。 在参数的DTO里,必须指定PROCEDURE_PARAMETER注释以便表示与Procedure的参数相对应的字段和参数类型。

PROCEDURE_PARAMETER注释

从1.0.47版本开始导入此功能。这一注释做为PROCEDURE_CALL注释所指定的方法(method)参数,在被用到的DTO里指定。PROCEDURE_PARAMETER注释的语法定义如下所示。

- public static final String 字段名_PROCEDURE_PARAMETER = "Procedure的参数类型名";

public static class FooDto {
  public static String aaa_PROCEDURE_PARAMETER = "return";
  public static String bbb_PROCEDURE_PARAMETER = "in";
  public static String ccc_PROCEDURE_PARAMETER = "inout";

  private double aaa;
  private double bbb;
  private double ccc;
  //...
}

PROCEDURE_PARAMETER的类型名可以指定的值只有returninoutinout几种。 此注释的字段数和定义顺序必须和Procedure的参数顺序一致。

CHECK_SINGLE_ROW_UPDATE注释

从1.0.47版本开始使用CHECK_SINGLE_ROW_UPDATE注释。在自动生成的SQL更新指令执行后,可以使用这一注释来选择是否要检查更新行数。 CHECK_SINGLE_ROW_UPDATE注释如果定义为false,那么即使是没有任何更新,也不会发生NotSingleRowUpdateRuntimeException例外。 CHECK_SINGLE_ROW_UPDATE注释可以在Dao全体范围内或者是方法(method)为单位的范围内指定。

// 对Dao全体范围内的更新行数的检查设定为off的例子

public interface HogeDao {

    public static final boolean CHECK_SINGLE_ROW_UPDATE = false;
    // ...
public interface FugaDao {

    // 以方法(method)为单位的范围内更新行数的检查设定为off的例子
    
    public static final boolean insert_CHECK_SINGLE_ROW_UPDATE = false;
    insert(Fuga fuga);
    // ...

dicon文件

dicon文件把Dao作为组件(compenent)登记到容器(container)中。要使用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)登记为组件(compenent)的例子。

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文件里记述SQL检索,更新指令。 一旦调用Dao里定义的方法(method),就可以执行对应的SQL文件中记述的SQL指令。 请将作成的SQL文件与Dao放在同一个目录中。 ※S2Dao具有自动生成SQL指令的功能,没有SQL文件的场合,S2Dao可以自动生成SQL指令。

SQL文件名

在S2Dao里,SQL文件名也有命名规约。

  • SQL文件和Dao里定义的方法(method)之间的关联
  • 要将SQL文件和Dao里定义的方法(method)进行关联,SQL文件的文件名必须是如下形式。

    - Daoの类名_方法名.sql

    与examples.dao.EmployeeDao#getAllEmployees()相对应的SQL文件如下所示。
    examples/dao/EmployeeDao_getAllEmployees.sql	
  • 复数DBMS的对应
  • 可以指定不同的SQL文件来对应不同的DBMS。 根据java.sql.DatabaseMetadata#getDatabaseProductName(),S2Dao可以自动判断应该使用哪一个DBMS。 在S2Dao里,与各DBMS相对应,有不同的后缀,可以在SQL文件中追加后缀。 例如,Oracle的场合,因为后缀是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
    Sybase(1.0.43以后) sybase
SQL指令的记述

在SQL文件中,可以记述像”SELECT * FROM EMP”, “DELETE FROM EMP WHERE EMPNO = 7788”这样的普通的SQL指令。 另外,也可以使WHERE语句中条件的数值发生变动。详情请参阅SQL注解(comment)

SQL_FILE注释

从1.0.43版本开始导入SQL_FILE注释。 对于利用SQL文件的Dao的方法(method),如果使用SQL_FILE注释的话,找不到相应的SQL文件的场合, S2Dao会产生例外。 SQL_FILE注释可用于检测出纪录不完备或者是忘记放置SQL文件等错误。

SQL_FILE注释的语法如下所示。

- public static final String 方法名_SQL_FILE = null;

例如,Dao的getAllEmployees方法在利用SQL文件的场合如下所示。

public String getAllEmployees_SQL_FILE = null;
public List getAllEmployees();
SQL文件的路径指定

从1.0.47版本开始,在SQL_FILE注释里可以指定SQL文件的路径。 因此,可以将SQL文件配置在与Dao不同的目录下。 这里所说的路径,并非绝对路径,而是指classpath下的相对路径。

public String getAllEmployees_SQL_FILE = "resource/sqlfile/employee_all.sql";
public List getAllEmployees();

另外,与复数个DBMS对应的SQL文件也无需更改就可使用。 例如上面的例子中,与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アノテーションの値と揃える必要があります。 (ただし引数が一つの場合は、その制約はありません。)

public String getEmployee_ARGS = "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コメントを参照してください。

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

また同様の指定方法で引数にEntityを使用することも出来ます。DTOの詳しい使用方法は、自動で検索用SQL文を生成する場合のExampleを参照して下さい。
更に、v1.0.37より、同様の指定方法で、引数にBEANアノテーションと互換性のある型を使用する事も出来ます。その場合、BEANアノテーションに定義された型がDTOとして使用されます。 例えば、以下の様なDao定義を行う事が出来ます。

public class Employee implements Entity {
    private long empno;
    private String ename;
    ...
}
public interface GenericDao {
    Object select(Entity entity);
    List selectList(Entity entity);
}
public interface EmployeeDao extends GenericDao {
    Class BEAN = Employee.class;
}

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

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;

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

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

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

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

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

public interface DepartmentDao {

    public Class BEAN = Department.class;

    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;

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);
}
DTOの作成
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;
    }
}
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;

public interface Employee2Dao {

    public Class BEAN = Employee.class;

    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以下に用意されています。