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这样的指令,返回值为1行只有一个列项值的情况。

    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的类型名可以指定的值只有return、in、out、inout几种。 此注释的字段数和定义顺序必须和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注解(comment)

S2Dao使用/**/或者--注解将方法(method)的参数和SQL指令的绑定变量进行关联。 因为只是追加注解,所以即使是对SQL指令设定了关联之后,使用SQL*Plus等SQL工具依然可以直接执行这些SQL指令。 先用SQL工具执行SQL指令,得到预想的结果之后再追加注解(comment)是一个好办法。

另外,如果只想对SQl指令使用具有说明性质的注解,可以在/*之后输入空格,这样就变成了一般的注解。 例如,像/* hoge*/这样的写法,/*之后是一个空格,那么执行SQL指令时hoge就会被忽视掉。

绑定变量注解(Bind variable comment)

在SQL指令中使用Dao中定义的方法的参数值的场合,可以在SQL指令中指定绑定变量注解。 执行时,方法的参数值会自动地被置换成绑定变量注解右边的固定值。 绑定变量注解的记法如下所示。

- /*参数名*/固定值

参数为JavaBean的场合,记法如下所示。

- /*参数名.属性名*/固定值

参数名必须和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'

使用通配符(wild card)的场合,将通配符嵌入到方法的参数值里。 例如,指定条件为「含有"COT"」的场合,象下面这样在参数值里嵌入通配符。

employeeDao.findEmployees("%COT%");

嵌入变量注解(Embedded variable comment)

把Dao中定义的方法的参数作为文字列直接嵌入的场合,在SQL指令中指定嵌入变量注解。 执行时,参数值会自动地被置换成嵌入变量注解右边的固定值。 嵌入变量注解的记法如下所示。

- /*$参数名*/固定值

参数为JavaBean的场合,记法如下所示。

- /*$参数名.属性名*/固定值

使用嵌入变量注解的场合,调用Dao的程序中必须有防止SQL植入式(injection)攻击的处理。

IF注解(comment)

使用IF注解,可以根据相应的条件改变要执行的SQL指令。IF注解的记法如下。

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

使用例如下所示。

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

IF注解的条件如果为真,则使用/*IF*/和/*END*/之间的部分。 上述场合,只有参数hoge不为空(null)的情况下,才使用IF注解内的部分(hoge = /*hoge*/'abc')
还有,作为IF注解的条件为假的处理部分,使用ELSE注解。 条件为假的场合,使用/*ELSE*/之后的部分。ELSE注解的记法如下。

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

如果条件为假,使用ELSE之后的部分(hoge is null)。

BEGIN注解(comment)

当WHERE子句中的所有的IF注解(不包含ELSE注解)的条件为假的时候,使用BEGIN注解可以不输出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,要继承以下的类(class)。

- org.seasar.dao.impl.AbstractDao

Dao的接口名,必须以"Dao"结尾。从继承AbstractDao的类所实装的接口中, S2Dao会把以"Dao"结尾接口当作Dao接口。

EntityManager提供以下的方法(method)。
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. 被实装的Dao的接口名必须以"Dao"结尾。
  4. 实装构造方法(constructor)

  5. 把org.seasar.dao.DaoMetaDataFactory作为参数,调用super(org.seasar.dao.DaoMetaDataFactory)。
  6. 对Dao中定义的方法(method)进行実装

  7. 使用EntityManager中提供的方法(method)的场合,可以像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更新指令,只要遵循方法(method)名的命名规约,定义一个将JavaBeans作为参数的方法即可, 不需要作成SQL文件。例如要生成Insert指令的场合,只需遵循命名规约做如下定义。

public int insert(Department department);

使用VersionNo进行排他控制

S2Dao可以自动地进行排他控制。
设定方法是,创建一个有排他控制的列项的表,在JavaBeans里定义一个变量versionNo,类型为int(或者Integer),使用VersionNo自动进行排他控制。

例如,有两个用户取得了versionNo=0的同一个纪录,试图对其进行更新的场合, 先更新的用户可以正常地完成更新处理。这时versionNo的值自动加1,更新后DB内的versionNo的值变为1。 其后另一个用户要更新时,用户保存的versionNo值为0, 但是实际上DB内的值已经是1,两者为不同的值,所以更新失败,产生NotSingleRowUpdatedRuntimeException例外。

要根据VersionNo的值来判断一个对象是刚插入DB的对象还是新创建的未持久化的对象, 请在JavaBeans里将versionNo的初期值设定为-1。 这样就可以做如下判断,versionNo == -1,是未持久化对象,versionNo >= 0,则是持久化对象。

使用Timestamp进行排他控制

除了VersionNo之外,S2Dao还可以用Timestamp来自动地进行排他控制。 只要定义一个Timestamp类型的名叫timestamp的属性,就可自动进行排他控制。 从Dao执行Insert处理时,会把new java.sql.Timestamp()的值赋给timestamp属性,然后发行Insert指令。 执行更新(Update・Delete)处理时,把JavaBeans的timestamp属性值与记录中的timestamp列项的值相比较, 如果不相等,就和VersionNo处理时同样,产生NotSingleRowUpdatedRuntimeException例外。 更新时如果Timestamp列项的值被设定为null,进行比较就会失败,务请注意。

