Seasar DI Container with AOP

Example

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

テーブル:CD

カラム名 論理名 NotNull 主キー
ID ID INTEGER
TITLE アルバム名 VARCHAR(100)

CONTENT 内容(ジャンル) VARCHAR(200)

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

<?php

class CdBean {

    const TABLE = "CD";

    private $id;
    private $title;
    private $content;

    public function getContent() {
        return $this->content;
    }

    public function setContent($content) {
        $this->content = $content;
    }

    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getTitle() {
        return $this->title;
    }

    public function setTitle($title) {
        $this->title = $title;
    }
}
?>

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

src/s2dao.php5/dao.dicon

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components namespace="dao">

    <include path="%PDO_DICON%" />

    <component class="S2Dao_BasicResultSetFactory" />
    <component class="S2Dao_BasicStatementFactory" />
    <component class="S2Dao_FieldAnnotationReaderFactory" />

    <component class="S2Dao_DaoMetaDataFactoryImpl" />
    <component name="interceptor" class="S2DaoInterceptor" />

</components>

src/example/pdo.dicon

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"components21.dtd">
<components namespace="pdo">

    <component name="dataSource" class="S2Container_PDODataSource">
        <property name="dsn">"mysql:host=localhost; dbname=s2con"</property>
        <property name="user">"root"</property>
        <property name="password">"pass"</property>
    </component>

</components>

また、テーブルの状態は以下の通りです。

mysql> select * from CD order by ID;
+----+--------------------+---------+
| ID | TITLE              | CONTENT |
+----+--------------------+---------+
|  1 | HeyJude            | Rock    |
|  2 | ride on technology |         |
|  3 | あの木何の木 | J-POP   |
| 20 | gonna rice         | NULL    |
+----+--------------------+---------+

S2Dao.PHP5の簡単なExample

S2Dao.PHP5を用いる場合の簡単なサンプルです。
作成するファイルは以下のとおりです。

  • Dao(Cd1Dao.class.php)
  • SQLファイル(Cd1Dao_getCD1List.sql, Cd1Dao_getCD2List.sql, Cd1Dao_getCD3List.sql)
  • diconファイル(example.dicon.xml)
  • 共通読み込みファイル(example.inc.php)
  • 実行クラス<検索系>(cd_select1.php, cd_select2.php, cd_select3.php)
  • 実行クラス<更新系>(cd_insert.php, cd_update.php, cd_delete.php)

Daoの作成

  • CDテーブルと対応するBeanと関連付けをします。
  • メソッドを定義します。
    • 全件検索するメソッド(getAllCdArray()メソッド)
    • IDが1以上のCDを検索するメソッド(getCdsArray()メソッド)
    • IDを引数として、一致するCDを検索するメソッド(getSelectCdByIdArray($id)メソッド)
    • IDとタイトルを引数として一致するCDを検索するメソッド(getCD1List($id, $title = null)メソッド)
    • IDとタイトル、ジャンルを引数として一致するCDを検索するメソッド(getCD2List($id, $title, $content)メソッド)
    • IDを引数として、一致するCDを検索するメソッド(getCD3List($id = null)メソッド)
    • CDの更新を行うメソッド(update(CdBean $cd)メソッド)
    • CDの追加を行うメソッド(insert(CdBean $cd)メソッド)
    • CDの削除を行うメソッド(delete(CdBean $cd)メソッド)
  • SQL文とメソッドの引数を関連付けするにはARGSアノテーションを使用します。
  • ※List、Arrayで終わるメソッドの戻り値についてはメソッドの定義を御覧になってください
<?php

interface Cd1Dao {

    const BEAN = "CdBean";

    const insert_NO_PERSISTENT_PROPS = "content";
    const update_NO_PERSISTENT_PROPS = "id, content";

    const getSelectCdByIdArray_ARGS = "id";
    const getCD1List_ARGS = "id, title";
    const getCD2List_ARGS = "id, title, content";
    const getCD3List_ARGS = "id";

    const getCdsArray_SQL = "SELECT CD.ID, CD.TITLE FROM CD WHERE ID > 1";
    const getSelectCdByIdArray_QUERY = "ID = /*id*/1";

    public function update(CdBean $cd);
    public function insert(CdBean $cd);
    public function delete(CdBean $cd);

    public function getAllCdArray();
    public function getCdsArray();
    public function getSelectCdByIdArray($id);
    public function getCD1List($id, $title = null);
    public function getCD2List($id, $title, $content);
    public function getCD3List($id = null);
}
?>

SQLファイルの作成

  • Daoに定義したメソッドに対応するSQLファイルをそれぞれ作成します。
  • ファイル名は「クラス名_メソッド名.sql」とします。
  • 動的なSQLはSQLコメントを使用して作成します。

Cd1Dao_getCD1List.sql

SELECT * FROM CD WHERE ID = /*id*/16 OR TITLE = /*title*/'18';

Cd1Dao_getCD2List.sql

SELECT *
FROM CD
/*BEGIN*/WHERE
    /*IF id != null*/ ID = /*id*/'1'/*END*/
    /*IF title != null*/ AND TITLE = /*title*/'bohemian rhapsody'/*END*/
    /*IF content == "rock"*/ AND CONTENT = 'blues'/*END*/
/*END*/

Cd1Dao_getCD3List.sql

SELECT *
FROM CD
WHERE
/*IF id != null*/ ID = /*id*/'1'
    -- ELSE ID = 1
/*END*/

diconファイルの作成

  • dao.diconをincludeします。
  • 作成したDaoのコンポーネント定義します。
  • Daoにdao.interceptor(S2DaoInterceptor)を適用します。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"components21.dtd">
<components namespace="example">

    <include path="DAO_DICON" />

    <component name="beanCdDao" class="Cd1Dao">
        <aspect>dao.interceptor</aspect>
    </component>

    <component name="daoImpl" class="Cd2DaoImpl">
        <aspect>dao.interceptor</aspect>
    </component>

</components>

共通読み込みファイル(example.inc.php)の作成

  • S2Container.phpとS2Dao.phpを読み込みます。
  • pdo.diconファイルまでのパスを設定します。(PDO_DICON定数)
  • S2ContainerClassLoader::import()で作成したファイルを読み込ませます。
  • __autoload()関数にS2ContainerClassLoader::load()を設定します。
<?php
require_once 'S2Container/S2Container.php';
require_once 'S2Dao/S2Dao.php';

define('S2CONTAINER_PHP5_LOG_LEVEL', S2Container_SimpleLogger::DEBUG);
define('DAO_DICON', dirname(dirname(__FILE__)) . '/dao.dicon');
define('PDO_DICON', dirname(dirname(__FILE__)) . '/pdo.dicon');

/** class import */
if(class_exists('S2ContainerClassLoader')){
    S2ContainerClassLoader::import(S2CONTAINER_PHP5);
    S2ContainerClassLoader::import(S2DAO_PHP5);
    S2ContainerClassLoader::import(dirname(__FILE__) . "/dao");
    S2ContainerClassLoader::import(dirname(__FILE__) . "/entity");
    S2ContainerClassLoader::import(dirname(__FILE__) . "/impl");
    function __autoload($class = null){
        if(S2ContainerClassLoader::load($class)){
            return;
        }
    }
}
?>

実行ファイルの作成

  • 先に作成した共通読み込みファイル(example.inc.php)を読み込みます
  • S2Container::create()メソッドの第1引数に作成したdiconファイル(example.dicon.dicon)のパスを指定してコンテナを作成します。
  • S2Container::getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(beanCdDao)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。

検索系のサンプル

cd_select1.php
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("beanCdDao");

$cd = $dao->getAllCDArray();

for($i = 0; $i < count($cd); $i++){
    echo "ID: " . $cd[$i]->getId(), PHP_EOL;
    echo "TITLE: " . $cd[$i]->getTitle(), PHP_EOL;
    echo "CONTENT: " . $cd[$i]->getContent(), PHP_EOL;
    echo "-------", PHP_EOL;
}

echo "=====", PHP_EOL;

$cd = $dao->getSelectCdByIdArray(20);
foreach($cd as $bean){
    echo "ID: " . $bean->getId(), PHP_EOL;
    echo "TITLE: " . $bean->getTitle(), PHP_EOL;
    echo "CONTENT: " . $bean->getContent(), PHP_EOL;
    echo "-------", PHP_EOL;
}

?>
実行結果
> php cd_select1.php
[DEBUG]  - SELECT CD.CONTENT, CD.ID, CD.TITLE FROM CD
ID: 1
TITLE: HeyJude
CONTENT: Rock
-------
ID: 20
TITLE: gonna rice
CONTENT:
-------
ID: 3
TITLE: あの木何の木
CONTENT: J-POP
-------
ID: 2
TITLE: ride on technology
CONTENT:
-------
=====
[DEBUG]  - SELECT CD.CONTENT, CD.ID, CD.TITLE FROM CD WHERE ID = 20
ID: 20
TITLE: gonna rice
CONTENT:
-------
cd_select2.php
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("beanCdDao");

$list = $dao->getCD1List(2, 'aaa');
for($i = 0; $i < $list->size(); $i++){
    $cd = $list->get($i);
    echo "ID: " . $cd->getId(), PHP_EOL;
    echo "TITLE: " . $cd->getTitle(), PHP_EOL;
    echo "CONTENT: " . $cd->getContent(), PHP_EOL;
    echo "-------", PHP_EOL;
}

echo "=====", PHP_EOL;

$list = $dao->getCD2List(2, 'help!', 'rock');
$i = $list->getIterator();
while($i->valid()){
    $cd = $i->current();
    echo "ID: " . $cd->getId(), PHP_EOL;
    echo "TITLE: " . $cd->getTitle(), PHP_EOL;
    echo "CONTENT: " . $cd->getContent(), PHP_EOL;
    echo "-------", PHP_EOL;
    $i->next();
}

?>
実行結果
> php cd_select2.php
[DEBUG]  - SELECT * FROM CD WHERE ID = 2 OR TITLE = 'aaa'
ID: 2
TITLE: ride on technology
CONTENT:
-------
=====
[DEBUG]  - SELECT *
FROM CD
WHERE
     ID = 2
     AND TITLE = 'help!'
     AND CONTENT = 'blues'
cd_select3.php
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("beanCdDao");