批处理更新

使用更新系列的方法(method),像下面这样,把参数定义为实体(Entity)类的数组或是列表(List),就可以自动生成SQL更新指令,实现批处理更新功能。 不过,批处理更新的场合,不能实现ID的自动生成处理。

还有,即使有versionNo或是timestamp设定,实体(Entity)的值并不等于更新后的DB的值。 批处理更新后,请从DB中取得最新的实体(Entity)的数据。

int insertBatch(Employee[] employees)

从1.0.47版开始,可以像下面这样,把返回值的类型定义为int[],就可以取得每一个实体(Entity)的更新记录的数目。 不过这一返回值是返回JDBC驱动程序(driver)的数值,根据驱动程序的情况,有时只能得到java.sql.Statement#SUCCESS_NO_INFO的值。

int[] insertBatch2(Employee[] employees)

SQL检索指令的自动生成

根据方法的signatue,S2Dao也可以自动生成SELECT指令。只要在ARGS注释里指定列项名,就可以自动生成动态SQL指令,指令中的WHERE子句会根据参数值的变化而变化。

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映射的列项的场合,使用「列项名_相关序号」。 使用左联接执行一个SQL指令可以取得用于N:1映射的Bean。不支持左联接的RDBMS不能自动生成SELECT指令。 像Oracle那样的,其左联接语法不同于标准联接语法的场合,S2Dao自动判断该RDBMS为Oracle,生成合适的SQL指令。

也可以使用DTO(Data Transter Object)作为参数。在这种场合,不能使用ARGS注释。 在只有一个参数而且没有使用ARGS注释的场合,S2Dao就把参数视为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文件中的路径(path)为参数生成S2Container
  2. 从生成的S2Container调用getComponent,取得已注册的Dao
  3. 执行所得到的Dao的方法(method)

S2Dao不做事务(transaction)控制。有关事务(transaction)控制的内容,请参阅事务的自动控制一节。

使用例如下
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

作为前提条件,在案例中使用以下的表,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进行关联。
  • 定义方法(method)。
    全件检索方法(getAllEmployees()方法)
    以员工工号为条件抽出员工的方法(getEmployee(int empno)方法)
    计算员工人数的方法(getCount()方法)
    以员工的职务和部门号为条件抽出员工的方法(getEmployeeByJobDeptno(String job, Integer deptno)方法)
    更新员工记录的方法(update(Employee employee)方法)
  • 使用ARGS注释将SQL指令和方法(method)的参数进行关联。
  • 取得EMP表的记录数的getCount()方法,只返回表的记录数,所以返回值定为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中定义的方法(method),做成相应的SQL文件。
  • 文件名为「类名_方法名.sql」。
  • 使用SQL注解(comment)作成动态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文件
  • 引入(include)dao.dicon文件。
  • 将刚才作成的Dao定义为组件(component)。
  • 对上述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>
作成执行文件
  • 把刚才作成的dicon文件(EmployeeDao.dicon)的路径,作为org.seasar.framework.container.S2Container#create()方法的の第1参数,创建一个容器(container)。
  • 把组件中注册的类名(EmployeeDao.class),作为org.seasar.framework.container.S2Container#getComponent()方法的の第1参数,取得组件(component)。
  • 执行Dao里定义的方法(method)。
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] physical connection opened
DEBUG 2004-10-12 11:07:01,133 [main] logical connection opened
DEBUG 2004-10-12 11:07:01,914 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:07:02,867 [main] logical connection closed
7369, SMIT, 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] logical connection opened
DEBUG 2004-10-12 11:07:02,914 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:07:02,914 [main] logical connection closed
count:14
DEBUG 2004-10-12 11:07:02,929 [main] SELECT * FROM emp

DEBUG 2004-10-12 11:07:02,929 [main] logical connection opened
DEBUG 2004-10-12 11:07:02,945 [main] logical connection closed
DEBUG 2004-10-12 11:07:02,945 [main] SELECT * FROM emp
WHERE
  job = 'CLERK'


DEBUG 2004-10-12 11:07:02,945 [main] logical connection opened
DEBUG 2004-10-12 11:07:02,961 [main] logical connection closed
DEBUG 2004-10-12 11:07:02,961 [main] SELECT * FROM emp
WHERE

  deptno = 20

DEBUG 2004-10-12 11:07:02,961 [main] logical connection opened
DEBUG 2004-10-12 11:07:02,961 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:07:03,008 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:07:03,023 [main] logical connection closed
updatedRows:1
DEBUG 2004-10-12 11:07:03,023 [main] physical connection closed

从"updatedRows"的值,可以确认更新的记录数。
这一演习,放在s2dao/src/examples/dao目录下。

自动生成SQL更新指令的Example