$list = $dao->getCD3List(null);
$cds = $list->toArray();
for($i = 0; $i < $list->size(); $i++){
    echo "ID: " . $cds[$i]->getId(), PHP_EOL;
    echo "TITLE: " . $cds[$i]->getTitle(), PHP_EOL;
    echo "CONTENT: " . $cds[$i]->getContent(), PHP_EOL;
    echo "-------", PHP_EOL;
}

echo "======", PHP_EOL;

$cds = $dao->getCdsArray();
foreach($cds as $cd){
    echo "ID: " . $cd->getId(), PHP_EOL;
    echo "TITLE: " . $cd->getTitle(), PHP_EOL;
    echo "CONTENT: " . $cd->getContent(), PHP_EOL;
    echo "-------", PHP_EOL;
}
?>
実行結果
> php cd_select3.php
[DEBUG]  - SELECT *
FROM CD
WHERE
ID = 1

ID: 1
TITLE: HeyJude
CONTENT: Rock
-------
======
[DEBUG]  - SELECT CD.ID, CD.TITLE FROM CD WHERE ID > 1
ID: 2
TITLE: ride on technology
CONTENT:
-------
ID: 3
TITLE: あの木何の木
CONTENT:
-------
ID: 20
TITLE: gonna rice
CONTENT:
-------

更新系のサンプル

cd_insert.php
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("beanCdDao");
$cd = new CdBean();

$cd->setId(4);
$cd->setTitle("gonna rice");
$cd->setContent("Techno");
$dao->insert($cd);

?>
実行結果
> php cd_insert.php
[DEBUG]  - INSERT INTO CD (ID, TITLE) VALUES (4, 'gonna rice')
cd_update.php
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("beanCdDao");
$cd = new CdBean();

$cd->setId(4);
$cd->setTitle("ride on technology");
$dao->update($cd);

?>
実行結果
> php cd_update.php
[DEBUG]  - UPDATE CD SET TITLE = 'ride on technology' WHERE ID = 4
cd_delete.php
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("beanCdDao");
$cd = new CdBean();

$cd->setId(4);
$dao->delete($cd);

?>
実行結果
< php cd_delete.php
[DEBUG]  - DELETE FROM CD WHERE ID = 4

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

EntityManagerを使用したExample

EntityManagerを用いる場合の簡単なサンプルです。
作成するファイルは以下のとおりです。

  • Dao(Cd2Dao.class.php)
  • S2Dao_AbstractDaoを継承したクラス(Cd2DaoImpl.class.php)
  • 実行ファイル(Cd2DaoClient.php)

Daoの作成

  • インターフェース名は"Dao"で終わらす必要があります
  • CDテーブルと対応するBeanと関連付けをします。
  • メソッドを定義します。IDを引数に検索するメソッド(getCd($id))
<?php

interface Cd2Dao {
    const BEAN = "CdBean";
    public function getCd($id);
}
?>

S2Dao_AbstractDaoを継承したクラスの作成

  • S2Dao_AbstractDaoを継承します。
  • Cd2Daoをimplementsします。
  • geCdメソッドを実装します。
    IDを指定して検索をします。
<?php

class Cd2DaoImpl extends S2Dao_AbstractDao implements Cd2Dao {

    public function __construct(S2Dao_DaoMetaDataFactory $daoMetaDataFactory){
        parent::__construct($daoMetaDataFactory);
    }

    public function getCd($id){
        return $this->getEntityManager()->find("ID = ?", $id);
        //return $this->getEntityManager()->findArray("ID = ?", $id);
        //return $this->getEntityManager()->findBean("ID = ?", $id);
        //return $this->getEntityManager()->findObject("ID = ?", $id);
    }
}

?>

実行ファイルの作成

  • 先に作成した共通読み込みファイル(example.inc.php)を読み込みます
  • S2Container::create()メソッドの第1引数に作成したdiconファイル(example.dicon.dicon)のパスを指定してコンテナを作成します。
  • S2Container::getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(beanCdDao)を指定してコンポーネントを取得します。
  • IDに2を指定します。
  • Daoに定義したメソッドを実行します。
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/example.dicon.xml");
$dao = $container->getComponent("daoImpl");

var_dump($dao->getCd(2));

?>
実行結果
> php Cd2DaoClient.class.php
[DEBUG]  - SELECT CD.CONTENT, CD.ID, CD.TITLE FROM CD WHERE ID = 2
object(S2Dao_ArrayList)#259 (1) {
  [0]=>
  object(CdBean)#260 (3) {
    ["id:private"]=>
    string(1) "2"
    ["title:private"]=>
    string(18) "ride on technology"
    ["content:private"]=>
    string(0) ""
  }
}

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

コメントアノテーションを使用したExample

S2Dao.PHP5の簡単なExampleを全てコメントアノテーションで実行します。

コメントアノテーションを行うにあたって変更するファイルは以下です。

  1. 共通読み込みファイル(example.inc.php)
  2. Dao(CdDao.class.php)
  3. Entity(CdBean.class.php)
  4. SQLバインド変数によってコメントアノテーションのできないSQLについてはファイル(.sql)化

example.inc.phpの変更

<?php
require_once 'S2Container/S2Container.php';
require_once 'S2Dao/S2Dao.php';

define('S2CONTAINER_PHP5_LOG_LEVEL', S2Container_SimpleLogger::DEBUG);
define('DAO_DICON', dirname(dirname(__FILE__)) . '/dao.dicon');
define('PDO_DICON', dirname(dirname(__FILE__)) . '/pdo.dicon');
define('S2DAO_PHP5_USE_COMMENT', true);

if(class_exists('S2ContainerClassLoader')){
    S2ContainerClassLoader::import(S2CONTAINER_PHP5);
    S2ContainerClassLoader::import(dirname(__FILE__) . "/dao");
    S2ContainerClassLoader::import(dirname(__FILE__) . "/entity");
    function __autoload($class = null){
        if(S2ContainerClassLoader::load($class)){
            return;
        }
    }
}
?>

Dao(CdDao.class.php)の変更

<?php

/**
 * @author nowel
 * @Dao(bean = CdBean)
 */
interface CdDao {

    /**
     * @NoPersistentProperty("id, content")
     */
    public function update(CdBean $cd);

    /**
     * @NoPersistentProperty("content")
     */
    public function insert(CdBean $cd);
    public function delete(CdBean $cd);

    /**
     * @return array
     */
    public function getAll();

    /**
     * @Sql("SELECT CD.ID, CD.TITLE FROM CD WHERE ID > 1")
     * @return array
     */
    public function getCds();

    /**
     * @return array
     */
    public function getSelectCdById($id);

    /**
     * @return list
     */
    public function getCD1($id, $title = null);

    /**
     * @return list
     */
    public function getCD2($id, $title, $content);

    /**
     * @return list
     */
    public function getCD3($id = null);

    /**
     * @Sql("SELECT COUNT(*) FROM CD", dbms = mysql)
     */
    public function getCdCount();
}
?>

Entity(CdBean.class.php)の変更

<?php

/**
 * @author nowel
 * @Bean(table = CD)
 */
class CdBean {

    /**
     * @Column("ID")
     */
    private $id;

    /**
     * @Column("TITLE")
     */
    private $title;

    /**
     * @Column("CONTENT")
     */
    private $content;

    public function getContent() {
        return $this->content;
    }

    public function setContent($content) {
        $this->content = $content;
    }

    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getTitle() {
        return $this->title;
    }

    public function setTitle($title) {
        $this->title = $title;
    }
}
?>

SQLファイルの追加および変更

SQLコメントにてバインドを行うとPHPコメントと衝突してしまい正しく記述できない場合があります。
その場合はSQLファイルにしておくのがよいでしょう

SQLファイルについてはメソッド名と同一にするため、ファイル名にList、Arrayを付与する必要はありません。

  • CdDao_getCD1.sql
  • CdDao_getCD2.sql
  • CdDao_getCD3.sql
  • CdDao_getSelectCdById.sql

CdDao_getCD1.sql

SELECT * FROM CD WHERE ID = /*id*/16 OR TITLE = /*title*/'18';

CdDao_getCD2.sql

SELECT * FROM CD
/*BEGIN*/WHERE
    /*IF id != null*/ ID = /*id*/'1'/*END*/
    /*IF title != null*/ AND TITLE = /*title*/'bohemian rhapsody'/*END*/
    /*IF content == "rock"*/ AND CONTENT = 'blues'/*END*/
/*END*/

CdDao_getCD3.sql

SELECT *
FROM CD
WHERE
/*IF id != null*/ ID = /*id*/'1'
--ELSE ID = 1
/*END*/

CdDao_getSelectCdById.sql

select * from CD where ID = /*id*/1

実行結果

cd_select1.php

[DEBUG]  - SELECT CD.CONTENT, CD.ID, CD.TITLE FROM CD
ID: 1
TITLE: S2Dao!!!
CONTENT: hello!!
-------
=====
[DEBUG]  - select * from CD where ID = 1
ID: 1
TITLE: S2Dao!!!
CONTENT: hello!!
-------

cd_select2.php

[DEBUG]  - SELECT * FROM CD WHERE ID = 2 OR TITLE = 'aaa'
=====
[DEBUG]  - SELECT * FROM CD
WHERE
     ID = 2
     AND TITLE = 'help!'
     AND CONTENT = 'blues'

cd_select3.php

[DEBUG]  - SELECT *
FROM CD
WHERE
 ID = 1

object(S2Dao_ArrayList)#345 (1) {
  [0]=>
  object(CdBean)#348 (3) {
    ["id:private"]=>
    string(1) "1"
    ["title:private"]=>
    string(8) "S2Dao!!!"
    ["content:private"]=>
    string(7) "hello!!"
}
}
ID: 1
TITLE: S2Dao!!!
CONTENT: hello!!
-------
======

cd_getCount.php

[DEBUG]  - SELECT COUNT(*) FROM CD
int(1)

cd_insert.php

[DEBUG]  - INSERT INTO CD (ID, TITLE) VALUES (2, 'gonna rice')

cd_update.php

[DEBUG]  - UPDATE CD SET TITLE = 'ride on technology' WHERE ID = 2

cd_delete.php

[DEBUG]  - DELETE FROM CD WHERE ID = 2

定数アノテーション時と同じ結果が取れたことが確認できました。
この演習は、/examples/s2dao_comment/以下に用意されています。

Example2(Java版S2DaoのExample)

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

テーブル:EMP

カラム名 論理名 NotNull 主キー
EMPNO 従業員番号 INTEGER
ENAME 従業員名 VARCHAR