这个演习,使用VersionNo进行排他控制,并且自动生成SQL更新(UPDATE, INSERT, DELETE)指令。这个演习不用作成SQL文件。
作成的文件如下所示。

  • Dao(DepartmentDao.java)
  • dicon文件(DepartmentDao.dicon)
  • 执行用的类(DepartmentDaoClient.java)
作成Dao
  • 将DEPT表和相应的JavaBeans进行关联。
  • 定义用于更新处理的方法(method)。
    追加部门的方法(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文件
  • 引入(include) dao.dicon文件。
  • 将刚才作成的Dao定义为组件(component)。
  • 对上述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>
作成执行文件
  • 把刚才作成的dicon文件(DepartmentDao.dicon)的路径,作为org.seasar.framework.container.S2Container#create()方法的第一参数里,创建一个容器(container)。
  • 把组件中注册的类名(DepartmentDao.class),作为org.seasar.framework.container.S2Container#getComponent()メ方法的第一参数,取得组件(component)。
  • 执行Dao里定义的方法(method)。
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] physical connection opened
DEBUG 2004-09-09 19:22:10,588 [main] logical connection opened
DEBUG 2004-09-09 19:22:11,447 [main] logical connection closed
DEBUG 2004-09-09 19:22:11,603 [main] logical connection opened
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] logical connection closed
before update versionNo:0
DEBUG 2004-09-09 19:22:11,666 [main] logical connection opened
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] logical connection closed
after update versionNo:1
DEBUG 2004-09-09 19:22:11,666 [main] logical connection opened
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] logical connection closed
DEBUG 2004-09-09 19:22:11,681 [main] physical connect closed

从输出结果可以看出,SQL指令是自动的被生成并执行的。而且由于在JavaBeans(Department)里,定义了int类型的versionNo属性, versionNo会自动加1,根据这个值实现了排他控制。 在调用update方法之前,versionNo的值为0,但是在调用了update方法之后,versionNo的值变成了1。
这一演习,放在s2dao/src/examples/dao目录下。

自动生成SQL检索指令的Example

这个演习,使用Timestamp进行排他控制并且自动生成SELECT指令。这个演习不用作成SQL文件。还有,演习中还定义了使用DTO作为参数的方法。 作成的文件如下所示。

  • Dao(EmployeeAutoDao.java)
  • DTO(EmployeeSearchCondition.java)
  • dicon文件(EmployeeAutoDao.dicon)
  • 执行用的类(EmployeeAutoDaoClient.java)
作成Dao
  • 将EMP表和相应的JavaBeans进行关联。
  • 定义方法(method)。
    全件检索方法(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
  • 使用COLUMN注释将列项和dname属性进行关联。
  • 进行属性声明。
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文件
  • 引入(include) dao.dicon文件。
  • 将刚才作成的Dao定义为组件(component)。
  • 对上述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>
作成执行文件
  • 把刚才作成的dicon文件(EmployeeAutoDao.dicon)的路径,作为org.seasar.framework.container.S2Container#create()方法的第一参数,创建一个容器(container)。
  • 把组件中注册的类名(EmployeeAutoDao.class),作为org.seasar.framework.container.S2Container#getComponent()方法的第一参数,取得组件(component)。
  • 执行Dao里定义的方法(method)。
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] physical connection opened
DEBUG 2004-10-12 11:35:22,069 [main] logical connection opened
DEBUG 2004-10-12 11:35:22,897 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,866 [main] logical connection closed
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] ogical connection opened
DEBUG 2004-10-12 11:35:23,882 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,913 [main] loical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,929 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,944 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,960 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,976 [main] logical connection closed
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] logical connection opened
DEBUG 2004-10-12 11:35:23,991 [main] logical connection closed
before tmestamp:2004-10-12 10:15:54.914
DEBUG 2004-10-12 11:35:23,991 [main] logical connection opened
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] logical connection closed
after timestamp:2004-10-12 11:35:23.991
DEBUG 2004-10-12 11:35:24,054 [main] physical connection closed

从输出的日志(log)可以看出,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。
  • 实装(implements)Employee2Dao。
  • 实装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 + "%");
    }
}
作成icon文件
  • 引入(include) dao.dicon文件。
  • 将继承AbstractDao的类定义为组件(component)。
  • 对已注册的组件应用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>
作成执行文件
  • 把刚才作成的dicon文件(Employee2Dao.dicon)的路径,作为org.seasar.framework.container.S2Container#create()方法的第一参数,创建一个容器(container)。
  • 把组件中注册的类名(Employee2Dao.class),作为org.seasar.framework.container.S2Container#getComponent()方法的第一参数,取得组件(component)。
  • 指定检索条件为含有"CO"的名字。
  • 执行Dao里定义的方法(method)。
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] physical connection opened
DEBUG 2004-10-01 10:14:39,333 [main] logical connection opened
DEBUG 2004-10-01 10:14:40,379 [main] logical connetion closed
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] logical connection opened
DEBUG 2004-10-01 10:14:41,426 [main] logical connection closed
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] physical connection closed

这一演习,放在s2dao/src/examples/dao目录下。