JOB 仕事 VARCHAR

MGR 上司 INTEGER

HIREDATE 雇用日 DATE

SAL 給料 NUMBER

COMM 手数料 NUMBER

DEPTNO 部署番号 INTEGER

テーブル:DEPT

カラム名 論理名 NotNull 主キー
DEPTNO 部署番号 INTEGER
DNAME 部署名 VARCHAR

LOC ロケーション VARCHAR

VERSIONNO バージョン番号 INTEGER

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

<?php

class Employee implements Serializable {

    const TABLE = "EMP";
    const department_RELNO = 0;
    const timestamp_COLUMN = "tstamp";

    private $empno;
    private $ename;
    private $job;
    private $mgr;
    private $hiredate;
    private $sal;
    private $comm;
    private $deptno;
    private $timestamp;
    private $department;

    public function serialize(){
        $prop = array();
        foreach(get_class_vars(__CLASS__) as $key => $value){
            $prop[$key] = $value;
        }
        return serialize($prop);
    }

    public function unserialize($serialized){
        foreach(unserialize($serialized) as $key => $value){
            $this->$key = $value;
        }
    }

    public function __construct($empno = null) {
        if(!is_null($empno)){
            $this->empno = $empno;
        }
    }

    public function getEmpno() {
        return $this->empno;
    }

    public function setEmpno($empno) {
        $this->empno = $empno;
    }

    public function getEname() {
        return $this->ename;
    }

    public function setEname($ename) {
        $this->ename = $ename;
    }

    public function getJob() {
        return $this->job;
    }

    public function setJob($job) {
        $this->job = $job;
    }

    public function getMgr() {
        return $this->mgr;
    }

    public function setMgr($mgr) {
        $this->mgr = $mgr;
    }

    public function getHiredate() {
        return $this->hiredate;
    }

    public function setHiredate($hiredate) {
        $this->hiredate = $hiredate;
    }

    public function getSal() {
        return $this->sal;
    }

    public function setSal($sal) {
        $this->sal = $sal;
    }

    public function getComm() {
        return $this->comm;
    }

    public function setComm($comm) {
        $this->comm = $comm;
    }

    public function getDeptno() {
        return $this->deptno;
    }

    public function setDeptno($deptno) {
        $this->deptno = $deptno;
    }

    public function getTimestamp() {
        return $this->timestamp;
    }

    public function setTimestamp($timestamp) {
        $this->timestamp = $timestamp;
    }

    public function getDepartment() {
        return $this->department;
    }

    public function setDepartment(Department $department) {
        $this->department = $department;
    }

    public function equals($other) {
        if (!($other instanceof Employee)) return false;
        return $this->getEmpno() == $other->getEmpno();
    }

    public function toString() {
        $buf = $this->empno . ", ";
        $buf .= $this->ename . ", ";
        $buf .= $this->job . ", ";
        $buf .= $this->mgr . ", ";
        $buf .= $this->hiredate . ", ";
        $buf .= $this->sal . ", ";
        $buf .= $this->comm . ", ";
        $buf .= $this->deptno . ", ";
        $buf .= $this->timestamp . " {";
        if($this->department != null){
            $buf .=  $this->department->toString();
        } else {
            $buf .= "null";
        }
        $buf .= "}";
        return $buf;
    }

    public function hashCode() {
        return $this->getEmpno();
    }

}
?>

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

<?php

class Department implements Serializable {

    const TABLE = "DEPT";
    private $deptno;
    private $dname;
    private $loc;
    private $versionNo;

    public function __construct() {
    }

    public function serialize(){
        return serialize(array(
            "deptno" => $this->deptno,
            "dname" => $this->dname,
            "loc" => $this->loc,
            "versionNo" => $this->versionNo,
        ));
    }

    public function unserialize($serialized){
        foreach(unserialize($serialized) as $key => $value){
            $this->$key = $value;
        }
    }

    public function getDeptno() {
        return $this->deptno;
    }

    public function setDeptno($deptno) {
        $this->deptno = $deptno;
    }

    public function getDname() {
        return $this->dname;
    }

    public function setDname($dname) {
        $this->dname = $dname;
    }

    public function getLoc() {
        return $this->loc;
    }

    public function setLoc($loc) {
        $this->loc = $loc;
    }

    public function getVersionNo() {
        return $this->versionNo;
    }

    public function setVersionNo($versionNo) {
        $this->versionNo = $versionNo;
    }

    public function equals($other) {
        if (!($other instanceof Department) ) return false;
        return $this->getDeptno() == $other->getDeptno();
    }

    public function toString() {
        $buf = $this->deptno . ", ";
        $buf .= $this->dname . ", ";
        $buf .= $this->loc . ", ";
        $buf .= $this->versionNo;
        return $buf;
    }

    public function hashCode() {
        return $this->getDeptno();
    }
}

?>

SQL文を記述する場合のExample

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

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

interface EmployeeDao {

    const BEAN = "Employee";

    public function getAllEmployeesList();

    public function getEmployee($empno);

    public function getCount();

    public function getEmployeeByJobDeptnoList($job, $deptno);

    const getEmployeeByDeptnoList_QUERY = "
                /*IF deptno !== null*/EMP.DEPTNO = /*deptno*/123
                --ELSE 1 = 1
                /*END*/";

    public function getEmployeeByDeptnoList($deptno);

    public function update(Employee $employee);
}
?>
SQLファイルの作成
  • Daoに定義したメソッドに対応するSQLファイルをそれぞれ作成します。
  • ファイル名は「クラス名_メソッド名.sql」とします。
  • 動的なSQLはSQLコメントを使用して作成します。

EmployeeDao_getAllEmployeesList.sql

SELECT emp.*, dept.dname dname_0, dept.loc loc_0 FROM EMP as emp, DEPT as 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 EMP.empno = /*empno*/7788 AND EMP.deptno = DEPT.deptno

EmployeeDao_getCount.sql

SELECT count(*) FROM emp

EmployeeDao_getEmployeeByJobDeptnoList.sql

SELECT * FROM EMP
/*BEGIN*/WHERE
  /*IF job !== null*/EMP.JOB = /*job*/'CLERK'/*END*/
  /*IF deptno !== null*/AND EMP.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="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)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/EmployeeDao.dicon");
$dao = $container->getComponent("EmployeeDao");
$employees = $dao->getAllEmployeesList();
for ($i = 0; $i < $employees->size(); ++$i) {
    var_dump($employees->get($i)->toString());
}

$employee = $dao->getEmployee(7788);
var_dump($employee->toString());

$count = $dao->getCount();
var_dump("count:" . $count);

$dao->getEmployeeByJobDeptnoList(null, null);
$dao->getEmployeeByJobDeptnoList("CLERK", null);
$dao->getEmployeeByJobDeptnoList(null, 20);
$dao->getEmployeeByJobDeptnoList("CLERK", 20);
$dao->getEmployeeByDeptnoList(20);
$dao->getEmployeeByDeptnoList(null);

var_dump("updatedRows:" . $dao->update($employee));

$container->destroy();
?>
実行結果
> php EmployeeDaoClient.class.php
[DEBUG]  - SELECT emp.*, dept.dname as dname_0, dept.loc as loc_0 FROM EMP as emp, DEPT as dept
WHERE emp.deptno = dept.deptno ORDER BY emp.empno
string(96) "7369, SMITH, CLERK, 7902, 1980-12-17, 800.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(104) "7499, ALLEN, SALESMAN, 7698, 1981-02-20, 1600.00, 300.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(103) "7521, WARD, SALESMAN, 7698, 1981-02-22, 1250.00, 500.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(99) "7566, JONES, MANAGER, 7839, 1981-04-02, 2975.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(106) "7654, MARTIN, SALESMAN, 7698, 1981-09-28, 1250.00, 1400.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(97) "7698, BLAKE, MANAGER, 7839, 1981-05-01, 2850.00, , 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(103) "7782, CLARK, MANAGER, 7839, 1981-06-09, 2450.00, , 10, 2000-01-01 00:00:00 {10, ACCOUNTING, NEW YORK, }"
string(99) "7788, SCOTT, ANALYST, 7566, 1982-12-09, 3000.00, , 20, 2006-05-14 10:30:16 {20, RESEARCH, DALLAS, }"
string(100) "7839, KING, PRESIDENT, , 1981-11-17, 5000.00, , 10, 2000-01-01 00:00:00 {10, ACCOUNTING, NEW YORK, }"
string(103) "7844, TURNER, SALESMAN, 7698, 1981-09-08, 1500.00, 0.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(97) "7876, ADAMS, CLERK, 7788, 1983-01-12, 1100.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(94) "7900, JAMES, CLERK, 7698, 1981-12-03, 950.00, , 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(98) "7902, FORD, ANALYST, 7566, 1981-12-03, 3000.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(102) "7934, MILLER, CLERK, 7782, 1982-01-23, 1300.00, , 10, 2000-01-01 00:00:00 {10, ACCOUNTING, NEW YORK, }"
[DEBUG]  - SELECT EMP.*, DEPT.dname as dname_0, DEPT.loc as loc_0 FROM EMP, DEPT
WHERE EMP.empno = 7788 AND EMP.deptno = DEPT.deptno
tring(97) "7788, SCOTT, ANALYST, 7566, 1982-12-09, 3000.00, , 20, 2006-05-14 10:30:16 {, RESEARCH, DALLAS, }"
[DEBUG]  - SELECT count(*) FROM EMP
string(8) "count:14"
[DEBUG]  - SELECT * FROM EMP
[DEBUG]  - SELECT * FROM EMP
WHERE
  EMP.JOB = 'CLERK'

[DEBUG]  - SELECT * FROM EMP
WHERE

   EMP.DEPTNO = 20

[DEBUG]  - SELECT * FROM EMP
WHERE
  EMP.JOB = 'CLERK'
  AND EMP.DEPTNO = 20

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno,
    EMP.tstamp, 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
                EMP.DEPTNO = 20

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno,
    EMP.tstamp, 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
                 1 = 1

[DEBUG]  - UPDATE EMP SET ename = 'SCOTT'
WHERE empno = 7788
string(13) "updatedRows:1"

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

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

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

作成するファイルは以下のとおりです。

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

interface DepartmentDao {

    const BEAN = "Department";

    public function insert(Department $department);
    public function update(Department $department);
    public function 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="DepartmentDao">
            <aspect>dao.interceptor</aspect>
    </component>
</components>
実行ファイルの作成
  • S2Container::create()メソッドの第1引数に作成したdiconファイル(DepartmentDao.dicon)のパスを指定してコンテナを作成します。
  • S2Container::getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(DepartmentDao)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/DepartmentDao.dicon");
$dao = $container->getComponent("DepartmentDao");
$dept = new Department();

$dept->setDeptno(99);
$dept->setDname("foo");
$dao->insert($dept);
$dept->setDname("bar");

echo "before update versionNo:" . $dept->getVersionNo(), PHP_EOL;
$dao->update($dept);
echo "after update versionNo:" . $dept->getVersionNo(), PHP_EOL;
$dao->delete($dept);

$container->destroy();
?>
実行結果
> php DepartmentDaoClient.php
[DEBUG]  - INSERT INTO DEPT (DEPTNO, DNAME, LOC, VERSIONNO) VALUES (99, 'foo', null, 0)
before update versionNo:0
[DEBUG]  - UPDATE DEPT SET DNAME = 'bar', LOC = null, VERSIONNO = 1 WHERE DEPTNO = 99 AND VERSIONNO = 0
after update versionNo:1
[DEBUG]  - DELETE FROM DEPT WHERE DEPTNO = 99 AND VERSIONNO = 1

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

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

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

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

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

interface EmployeeAutoDao {

    const BEAN = "Employee";

    public function getAllEmployeesList();

    public function getEmployeeByJobDeptnoList($job = null, $deptno = null);

    public function getEmployeeByEmpno($empno);

    const getEmployeesBySalList_QUERY = "EMP.sal BETWEEN ? AND ? ORDER BY EMP.empno";

    public function getEmployeesBySalList($minSal, $maxSal);

    public function getEmployeeByDnameList($dname_0);

    public function getEmployeesBySearchConditionList(EmployeeSearchCondition $dto);

    public function update(Employee $employee);
}
?>
DTOの作成
<?php

class EmployeeSearchCondition {

    const dname_COLUMN = "dname_0";

    private $job;
    private $dname;

    public function getDname() {
        return $this->dname;
    }

    public function setDname($dname) {
        $this->dname = $dname;
    }

    public function getJob() {
        return $this->job;
    }

    public function setJob($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="EmployeeAutoDao">
        <aspect>dao.interceptor</aspect>
    </component>
</components>
実行ファイルの作成
  • S2Container#create()メソッドの第1引数に作成したdiconファイル(EmployeeAutoDao.dicon)のパスを指定してコンテナを作成します。
  • S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(EmployeeAutoDao)を指定してコンポーネントを取得します。
  • Daoに定義したメソッドを実行します。
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/EmployeeAutoDao.dicon");
$dao = $container->getComponent("EmployeeAutoDao");

$dao->getEmployeeByJobDeptnoList(null, null);
$dao->getEmployeeByJobDeptnoList("CLERK", null);
$dao->getEmployeeByJobDeptnoList(null, 20);
$dao->getEmployeeByJobDeptnoList("CLERK", 20);

$employees = $dao->getEmployeesBySalList(0, 1000);
for ($i = 0; $i < $employees->size(); ++$i) {
    var_dump($employees->get($i)->toString());
}

$employees = $dao->getEmployeeByDnameList("SALES");
for ($i = 0; $i < $employees->size(); ++$i) {
    var_dump($employees->get($i)->toString());
}

$dto = new EmployeeSearchCondition();
$dto->setDname("RESEARCH");
$employees = $dao->getEmployeesBySearchConditionList($dto);
for ($i = 0; $i < $employees->size(); ++$i) {
    var_dump($employees->get($i)->toString());
}

$employee = $dao->getEmployeeByEmpno(7788);
var_dump("before timestamp:" . $employee->getTimestamp());
$dao->update($employee);
var_dump("after timestamp:" . $employee->getTimestamp());

$container->destroy();
?>
実行結果
[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm,
EMP.deptno, EMP.tstamp, 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

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm,
EMP.deptno, EMP.tstamp, 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  EMP.job = 'CLERK'

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm,
EMP.deptno, EMP.tstamp, 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  EMP.job = '' AND EMP.deptno = 20

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm,
EMP.deptno, EMP.tstamp, 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 EMP.job = 'CLERK' AND EMP.deptno = 20

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal,
EMP.comm, EMP.deptno, EMP.tstamp, 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 EMP.sal BETWEEN 0 AND 1000 ORDER BY EMP.empno

string(96) "7369, SMITH, CLERK, 7902, 1980-12-17, 800.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(94) "7900, JAMES, CLERK, 7698, 1981-12-03, 950.00, , 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal,
EMP.comm, EMP.deptno, EMP.tstamp, 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

string(96) "7369, SMITH, CLERK, 7902, 1980-12-17, 800.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(104) "7499, ALLEN, SALESMAN, 7698, 1981-02-20, 1600.00, 300.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(103) "7521, WARD, SALESMAN, 7698, 1981-02-22, 1250.00, 500.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(99) "7566, JONES, MANAGER, 7839, 1981-04-02, 2975.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(106) "7654, MARTIN, SALESMAN, 7698, 1981-09-28, 1250.00, 1400.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(97) "7698, BLAKE, MANAGER, 7839, 1981-05-01, 2850.00, , 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(103) "7782, CLARK, MANAGER, 7839, 1981-06-09, 2450.00, , 10, 2000-01-01 00:00:00 {10, ACCOUNTING, NEW YORK, }"
string(100) "7839, KING, PRESIDENT, , 1981-11-17, 5000.00, , 10, 2000-01-01 00:00:00 {10, ACCOUNTING, NEW YORK, }"
string(103) "7844, TURNER, SALESMAN, 7698, 1981-09-08, 1500.00, 0.00, 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(97) "7876, ADAMS, CLERK, 7788, 1983-01-12, 1100.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(94) "7900, JAMES, CLERK, 7698, 1981-12-03, 950.00, , 30, 2000-01-01 00:00:00 {30, SALES, CHICAGO, }"
string(98) "7902, FORD, ANALYST, 7566, 1981-12-03, 3000.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(102) "7934, MILLER, CLERK, 7782, 1982-01-23, 1300.00, , 10, 2000-01-01 00:00:00 {10, ACCOUNTING, NEW YORK, }"
string(99) "7788, SCOTT, ANALYST, 7566, 1982-12-09, 3000.00, , 20, 2006-05-14 10:30:16 {20, RESEARCH, DALLAS, }"

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm,
EMP.deptno, EMP.tstamp, 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  department.dname = 'RESEARCH'

string(96) "7369, SMITH, CLERK, 7902, 1980-12-17, 800.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(99) "7566, JONES, MANAGER, 7839, 1981-04-02, 2975.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(97) "7876, ADAMS, CLERK, 7788, 1983-01-12, 1100.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(98) "7902, FORD, ANALYST, 7566, 1981-12-03, 3000.00, , 20, 2000-01-01 00:00:00 {20, RESEARCH, DALLAS, }"
string(99) "7788, SCOTT, ANALYST, 7566, 1982-12-09, 3000.00, , 20, 2006-05-16 23:38:39 {20, RESEARCH, DALLAS, }"

[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm,
EMP.deptno, EMP.tstamp, 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  EMP.empno = 7788

string(36) "before timestamp:2006-05-16 23:58:48"

[DEBUG]  - UPDATE EMP SET ename = 'SCOTT', job = 'ANALYST', mgr = 7566,
hiredate = '1982-12-09', sal = '3000.00', comm = null, deptno = 20,
tstamp = '2006-05-17 00:00:41' WHERE empno = 7788 AND tstamp = '2006-05-16 23:58:48'

string(35) "after timestamp:2006-05-17 00:00:41"

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

EntityManagerを使用したExample2

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

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

interface Employee2Dao {
    public function getEmployees($ename);
    public function getEmployee($eno);
}

?>
AbstractDaoを継承したクラスの作成
  • S2Dao_AbstractDaoを継承します。
  • Employee2Daoをimplementsします。
  • getEmployeesメソッドを実装します。指定した文字列を名前に含む検索をします。
<?php

class Employee2DaoImpl extends S2Dao_AbstractDao implements Employee2Dao {

    const BEAN = "Employee";

    public function __construct(S2Dao_DaoMetaDataFactory $daoMetaDataFactory){
        parent::__construct($daoMetaDataFactory);
    }

    public function getEmployees($ename) {
        return $this->getEntityManager()->find("EMP.ename LIKE ? ", "%" . $ename . "%");
    }

    public function getEmployee($eno){
        return $this->getEntityManager()->find(
                    "SELECT * FROM EMP WHERE EMPNO = ?", $eno);
    }

}

?>
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="Employee2DaoImpl">
        <aspect>dao.interceptor</aspect>
    </component>
</components>
実行ファイルの作成
  • S2Container::create()メソッドの第1引数に作成したdiconファイル(Employee2Dao.dicon)のパスを指定してコンテナを作成します。
  • S2Container::getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(Employee2Dao)を指定してコンポーネントを取得します。
  • "CO"を名前に含むという条件を指定します。
  • Daoに定義したメソッドを実行します。
<?php
require_once dirname(__FILE__) . "/example.inc.php";

$container = S2ContainerFactory::create("./resource/Employee2Dao.dicon");
$dao = $container->getComponent("Employee2Dao");
$employees = $dao->getEmployees("CO");
for ($i = 0; $i < $employees->size(); ++$i) {
    var_dump($employees->get($i)->toString());
}
$employee = $dao->getEmployee(7788);
var_dump($employee[0]->toString());

$container->destroy();
?>
実行結果
> php Employee2DaoClient.class.php
[DEBUG]  - SELECT EMP.empno, EMP.ename, EMP.job, EMP.mgr, EMP.hiredate, EMP.sal, EMP.comm, EMP.deptno, EMP.tstamp,
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 EMP.ename LIKE '%CO%'
string(99) "7788, SCOTT, ANALYST, 7566, 1982-12-09, 3000.00, , 20, 2006-05-14 10:30:16 {20, RESEARCH, DALLAS, }"
[DEBUG]  - SELECT * FROM EMP WHERE EMPNO = 7788
string(85) "7788, SCOTT, ANALYST, 7566, 1982-12-09, 3000.00, , 20, 2006-05-14 10:30:16 {20, , , }"

※[DEBUG]表示において不要な部分は削除しています。

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