日本オラクル
プロダクトSC本部 テクノロジーSC部
佐藤 裕之

第4回:EJB vs. POJO 〜準備編〜



 はじめに

前回「EJB2.xからEJB3.0へ」では、簡単なサンプルを元にEJB仕様のVer2.xからVer3.0(EaryDraft)への変化を見ました。EJB3.0ではPOJO(Plain Old Java Object)としてEJBを開発出来るようになります。では、そのEJB3.0に影響を与えたPOJOとは如何なるものなのか、今一度調べておいても良いかな・・・ということで今回から数回に渡り、高度なDataMapperの機能を実現するテクノロジとして現状最も選択される可能性が高い、EJB2.xとPOJOを用いたO/Rマッピングツールの違いについて触れてみたいと思います。
何か比較しやすいサンプルを探していたのですが、適当な物がなかったので、今回は、DataMapperの機能のみを抽出したシンプルなサンプルを作成し、今後の調査の環境を整えたいと思います。


 サンプルスキーマ/アプリケーション

今回は、簡易的なオーダシステムを表現した、CUSTOMER/ITEM/CORDERの3種類のテーブルがある以下のスキーマを利用します。

【コード】オーダシステムサンプル用のスキーマ作成DDL
CREATE TABLE "CORDER" (
 ORDER_ID NUMBER(20) NOT NULL,
 ITEM_ID NUMBER(20) NULL,
 SHIP_ADDR VARCHAR2(80) NULL,
 QUANTITY NUMBER(20) NOT NULL,
 CUST_ID NUMBER(20) NULL,
 PRIMARY KEY (ORDER_ID));
 
CREATE TABLE "ITEM" (
 ITEM_ID NUMBER(20) NOT NULL,
 NAME VARCHAR2(80) NULL,
 DESCRIPTION VARCHAR2(80) NULL,
 PRIMARY KEY (ITEM_ID));
 
DROP TABLE "CUSTOMER";
CREATE TABLE "CUSTOMER" (
 CUST_ID NUMBER(20) NOT NULL,
 ADDR VARCHAR2(80) NULL,
 NAME VARCHAR2(80) NULL,
 PRIMARY KEY (CUST_ID));

ALTER TABLE "CORDER" ADD CONSTRAINT
 ORDER_ITEM FOREIGN KEY (ITEM_ID) REFERENCES ITEM (ITEM_ID);
ALTER TABLE "CORDER" ADD CONSTRAINT
 ORDER_CUSTOMER FOREIGN KEY (CUST_ID) REFERENCES CUSTOMER (CUST_ID);

このスキーマを元に現行のEJB2.xとPOJOで其々以下の12の機能(要件)を実現できるサンプルを作成します。

1) 顧客(Customer)の新規作成
2) 品目(Item)の作成
3) 注文(Order)の追加(新規作成)
4) 顧客(Customer)の全件検索
5) 品目(Item)の全件検索
6) ある顧客が注文した全リストの取得
7) ある品目(Item)を注文した全リスト取得
8) 顧客(Customer)の検索
9) 品目(Item)の検索
10) 注文(Order)IDでの注文詳細情報取得
11) 注文(Order)の数量の変更
12) 注文(Order)のキャンセル

なお、サンプルの作成において、なるべくDataMapperの機能のみを実装することを心がけました。例えば、DTO(Data Transfer Object)等は作らずになるべくシンプルなものにしたとか、EJB2.xとPOJOのサンプルで比較しやすい命名規則にしたとか、です。


 EJB2.x

では、現状のEJB2.xでのCMP(Container Managed Persistence)のサンプルを見ていきましょう。作成するコンポーネント(EJB)は、以下の4つになります。

1) CUSTOMERテーブルに対応したドメインクラスCustomerBean - CMP
2) ITEMテーブルに対応したドメインクラスItemBean - CMP
3) CORDERテーブルに対応したドメインクラスOrderBean - CMP
4) 12の機能(要件)を満たし、クライアントからのインタフェースとなるOrderSystemBean - Stateless Session Bean

また、CustomerBeanとOrderBeanは、1対多の双方向の関連をもち、ItemBeanとOrderBeanも1対多の双方向の関連をもちます。
EJB2.x仕様でEJB開発する上では、以下のファイルが必要になります。(念のため)

ファイル 役割
Homeインタフェース Local/Remote Object生成・検索のためのファクトリとなるHome Objectのためのインタフェース
コンポーネントインタフェース ビジネスロジックを公開したLocal/Remote Objectのためのインタフェース
Beanクラス ビジネスロジックの実装
デプロイメント
ディスクリプタ
J2EE汎用 EJB2.Xで定められたデプロイメントディスクリプタ
EJBコンテナ固有 EJBコンテナ(アプリケーションサーバ)で定めされたデプロイメントディスクリプタ


  CustomerBeanサンプル

CUSTOMERテーブルに対応したCMP(Container Managed Persistence)のEntity Beanです。


Homeインタフェース

EJB2.x仕様のHomeインタフェースはLocalかRemoteかにより、javax.ejb.EJBLocalHomeもしくはjavax.ejb.EJBHomeを実装し作成します。CustomerBeanはEntity Beanなのでjavax.ejb.EJBLocalHomeを実装します。

【コード】CustomerLocalHome.java
package sample.ejb.domain;

import java.util.*;
import javax.ejb.*;

/**
 *  CustomerBeanのLocalHomeインタフェース
 */
public interface CustomerLocalHome extends EJBLocalHome {
    // コンポーネントJNDI名
    public static final String COMP_NAME = "java:comp/env/ejb/CustomerBean";

    CustomerLocal create(Long custId, String name, String addr)
        throws CreateException;

    CustomerLocal findByPrimaryKey(Long custId) throws FinderException;

    Collection findAll() throws FinderException;

    Collection findAllByName(String searchString) throws FinderException;
}

COMP_NAMEという定数にコンポーネント(EJB)のJNDI名を持たせ、クライアントから呼び出すときに利用する様にしています。


コンポーネントインタフェース

EJB2.x仕様のコンポーネントインタフェースはLocalかRemoteかにより、javax.ejb.EJBLocalObjectもしくはjavax.ejb.EJBObjectを実装し作成します。CustomerBeanはEntity Beanなのでjavax.ejb.EJBLocalObjectを実装します。

【コード】CustomerLocal.java
package sample.ejb.domain;

import java.util.*;
import javax.ejb.*;

/**
 * CustomerBeanのコンポーネントインタフェース
 */
public interface CustomerLocal extends EJBLocalObject {
    Long getCustId();

    String getAddr();

    void setAddr(String addr);

    String getName();

    void setName(String name);

    Collection getOrders();

    void setOrders(Collection orders);

    void addOrder(OrderLocal order);
}


Beanクラス

EJB2.x仕様のEntity Beanクラスは、javax.ejb.EntityBeanインタフェースを実装し作成します。
CustomerBeanはEntity Beanなのでjavax.ejb.EntityBeanインタフェースを実装します。また、javax.ejb.EntityBeanインタフェース内でされているコンテナ契約メソッド(ejbXXX()メソッド)をオーバライドする必要があります。

【コード】CustomerBean.java
package sample.ejb.domain;

import org.apache.log4j.*;
import java.util.*;
import javax.ejb.*;

/**
 *  CustomerBeanのBeanクラス
 */
public abstract class CustomerBean implements EntityBean {
    private static Logger logger = Logger.getLogger(CustomerBean.class);
    private EntityContext context;

    public Long ejbCreate(Long custId, String name, String addr) {
        logger.debug("ejbCreate() is called");
        setCustId(custId);
        setName(name);
        setAddr(addr);

        return null;
    }

    public void ejbPostCreate(Long custId, String name, String city) {
        logger.debug("ejbPostCreate() is called");
    }

    public void ejbActivate() {
        logger.debug("ejbActivate() is called");
    }

    public void ejbPassivate() {
        logger.debug("ejbPassivate() is called");
    }

    public void ejbLoad() {
        logger.debug("ejbLoad() is called");
    }

    public void ejbStore() {
        logger.debug("ejbStore() is called");
    }

    public void ejbRemove() {
        logger.debug("ejbRemove() is called");
    }

    public void setEntityContext(EntityContext ctx) {
        logger.debug("ejbEntityContext() is called");
        this.context = ctx;
    }

    public void unsetEntityContext() {
        logger.debug("unsetEntityContext() is called");
        this.context = null;
    }

    public abstract Long getCustId();

    public abstract void setCustId(Long custId);

    public abstract String getAddr();

    public abstract void setAddr(String addr);

    public abstract String getName();

    public abstract void setName(String name);

    public abstract Collection getOrders();

    public abstract void setOrders(Collection orders);

    public void addOrder(OrderLocal order) {
        logger.debug("addOrder() is called");
        getOrders().add(order);
    }
}

今回のサンプルでは、デバック出力としてLog4jを利用しています。以下は、log4j.propertiesのサンプルです。

【コード】log4j.properties
# # ファイルに出力
log4j.rootCategory=DEBUG, A1
#A1の設定。
log4j.appender.A1=org.apache.log4j.FileAppender
#出力先
log4j.appender.A1.File=d:\\temp\\test.log
#出力レイアウト
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#レイアウト定義
log4j.appender.A1.layout.ConversionPattern=%d %-5p %c - %m [%t] (%F:%L)%n

上記の設定で、d:\temp\test.logにコード内のログを出力する様にしています。


  ItemBeanサンプル

ITEMテーブルに対応したCMP(Container Managed Persistence)のEntity Beanです。


Homeインタフェース

ItemBeanはEntity Beanなのでjavax.ejb.EJBLocalHomeインタフェースを実装します。

【コード】ItemLocalHome.java
package sample.ejb.domain;

import java.util.*;
import javax.ejb.*;

/**
 *  ItemBeanのLocalHomeインタフェース
 */
public interface ItemLocalHome extends EJBLocalHome {
    // コンポーネントJNDI名
    public static final String COMP_NAME = "java:comp/env/ejb/ItemBean";

    ItemLocal create(Long itemId, String name, String description)
        throws CreateException;

    ItemLocal findByPrimaryKey(Long primaryKey) throws FinderException;

    Collection findAll() throws FinderException;

    Collection findAllByName(String searchString) throws FinderException;
}


コンポーネントインタフェース

ItemBeanはEntity Beanなのでjavax.ejb.EJBLocalObjectインタフェースを実装します。

【コード】ItemLocal.java
package sample.ejb.domain;

import java.util.*;
import javax.ejb.*;

/**
 *  ItemLocalのLocalHomeインタフェース
 */
public interface ItemLocal extends EJBLocalObject {
    Long getItemId();

    String getName();

    void setName(String name);

    String getDescription();

    void setDescription(String description);

    Collection getOrders();

    void setOrders(Collection orders);
}


Beanクラス

ItemBeanはEntity Beanなのでjavax.ejb.EntityBeanインタフェースを実装します。

【コード】ItemBean.java
package sample.ejb.domain;

import org.apache.log4j.*;
import java.util.*;
import javax.ejb.*;

/**
 *  ItemBeanのBeanクラス
 */
public abstract class ItemBean implements EntityBean {
    private static Logger logger = Logger.getLogger(ItemBean.class);
    private EntityContext context;

    public Long ejbCreate(Long itemId, String name, String description) {
        logger.debug("ejbCreate() is called");
        setItemId(itemId);
        setName(name);
        setDescription(description);

        return null;
    }

    public void ejbPostCreate(Long itemId, String name, String description) {
        logger.debug("ejbPostCreate() is called");
    }

    public void ejbActivate() {
        logger.debug("ejbActivate() is called");
    }

    public void ejbPassivate() {
        logger.debug("ejbPassivate() is called");
    }

    public void ejbLoad() {
        logger.debug("ejbLoad() is called");
    }

    public void ejbStore() {
        logger.debug("ejbStore() is called");
    }

    public void ejbRemove() {
        logger.debug("ejbRemove() is called");
    }

    public void setEntityContext(EntityContext ctx) {
        logger.debug("setEntityContext() is called");
        this.context = ctx;
    }

    public void unsetEntityContext() {
        logger.debug("unsetEntityContext() is called");
        this.context = null;
    }

    public abstract Long getItemId();

    public abstract void setItemId(Long itemId);

    public abstract String getName();

    public abstract void setName(String name);

    public abstract String getDescription();

    public abstract void setDescription(String description);

    public abstract Collection getOrders();

    public abstract void setOrders(Collection orders);
}


  OrderBeanサンプル

CORDERテーブルに対応したCMP(Container Managed Persistence)のEntity Beanです。


Homeインタフェース

OrderBeanはEntity Beanなのでjavax.ejb.EJBLocalHomeインタフェースを実装します。

【コード】OrderLocalHome.java
package sample.ejb.domain;

import java.util.*;
import javax.ejb.*;

/**
 *  OrderLocalBeanのLocalHomeインタフェース
 */
public interface OrderLocalHome extends EJBLocalHome {
    // コンポーネントJNDI名
    public static final String COMP_NAME = "java:comp/env/ejb/OrderBean";

    OrderLocal create(Long orderId, Long quantity, String shippingAddress)
        throws CreateException;

    OrderLocal findByPrimaryKey(Long primaryKey) throws FinderException;

    Collection findAll() throws FinderException;

    Collection findAllByItem(Long itemId) throws FinderException;
}


コンポーネントインタフェース

OrberBeanはEntity Beanなのでjavax.ejb.EJBLocalObjectインタフェースを実装します。

【コード】OrderLocal.java
package sample.ejb.domain;

import javax.ejb.EJBLocalObject;

/**
 *  OrderLocalBeanのコンポーネントインタフェース
 */
public interface OrderLocal extends EJBLocalObject {
    Long getOrderId();

    String getShipAddr();

    void setShipAddr(String shipAddr);

    Long getQuantity();

    void setQuantity(Long quantity);

    ItemLocal getItem();

    void setItem(ItemLocal item);

    CustomerLocal getCustomer();

    void setCustomer(CustomerLocal customer);
}


Beanクラス

OrderBeanはEntity Beanなのでjavax.ejb.EntityBeanインタフェースを実装します。

【コード】OrderBean.java
package sample.ejb.domain;

import org.apache.log4j.*;
import javax.ejb.*;

/**
 *  OrderLocalBeanのBeanクラス
 */
public abstract class OrderBean implements EntityBean {
    private static Logger logger = Logger.getLogger(ItemBean.class);
    private EntityContext context;

    public Long ejbCreate(Long orderId, Long quantity, String shippingAddress) {
        logger.debug("ejbCreate() is called");
        setOrderId(orderId);
        setQuantity(quantity);
        setShipAddr(shippingAddress);

        return null;
    }

    public void ejbPostCreate(Long orderId, Long quantity, String city) {
        logger.debug("ejbPostCreate() is called");
    }

    public void ejbActivate() {
        logger.debug("ejbActivate() is called");
    }

    public void ejbLoad() {
        logger.debug("ejbLoad() is called");
    }

    public void ejbPassivate() {
        logger.debug("ejbPassivate() is called");
    }

    public void ejbRemove() {
        logger.debug("ejbRemove() is called");
    }

    public void ejbStore() {
        logger.debug("ejbStore() is called");
    }

    public void setEntityContext(EntityContext ctx) {
        logger.debug("setEntityContext() is called");
        this.context = ctx;
    }

    public void unsetEntityContext() {
        logger.debug("unsetEntityContext() is called");
        this.context = null;
    }

    public abstract Long getOrderId();

    public abstract void setOrderId(Long orderId);

    public abstract String getShipAddr();

    public abstract void setShipAddr(String shipAddr);

    public abstract Long getQuantity();

    public abstract void setQuantity(Long quantity);

    public abstract ItemLocal getItem();

    public abstract void setItem(ItemLocal item);

    public abstract CustomerLocal getCustomer();

    public abstract void setCustomer(CustomerLocal customer);
}


  OrderSystemBeanサンプル

ドメインクラスであり、Entity BeanであるCustomerBean/ItemBean/OrderBeanにアクセスするファサード的な役割を持ち、12の機能(要件)を実装したStateless Session Beanです。


Homeインタフェース

OrderSystemはSession Beanであり、リモートからのアクセスを想定しているのでjavax.ejb.EJBHomeを実装します。

// OrderSystemHome.java
package sample.ejb.business;

import java.rmi.*;
import javax.ejb.*;

public interface OrderSystemHome extends EJBHome {
    public OrderSystem create() throws CreateException, RemoteException;
}


コンポーネントインタフェース

OrderSystemはSession Beanであり、リモートからのアクセスを想定しているのでjavax.ejb.EJBObjectを実装します。

// OrderSystem.java
package sample.ejb.business;

import java.math.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

public interface OrderSystem extends EJBObject {
    public long createNewCustomer(String name, String address)
        throws RemoteException, CreateException;

    public long createNewItem(String name, String description)
        throws RemoteException, CreateException;

    public long placeOrder(Long customerId, Long itemId, Long quantity)
        throws RemoteException, CreateException, FinderException;

    public long getAllCustomers() throws RemoteException, FinderException;

    public long getAllItems() throws RemoteException, FinderException;

    public long getOrdersForCustomer(Long customerId)
        throws RemoteException, FinderException;

    public long getOrdersForItem(Long itemId)
        throws RemoteException, FinderException;

    public long getMatchingCustomers(String searchString)
        throws RemoteException, FinderException;

    public long getMatchingItems(String searchString)
        throws RemoteException, FinderException;

    public long findOrderDetailFor(Long orderId)
        throws RemoteException, FinderException;

    public long changeOrderQuantity(Long orderId, Long quantity)
        throws RemoteException, FinderException;

    public long cancelOrder(Long orderId)
        throws RemoteException, FinderException, RemoveException;
}


Beanクラス

OrderSystemはSession Beanなのでjavax.ejb. SessionBeanを実装します。

【コード】OrderSystemBean.java
package sample.ejb.business;

import org.apache.log4j.*;
import sample.ejb.domain.*;
import sample.ejb.util.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

/**
 *  OrderLocalBeanのBeanクラス
 */
public class OrderSystemBean implements SessionBean {
    // コンポーネントJNDI名
    public static final String ORDER_NAME = OrderLocalHome.COMP_NAME;
    public static final String CUSTOMER_NAME = CustomerLocalHome.COMP_NAME;
    public static final String ITEM_NAME = ItemLocalHome.COMP_NAME;

    // Loggerの取得
    private static Logger logger = Logger.getLogger(OrderSystemBean.class);

    // LocalHomeスタブ
    private OrderLocalHome orderLocalHome;
    private CustomerLocalHome customerLocalHome;
    private ItemLocalHome itemLocalHome;

    public void ejbCreate() throws CreateException {
        logger.debug("ejbCreate() is called");

        try {
            // LocalHomeスタブの取得
            orderLocalHome = getOrderLocalHome();
            customerLocalHome = getCustomerLocalHome();
            itemLocalHome = getItemLocalHome();
        } catch (NamingException ne) {
            throw new CreateException(ne.getMessage());
        }
    }

    public void ejbActivate() {
        logger.debug("ejbActivate() is called");
    }

    public void ejbPassivate() {
        logger.debug("ejbPassivate() is called");
    }

    public void ejbRemove() {
        logger.debug("ejbRemove() is called");
    }

    public void setSessionContext(SessionContext ctx) {
        logger.debug("setSessionContext() is called");
    }

    // 1) 顧客(Customer)の新規作成
    public long createNewCustomer(String name, String city)
        throws CreateException {
        logger.debug("createNewCustomer() is called");

        long startTime = System.currentTimeMillis();

        // CustomerLocalの生成
        CustomerLocal customer = customerLocalHome.create(Sequence.getNextCustId(),
                name, city);

        return (System.currentTimeMillis() - startTime);
    }

    // 2) 品目(Item)の新規作成
    public long createNewItem(String name, String description)
        throws CreateException {
        logger.debug("createNewItem() is called");

        long startTime = System.currentTimeMillis();

        // ItemLocalの生成
        ItemLocal item = (ItemLocal) (itemLocalHome.create(Sequence.getNextItemId(),
                name, description));

        return (System.currentTimeMillis() - startTime);
    }

    // 3) 注文(Order)の追加
    public long placeOrder(Long custId, Long itemId, Long quantity)
        throws FinderException, CreateException {
        logger.debug("placeOrder() is called");

        long startTime = System.currentTimeMillis();

        // 主キーによるCustomerとItemの検索
        CustomerLocal customer = customerLocalHome.findByPrimaryKey(custId);
        ItemLocal item = itemLocalHome.findByPrimaryKey(itemId);
        
        // Orderの生成
        OrderLocal order = orderLocalHome.create(Sequence.getNextOrderId(),
                quantity, customer.getAddr());

        order.setItem(item);

        // Orderの追加
        customer.addOrder(order);

        return (System.currentTimeMillis() - startTime);
    }

    // 4) 全ての顧客(Customer)情報の取得
    public long getAllCustomers() throws FinderException {
        logger.debug("getAllCustomers() is called");

        long startTime = System.currentTimeMillis();

        // Customerコレクションの取得
        Collection customers = customerLocalHome.findAll();
        Iterator customerIterator = customers.iterator();

        // コレクションからCustomerの取り出し
        while (customerIterator.hasNext()) {
            CustomerLocal customer = (CustomerLocal) customerIterator.next();

            // 属性の取得
            Long custId = customer.getCustId();
            String custName = customer.getName();
            String addr = customer.getAddr();

            // 属性の表示
            logger.info("====== 顧客情報 ======");
            logger.info("顧客ID: " + custId);
            logger.info("顧客名: " + custName);
            logger.info("住所: " + addr);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 5) 全ての品目(Item)の取得
    public long getAllItems() throws FinderException {
        logger.debug("getAllItems() is called");

        long startTime = System.currentTimeMillis();

        // Itemコレクションの取得
        Collection items = itemLocalHome.findAll();
        Iterator itemIterator = items.iterator();

        // コレクションからItemの取り出し
        while (itemIterator.hasNext()) {
            ItemLocal item = (ItemLocal) itemIterator.next();

            // 属性の取得
            Long itemId = item.getItemId();
            String itemName = item.getName();
            String itemDesc = item.getDescription();

            // 属性の表示
            logger.info("====== 品目情報 ======");
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + itemDesc);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 6) 顧客(Customer)に関連した注文の取得
    public long getOrdersForCustomer(Long customerId) throws FinderException {
        logger.debug("ejbOrdersForCustomer() is called");

        long startTime = System.currentTimeMillis();

        // 主キーによるCustomerの検索
        CustomerLocal customer = customerLocalHome.findByPrimaryKey(customerId);

        // Orderコレクションの取得
        Collection orders = customer.getOrders();
        Iterator orderIterator = orders.iterator();

        // 属性の取得
        String custName = customer.getName();

        // コレクションからOrderの取り出し
        while (orderIterator.hasNext()) {
            OrderLocal order = (OrderLocal) orderIterator.next();
            ItemLocal item = (ItemLocal) order.getItem();

            // 属性の取得
            Long orderId = order.getOrderId();
            Long itemId = item.getItemId();
            String itemName = item.getName();
            String desc = item.getDescription();
            Long quantity = order.getQuantity();

            // 属性の表示
            logger.info("====== " + custName + "の注文" + " ======");
            logger.info("顧客名" + custName);
            logger.info("注文ID: " + orderId);
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + desc);
            logger.info("注文量: " + quantity);
        }

        return (System.currentTimeMillis() - startTime);
    }

    // 7) 品目(Item)に関連した注文の取得
    public long getOrdersForItem(Long itemId) throws FinderException {
        logger.debug("getOrdersForItem() is called");

        long startTime = System.currentTimeMillis();

        // 品目IDによる関連したOrderの検索
        Collection orders = orderLocalHome.findAllByItem(itemId);
        Iterator orderIterator = orders.iterator();

        // コレクションからOrderの取り出し
        while (orderIterator.hasNext()) {
            OrderLocal order = (OrderLocal) orderIterator.next();
            CustomerLocal customer = order.getCustomer();
            ItemLocal item = order.getItem();

            // 属性の取得
            String custName = customer.getName();
            Long orderId = order.getOrderId();
            String itemName = item.getName();
            String desc = item.getDescription();
            Long quantity = order.getQuantity();

            // 属性の表示
            logger.info("====== " + itemName + "の注文" + " ======");
            logger.info("顧客名" + custName);
            logger.info("注文ID: " + orderId);
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + desc);
            logger.info("注文量: " + quantity);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 8) 顧客(Customer)情報の名前での検索
    public long getMatchingCustomers(String searchString)
        throws FinderException {
        logger.debug("getMatchingCustomers() is called");

        long startTime = System.currentTimeMillis();

        // 顧客名でのCustomerの検索によるコレクションの取得
        Collection customers = customerLocalHome.findAllByName(searchString);
        Iterator customerIterator = customers.iterator();

        // コレクションからCustomerの取り出し
        while (customerIterator.hasNext()) {
            CustomerLocal customer = (CustomerLocal) customerIterator.next();

            // 属性の取得
            Long custId = customer.getCustId();
            String custName = customer.getName();
            String addr = customer.getAddr();

            // 属性の表示
            logger.info("====== " + searchString + "の顧客検索結果" + " ======");
            logger.info("顧客ID: " + custId);
            logger.info("顧客名: " + custName);
            logger.info("住所: " + addr);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 9) 品目(Item)の名前での検索
    public long getMatchingItems(String searchString) throws FinderException {
        logger.debug("getMatchingItems() is called");

        long startTime = System.currentTimeMillis();

        // 品目名でのItemの検索によるコレクションの取得
        Collection items = itemLocalHome.findAllByName(searchString);
        Iterator itemIterator = items.iterator();

        //  コレクションからのItemの取得
        while (itemIterator.hasNext()) {
            ItemLocal item = (ItemLocal) itemIterator.next();

            // 属性の取得
            Long itemId = item.getItemId();
            String itemName = item.getName();
            String desc = item.getDescription();

            // 属性の表示
            logger.info("====== " + searchString + "品目検索結果" + " ======");
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + desc);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 10) 注文(Order)IDでの注文詳細検索
    public long findOrderDetailFor(Long orderId) throws FinderException {
        logger.debug("findOrderFor() is called");

        long startTime = System.currentTimeMillis();

        // 主キーによるOrderの検索
        OrderLocal order = (OrderLocal) (orderLocalHome.findByPrimaryKey(orderId));
        CustomerLocal customer = order.getCustomer();
        
        ItemLocal item = order.getItem();

        // 属性の取得
        Long ordId = order.getOrderId();
        Long orderQuentity = order.getQuantity();
        String shipAddr = order.getShipAddr();
        Long custId = customer.getCustId();
        String custName = customer.getName();
        String custAddress = customer.getAddr();
        Long itemId = item.getItemId();
        String itemName = item.getName();
        String itemDesc = item.getDescription();

        // 属性の表示
        logger.info("====== " + "注文ID" + orderId + "の詳細情報" + " ======");
        logger.info("注文ID: " + ordId);
        logger.info("数量: " + orderQuentity);
        logger.info("配送住所: " + shipAddr);
        logger.info("顧客ID: " + custId);
        logger.info("顧客名: " + custName);
        logger.info("顧客住所: " + custAddress);
        logger.info("品目ID: " + itemId);
        logger.info("品目名: " + itemName);
        logger.info("説明: " + itemDesc);

        return (System.currentTimeMillis() + startTime);
    }

    // 11) 注文(Order)の数量変更
    public long changeOrderQuantity(Long orderId, Long quantity)
        throws FinderException {
        logger.debug("changeOrderQuantity() is called");

        long startTime = System.currentTimeMillis();

        // 主キーによるOrderの検索
        OrderLocal order = (OrderLocal) (orderLocalHome.findByPrimaryKey(orderId));

        // 注文数量の変更
        order.setQuantity(quantity);

        // 属性の取得
        Long quant = order.getQuantity();

        // 属性の表示
        logger.info("====== " + "注文ID" + orderId + "の数量変更後情報" + " ======");
        logger.info("注文ID: " + orderId);
        logger.info("数量: " + quant);

        return (System.currentTimeMillis() + startTime);
    }

    // 12) 注文(Order)のキャンセル
    public long cancelOrder(Long orderId)
        throws FinderException, RemoveException {
        logger.debug("cancelOrder() is called");

        long startTime = System.currentTimeMillis();

        // 主キーよるOrderの検索
        OrderLocal order = (OrderLocal) (orderLocalHome.findByPrimaryKey(orderId));
        
        // Orderの削除
        order.remove();
        logger.info("====== " + "注文ID" + orderId + "はキャンセルされました" + " ======");

        return (System.currentTimeMillis() - startTime);
    }

    // LocalHome取得メソッド
    private CustomerLocalHome getCustomerLocalHome() throws NamingException {
        logger.debug("getCustomerLocalHome() is called");

        InitialContext ctx = new InitialContext();
        CustomerLocalHome ch = (CustomerLocalHome) ctx.lookup(CUSTOMER_NAME);

        return ch;
    }

    private OrderLocalHome getOrderLocalHome() throws NamingException {
        logger.debug("getOrderLocalHome() is called");

        InitialContext ctx = new InitialContext();
        OrderLocalHome oh = (OrderLocalHome) ctx.lookup(ORDER_NAME);

        return oh;
    }

    private ItemLocalHome getItemLocalHome() throws NamingException {
        logger.debug("getItemLocalHome() is called");

        InitialContext ctx = new InitialContext();
        ItemLocalHome ih = (ItemLocalHome) ctx.lookup(ITEM_NAME);

        return ih;
    }
}

コード見たいただければ分かるとおり今回は、分かりやすく1機能1メソッドとして実装し、戻り値は全てメソッド間の実行時間を返すように実装しました。またcreateNewCustomer()/createNewItem()/placeOrder()メソッドでは、其々Customer/Item/Orderを作成するメソッドですが、その主キーを生成するクラスSequenceクラスを作成し、今回は利用しました。

【コード】Sequence.java
package sample.ejb.util;

import org.apache.log4j.*;

/**
 *  シーケンスの生成
 */
public class Sequence {
    private static Logger logger = Logger.getLogger(Sequence.class);
    private static int cust_seq = 1;
    private static int item_seq = 1;
    private static int order_seq = 1;

    private Sequence() {
      logger.debug("Constructor is called");
    }

    public static synchronized Long getNextCustId() {
        logger.debug("getNextCustId() is called");
        logger.debug("Customer ID is " + cust_seq);
        return new Long(cust_seq++);
    }

    public static synchronized Long getNextItemId() {
        logger.debug("getNextItemId() is called");
        logger.debug("Item ID is " + item_seq);
          return new Long(item_seq++);
    }

    public static synchronized Long getNextOrderId() {
        logger.debug("getNextOrderId() is called");
        logger.debug("Order ID is " + order_seq);
        return new Long(order_seq++);
    }
}

もちろんこれでは、アプリケーションサーバを停止するとシーケンスが1に戻ってしまいますが、アプリケーションサーバが提供する主キーの生成機能はしたくなかったので、今回はこのようなシンプルな形にしました。


  デプロイメントディスクリプタのサンプル

EJBのソースコード内には記述しない環境に依存する情報等を設定するデプロイメントディスクリプタを作成します。


J2EE汎用のデプロイメントディスクリプタ

EJB2.x仕様で定めされたejb-jar.xmlという名前のデプロイメントディスクリプタを作成します。

【DD】ejb-jar.xml
<?xml version = '1.0' encoding = 'windows-31j'?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
    "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
  <enterprise-beans>
    <!-- Session Bean(Bussiness) -->
    <!-- OrderSystem -->
    <session>
      <description>Session Bean (ステートレス)</description>
      <display-name>OrderSystem</display-name>
      <ejb-name>OrderSystem</ejb-name>
      <home>sample.ejb.business.OrderSystemHome</home>
      <remote>sample.ejb.business.OrderSystem</remote>
      <ejb-class>sample.ejb.business.OrderSystemBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
      <ejb-local-ref>
        <ejb-ref-name>ejb/OrderBean</ejb-ref-name>
        <ejb-ref-type>Entity</ejb-ref-type>
        <local-home>sample.ejb.domain.OrderLocalHome</local-home>
        <local>sample.ejb.domain.OrderLocal</local>
        <ejb-link>Order</ejb-link>
      </ejb-local-ref>
      <ejb-local-ref>
        <ejb-ref-name>ejb/ItemBean</ejb-ref-name>
        <ejb-ref-type>Entity</ejb-ref-type>
        <local-home>sample.ejb.domain.ItemLocalHome</local-home>
        <local>sample.ejb.domain.ItemLocal</local>
        <ejb-link>Item</ejb-link>
      </ejb-local-ref>
      <ejb-local-ref>
        <ejb-ref-name>ejb/CustomerBean</ejb-ref-name>
        <ejb-ref-type>Entity</ejb-ref-type>
        <local-home>sample.ejb.domain.CustomerLocalHome</local-home>
        <local>sample.ejb.domain.CustomerLocal</local>
        <ejb-link>Customer</ejb-link>
      </ejb-local-ref>
    </session>
    <!-- Entity Bean(Domain) -->
    <!-- Customer -->
    <entity>
      <description>Entity Bean(CMP)</description>
      <display-name>Customer</display-name>
      <ejb-name>Customer</ejb-name>
      <local-home>sample.ejb.domain.CustomerLocalHome</local-home>
      <local>sample.ejb.domain.CustomerLocal</local>
      <ejb-class>sample.ejb.domain.CustomerBean</ejb-class>
      <persistence-type>Container</persistence-type>
      <prim-key-class>java.lang.Long</prim-key-class>
      <reentrant>False</reentrant>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>Customer</abstract-schema-name>
      <cmp-field>
        <field-name>custId</field-name>
      </cmp-field>
      <cmp-field>
        <field-name>addr</field-name>
      </cmp-field>
      <cmp-field>
        <field-name>name</field-name>
      </cmp-field>
      <primkey-field>custId</primkey-field>
      <query>
        <query-method>
          <method-name>findAll</method-name>
          <method-params/>
        </query-method>
        <ejb-ql>select object(o) from Customer o</ejb-ql>
      </query>
      <query>
        <query-method>
          <method-name>findAllByName</method-name>
          <method-params>
            <method-param>java.lang.String</method-param>
          </method-params>
        </query-method>
        <ejb-ql>select object(o) from Customer o where o.name like 'Dummy'</ejb-ql>
      </query>
    </entity>
    <!-- Item -->
    <entity>
      <description>Entity Bean(CMP)</description>
      <display-name>Item</display-name>
      <ejb-name>Item</ejb-name>
      <local-home>sample.ejb.domain.ItemLocalHome</local-home>
      <local>sample.ejb.domain.ItemLocal</local>
      <ejb-class>sample.ejb.domain.ItemBean</ejb-class>
      <persistence-type>Container</persistence-type>
      <prim-key-class>java.lang.Long</prim-key-class>
      <reentrant>False</reentrant>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>Item</abstract-schema-name>
      <cmp-field>
        <field-name>itemId</field-name>
      </cmp-field>
      <cmp-field>
        <field-name>name</field-name>
      </cmp-field>
      <cmp-field>
        <field-name>description</field-name>
      </cmp-field>
      <primkey-field>itemId</primkey-field>
      <query>
        <query-method>
          <method-name>findAll</method-name>
          <method-params/>
        </query-method>
        <ejb-ql>select object(o) from Item o</ejb-ql>
      </query>
      <query>
        <query-method>
          <method-name>findAllByName</method-name>
          <method-params>
            <method-param>java.lang.String</method-param>
          </method-params>
        </query-method>
        <ejb-ql>select object(o) from Item o where o.name like 'Dummy'</ejb-ql>
      </query>
    </entity>
    <!-- Order -->
    <entity>
      <description>Entity Bean(CMP)</description>
      <display-name>Order</display-name>
      <ejb-name>Order</ejb-name>
      <local-home>sample.ejb.domain.OrderLocalHome</local-home>
      <local>sample.ejb.domain.OrderLocal</local>
      <ejb-class>sample.ejb.domain.OrderBean</ejb-class>
      <persistence-type>Container</persistence-type>
      <prim-key-class>java.lang.Long</prim-key-class>
      <reentrant>False</reentrant>
      <cmp-version>2.x</cmp-version>
      <abstract-schema-name>Order</abstract-schema-name>
      <cmp-field>
        <field-name>orderId</field-name>
      </cmp-field>
      <cmp-field>
        <field-name>shipAddr</field-name>
      </cmp-field>
      <cmp-field>
        <field-name>quantity</field-name>
      </cmp-field>
      <primkey-field>orderId</primkey-field>
      <query>
        <query-method>
          <method-name>findAll</method-name>
          <method-params/>
        </query-method>
        <ejb-ql>select object(o) from Order o</ejb-ql>
      </query>
      <query>
        <query-method>
          <method-name>findAllByItem</method-name>
          <method-params>
            <method-param>java.lang.Long</method-param>
          </method-params>
        </query-method>
        <ejb-ql>select object(o) from Order o where o.item.itemId = ?1</ejb-ql>
      </query>
    </entity>
  </enterprise-beans>
  <!-- Relationship Configration -->
  <relationships>
    <ejb-relation>
      <ejb-relation-name>Order - Customer</ejb-relation-name>
      <ejb-relationship-role>
        <ejb-relationship-role-name>Order may have one Customer</ejb-relationship-role-name>
        <multiplicity>Many</multiplicity>
        <relationship-role-source>
          <ejb-name>Order</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>customer</cmr-field-name>
        </cmr-field>
      </ejb-relationship-role>
      <ejb-relationship-role>
        <ejb-relationship-role-name>Customer may have many Order</ejb-relationship-role-name>
        <multiplicity>One</multiplicity>
        <relationship-role-source>
          <ejb-name>Customer</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>orders</cmr-field-name>
          <cmr-field-type>java.util.Collection</cmr-field-type>
        </cmr-field>
      </ejb-relationship-role>
    </ejb-relation>
    <ejb-relation>
      <ejb-relation-name>Order - Item</ejb-relation-name>
      <ejb-relationship-role>
        <ejb-relationship-role-name>Order may have one Item</ejb-relationship-role-name>
        <multiplicity>Many</multiplicity>
        <relationship-role-source>
          <ejb-name>Order</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>item</cmr-field-name>
        </cmr-field>
      </ejb-relationship-role>
      <ejb-relationship-role>
        <ejb-relationship-role-name>Item may have many Order</ejb-relationship-role-name>
        <multiplicity>One</multiplicity>
        <relationship-role-source>
          <ejb-name>Item</ejb-name>
        </relationship-role-source>
        <cmr-field>
          <cmr-field-name>orders</cmr-field-name>
          <cmr-field-type>java.util.Collection</cmr-field-type>
        </cmr-field>
      </ejb-relationship-role>
    </ejb-relation>
  </relationships>
  <!-- Transaction Configuration -->
  <assembly-descriptor>
    <container-transaction>
      <method>
        <ejb-name>Customer</ejb-name>
        <method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>
    <container-transaction>
      <method>
        <ejb-name>Item</ejb-name>
        <method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>
    <container-transaction>
      <method>
        <ejb-name>Order</ejb-name>
        <method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>
    <container-transaction>
      <method>
        <ejb-name>OrderSystem</ejb-name>
        <method-name>*</method-name>
      </method>
      <trans-attribute>Required</trans-attribute>
    </container-transaction>
  </assembly-descriptor>
</ejb-jar>

ejb-jar.xmlの中で特に注意する点は、EJB-QLの書き方です。EJB-QL内でlikeを利用したい時、なんとなく下記の様な書式で、

<ejb-ql>select object(o) from Customer o where o.name like $1</ejb-ql>

$1を利用してしまいがちですが、EJB仕様上likeの後にはリテラルのみを続ける事ができます(いいのかな・・・)。Oracle Application Server 10g(10.1.2)の実装では、EJB仕様に厳密に従い、以下の様に記述します。

<ejb-ql>select object(o) from Customer o where o.name like ‘Dummy’</ejb-ql>

後は、アプリケーションサーバ固有のデプロイメントディスクリプタorion-ejb-jar.xmlに残りの記述を行います。(後述)


EJBコンテナ固有のデプロイメントディスクリプタ

EJB2.x仕様でカバーしきれないEJBコンテナ(アプリケーションサーバ)固有のデプロイメントしスクリプタを作成します。下記の例は、Oracle Application Server 10g(10.1.2)の例です。EJB CMPの場合には、データベースとのマッピングは、このEJBコンテナ固有のデプロイメントディスクリプタ内に記述します。

【DD】orion-ejb-jar.xml
<?xml version = '1.0' encoding = 'windows-31j'?>
<!DOCTYPE orion-ejb-jar PUBLIC "-//Evermind//DTD Enterprise JavaBeans 1.1 runtime//EN"
    "http://xmlns.oracle.com/ias/dtds/orion-ejb-jar.dtd">
<orion-ejb-jar>
  <enterprise-beans>
    <!-- Session Bean(Businness) -->
    <!-- OrderSystem -->
    <session-deployment name="OrderSystem"/>
    <!-- Entity Bean(Domain) -->
    <!-- Customer -->
    <entity-deployment name="Customer" data-source="jdbc/OTNUSERDS"
                       table="OTNUSER.CUSTOMER">
      <primkey-mapping>
        <cmp-field-mapping name="custId" persistence-name="CUST_ID"/>
      </primkey-mapping>
      <cmp-field-mapping name="custId" persistence-name="CUST_ID"
                         persistence-type="NUMBER(20)"/>
      <cmp-field-mapping name="addr" persistence-name="ADDR"
                         persistence-type="VARCHAR2(80)"/>
      <cmp-field-mapping name="name" persistence-name="NAME"
                         persistence-type="VARCHAR2(80)"/>
      <cmp-field-mapping name="orders">
        <collection-mapping table="OTNUSER.CORDER">
          <primkey-mapping>
            <cmp-field-mapping>
              <entity-ref home="Customer">
                <cmp-field-mapping name="customer_custId_custId"
                                   persistence-name="CUST_ID"
                                   persistence-type="NUMBER(20)"/>
              </entity-ref>
            </cmp-field-mapping>
          </primkey-mapping>
          <value-mapping type="sample.ejb.domain.OrderLocal" immutable="true">
            <cmp-field-mapping>
              <entity-ref home="Order">
                <cmp-field-mapping name="order_custId_orderId"
                                   persistence-name="ORDER_ID"
                                   persistence-type="NUMBER(20)"/>
              </entity-ref>
            </cmp-field-mapping>
          </value-mapping>
        </collection-mapping>
      </cmp-field-mapping>
      <finder-method query="$name like $1">
        <method>
          <ejb-name>Customer</ejb-name>
          <method-name>findAllByName</method-name>
          <method-params>
            <method-param>java.lang.String</method-param>
          </method-params>
        </method>
      </finder-method>
    </entity-deployment>
    <!-- Item -->
    <entity-deployment name="Item" data-source="jdbc/OTNUSERDS"
                       table="OTNUSER.ITEM">
      <primkey-mapping>
        <cmp-field-mapping name="itemId" persistence-name="ITEM_ID"/>
      </primkey-mapping>
      <cmp-field-mapping name="itemId" persistence-name="ITEM_ID"
                         persistence-type="NUMBER(20)"/>
      <cmp-field-mapping name="name" persistence-name="NAME"
                         persistence-type="VARCHAR2(80)"/>
      <cmp-field-mapping name="description" persistence-name="DESCRIPTION"
                         persistence-type="VARCHAR2(80)"/>
      <cmp-field-mapping name="orders">
        <collection-mapping table="OTNUSER.CORDER">
          <primkey-mapping>
            <cmp-field-mapping>
              <entity-ref home="Item">
                <cmp-field-mapping name="item_itemId_itemId"
                                   persistence-name="ITEM_ID"
                                   persistence-type="NUMBER(20)"/>
              </entity-ref>
            </cmp-field-mapping>
          </primkey-mapping>
          <value-mapping type="sample.ejb.domain.OrderLocal" immutable="true">
            <cmp-field-mapping>
              <entity-ref home="Order">
                <cmp-field-mapping name="order_itemId_orderId"
                                   persistence-name="ORDER_ID"
                                   persistence-type="NUMBER(20)"/>
              </entity-ref>
            </cmp-field-mapping>
          </value-mapping>
        </collection-mapping>
      </cmp-field-mapping>
      <finder-method query="$name like $1">
        <method>
          <ejb-name>Item</ejb-name>
          <method-name>findAllByName</method-name>
          <method-params>
            <method-param>java.lang.String</method-param>
          </method-params>
        </method>
      </finder-method>
    </entity-deployment>
    <!-- Order -->
    <entity-deployment name="Order" data-source="jdbc/OTNUSERDS"
                       table="OTNUSER.CORDER">
      <primkey-mapping>
        <cmp-field-mapping name="orderId" persistence-name="ORDER_ID"/>
      </primkey-mapping>
      <cmp-field-mapping name="orderId" persistence-name="ORDER_ID"
                         persistence-type="NUMBER(20)"/>
      <cmp-field-mapping name="shipAddr" persistence-name="SHIP_ADDR"
                         persistence-type="VARCHAR2(80)"/>
      <cmp-field-mapping name="quantity" persistence-name="QUANTITY"
                         persistence-type="NUMBER(20)"/>
      <cmp-field-mapping name="customer" persistence-name="CUST_ID">
        <entity-ref home="Customer">
          <cmp-field-mapping persistence-name="CUST_ID"
                             persistence-type="NUMBER(20)"/>
        </entity-ref>
      </cmp-field-mapping>
      <cmp-field-mapping name="item" persistence-name="ITEM_ID">
        <entity-ref home="Item">
          <cmp-field-mapping persistence-name="ITEM_ID"
                             persistence-type="NUMBER(20)"/>
        </entity-ref>
      </cmp-field-mapping>
    </entity-deployment>
  </enterprise-beans>
</orion-ejb-jar>

先ほどのEJB-QLのlikeの残りの設定は、以下の様になります。

<finder-method query="$name like $1">
        <method>
          <ejb-name>Item</ejb-name>
          <method-name>findAllByName</method-name>
          <method-params>
            <method-param>java.lang.String</method-param>
          </method-params>
        </method>
</finder-method>

当たり前ですが本来ejb-jar.xmlで指定されるべき物が指定できないようにEJB仕様上なってしまっています・・・


 POJO(OracleAS TopLink)

OracleAS TopLinkは、POJO(Plain Old Java Object)を用いてO/Rマッピングを行う事の出来る代表的な製品です(World Wideでは・・・)。今回はTopLinkを用いたPOJOの実装サンプルを作成します。
作成するPOJOとコンポーネント(EJB)は、以下の4つになります。

[POJO]
  1) CUSTOMERテーブルに対応したドメインクラスCustomer
  2) ITEMテーブルに対応したドメインクラスItem
  3) CORDERテーブルに対応したドメインクラスOrder
[コンポーネント]
  4) 12の機能(要件)を満たし、クライアントからのインタフェースとなるOrderSystemBean - Stateless Session Bean

CustomerとOrderは、1対多の双方向の関連をもち、ItemとOrderも1対多の双方向の関連をもちます。

またTopLinkを用いCustomer/Item/OrderにアクセスするPOJOであるドメインクラスを開発するには以下のファイルを作成する必要があります。

ファイル 役割
ドメインクラス POJO(Plain Old Java Object)として実装
プロジェクトデプロイメントXML POJO(オブジェクト)とリレーショナルデータのマッピング等を定義したメタデータ
session.xml TopLinkの実行環境情報を定義したメタデータ


  Customerサンプル

CUSTOMERテーブルに対応したドメインクラスのPOJOです。

【コード】Customer..java
package sample.toplink.domain;

import java.io.*;
import java.util.*;

/**
 *  CustomerのPOJO
 */
public class Customer implements Serializable {
    private Collection orders;
    private Long custId;
    private String name;
    private String addr;

    // コンストラクタ
    public Customer() {
        super();
        this.orders = new Vector();
    }
    
    public Customer(Long custId, String name, String addr) {
        this.custId = custId;
        this.name = name;
        this.addr = addr;
        this.orders = new Vector();
    }

    public void addOrder(Order order) {
        this.orders.add(order);
    }

    // Setter/Getter
    public void setCustId(Long custId) {
        this.custId = custId;
    }

    public Long getCustId() {
        return this.custId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public String getAddr() {
        return this.addr;
    }

    public void setOrders(Collection orders) {
        this.orders = orders;
    }

    public Collection getOrders() {
        return this.orders;
    }

    public void removeFromOrderCollection(Order order) {
        this.orders.remove(order);
    }
}


  Itemサンプル

ITEMテーブルに対応したドメインクラスのPOJOです。

【コード】Item.java
package sample.toplink.domain;

import java.io.*;
import java.util.*;

/**
 *  ItemのPOJO
 */
public class Item implements Serializable {
    private Collection orders;
    private Long itemId;
    private String name;
    private String description;

    // コンストラクタ
    public Item() {
        super();
        this.orders = new Vector();
    }
    
    public Item(Long itemId, String name, String description) {
      this.itemId = itemId;
      this.name = name;
      this.description = description;
      this.orders = new Vector();
    }

    public void addOrder(Order order) {
        this.orders.add(order);
    }

    // Setter/Getter
    public void setItemId(Long itemId) {
        this.itemId = itemId;
    }

    public Long getItemId() {
        return this.itemId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return this.description;
    }

    public void setOrders(Collection orders) {
        this.orders = orders;
    }

    public Collection getOrders() {
        return this.orders;
    }

    public void removeFromOrders(Order order) {
        this.orders.remove(order);
    }
}


  Order.java

CORDERテーブルに対応したドメインクラスPOJOです。

【コード】Order.java
package sample.toplink.domain;

import oracle.toplink.indirection.*;
import java.io.*;

/**
 *  OrderのPOJO
 */
public class Order implements Serializable {
    private ValueHolderInterface item;
    private ValueHolderInterface customer;
    private Long orderId;
    private String shipAddr;
    private Long quantity;

    // コンストラクタ
    public Order() {
        super();
        this.customer = new ValueHolder();
        this.item = new ValueHolder();
    }
    
    public Order(Long orderId, Long quantity, String shippAddr) {
        this.orderId = orderId;
        this.quantity = quantity;
        this.shipAddr = shippAddr;
        this.customer = new ValueHolder();
        this.item = new ValueHolder();
    }

    // Setter/Getter
    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getOrderId() {
        return this.orderId;
    }

    public void setShipAddr(String shipAddr) {
        this.shipAddr = shipAddr;
    }

    public String getShipAddr() {
        return this.shipAddr;
    }

    public void setQuantity(Long quantity) {
        this.quantity = quantity;
    }

    public Long getQuantity() {
        return this.quantity;
    }

    public void setCust(Customer cust) {
        this.customer.setValue(cust);
    }

    public Customer getCust() {
        return (Customer) this.customer.getValue();
    }

    public void setItem(Item item) {
        this.item.setValue(item);
    }

    public Item getItem() {
        return (Item) this.item.getValue();
    }
}



  プロジェクトデプロイメントXMLサンプル

TopLinkのマッピングメタデータであるプロジェクトXMLを作成します。今回はOracle JDeveloper 10g(10.1.2)に含まれたMapping Workbenchの機能を利用し作成しました。

【コード】toplink-deployment-descriptor.xml
<?xml version = '1.0' encoding = 'UTF-8'?>
<project>
   <project-name>toplink_mappings</project-name>
   <login>
      <database-login>
         <platform>oracle.toplink.oraclespecific.Oracle9Platform</platform>
         <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
         <connection-url>jdbc:oracle:thin:@localhost:1521:orcl</connection-url>
         <user-name>otnuser</user-name>
         <password>8C235AE69AF16FA31D9169204321682D</password>
         <uses-native-sequencing>false</uses-native-sequencing>
         <sequence-preallocation-size>50</sequence-preallocation-size>
         <sequence-table>SEQUENCE</sequence-table>
         <sequence-name-field>SEQ_NAME</sequence-name-field>
         <sequence-counter-field>SEQ_COUNT</sequence-counter-field>
         <should-bind-all-parameters>false</should-bind-all-parameters>
         <should-cache-all-statements>false</should-cache-all-statements>
         <uses-byte-array-binding>true</uses-byte-array-binding>
         <uses-string-binding>false</uses-string-binding>
         <uses-streams-for-binding>false</uses-streams-for-binding>
         <should-force-field-names-to-upper-case>false
         </should-force-field-names-to-upper-case>
         <should-optimize-data-conversion>true
         </should-optimize-data-conversion>
         <should-trim-strings>true</should-trim-strings>
         <uses-batch-writing>false</uses-batch-writing>
         <uses-jdbc-batch-writing>true</uses-jdbc-batch-writing>
         <uses-external-connection-pooling>false
         </uses-external-connection-pooling>
         <uses-external-transaction-controller>false
         </uses-external-transaction-controller>
         <type>oracle.toplink.sessions.DatabaseLogin</type>
      </database-login>
   </login>
   <descriptors>
      <descriptor>
         <java-class>sample.toplink.domain.Customer</java-class>
         <tables>
            <table>OTNUSER.CUSTOMER</table>
         </tables>
         <primary-key-fields>
            <field>OTNUSER.CUSTOMER.CUST_ID</field>
         </primary-key-fields>
         <descriptor-type-value>Normal</descriptor-type-value>
         <identity-map-class>
             oracle.toplink.internal.identitymaps.SoftCacheWeakIdentityMap
         </identity-map-class>
         <remote-identity-map-class>
             oracle.toplink.internal.identitymaps.SoftCacheWeakIdentityMap
         </remote-identity-map-class>
         <identity-map-size>100</identity-map-size>
         <remote-identity-map-size>100</remote-identity-map-size>
         <should-always-refresh-cache>false</should-always-refresh-cache>
         <should-always-refresh-cache-on-remote>false
         </should-always-refresh-cache-on-remote>
         <should-only-refresh-cache-if-newer-version>false
         </should-only-refresh-cache-if-newer-version>
         <should-disable-cache-hits>false</should-disable-cache-hits>
         <should-disable-cache-hits-on-remote>false
         </should-disable-cache-hits-on-remote>
         <alias>Customer</alias>
         <copy-policy>
            <descriptor-copy-policy>
               <type>oracle.toplink.internal.descriptors.CopyPolicy</type>
            </descriptor-copy-policy>
         </copy-policy>
         <instantiation-policy>
            <descriptor-instantiation-policy>
               <type>oracle.toplink.internal.descriptors.InstantiationPolicy
               </type>
            </descriptor-instantiation-policy>
         </instantiation-policy>
         <query-manager>
            <descriptor-query-manager>
               <existence-check>Check cache</existence-check>
            </descriptor-query-manager>
         </query-manager>
         <event-manager>
            <descriptor-event-manager empty-aggregate="true"/>
         </event-manager>
         <mappings>
            <database-mapping>
               <attribute-name>addr</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.CUSTOMER.ADDR</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>custId</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.CUSTOMER.CUST_ID</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>name</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.CUSTOMER.NAME</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>orders</attribute-name>
               <read-only>false</read-only>
               <reference-class>sample.toplink.domain.Order</reference-class>
               <is-private-owned>false</is-private-owned>
               <uses-batch-reading>false</uses-batch-reading>
               <indirection-policy>
                  <mapping-indirection-policy>
                     <type>
                         oracle.toplink.internal.indirection.TransparentIndirectionPolicy
                     </type>
                  </mapping-indirection-policy>
               </indirection-policy>
               <container-policy>
                  <mapping-container-policy>
                     <container-class>
                         oracle.toplink.indirection.IndirectList
                     </container-class>
                     <type>
                         oracle.toplink.internal.queryframework.ListContainerPolicy
                     </type>
                  </mapping-container-policy>
               </container-policy>
               <source-key-fields>
                  <field>OTNUSER.CUSTOMER.CUST_ID</field>
               </source-key-fields>
               <target-foreign-key-fields>
                  <field>OTNUSER.CORDER.CUST_ID</field>
               </target-foreign-key-fields>
               <type>oracle.toplink.mappings.OneToManyMapping</type>
            </database-mapping>
         </mappings>
         <type>oracle.toplink.publicinterface.Descriptor</type>
      </descriptor>
      <descriptor>
         <java-class>sample.toplink.domain.Item</java-class>
         <tables>
            <table>OTNUSER.ITEM</table>
         </tables>
         <primary-key-fields>
            <field>OTNUSER.ITEM.ITEM_ID</field>
         </primary-key-fields>
         <descriptor-type-value>Normal</descriptor-type-value>
         <identity-map-class>
             oracle.toplink.internal.identitymaps.SoftCacheWeakIdentityMap
         </identity-map-class>
         <remote-identity-map-class>
             oracle.toplink.internal.identitymaps.SoftCacheWeakIdentityMap
         </remote-identity-map-class>
         <identity-map-size>100</identity-map-size>
         <remote-identity-map-size>100</remote-identity-map-size>
         <should-always-refresh-cache>false</should-always-refresh-cache>
         <should-always-refresh-cache-on-remote>false
         </should-always-refresh-cache-on-remote>
         <should-only-refresh-cache-if-newer-version>false
         </should-only-refresh-cache-if-newer-version>
         <should-disable-cache-hits>false</should-disable-cache-hits>
         <should-disable-cache-hits-on-remote>false
         </should-disable-cache-hits-on-remote>
         <alias>Item</alias>
         <copy-policy>
            <descriptor-copy-policy>
               <type>
                   oracle.toplink.internal.descriptors.CopyPolicy
               </type>
            </descriptor-copy-policy>
         </copy-policy>
         <instantiation-policy>
            <descriptor-instantiation-policy>
               <type>
                   oracle.toplink.internal.descriptors.InstantiationPolicy
               </type>
            </descriptor-instantiation-policy>
         </instantiation-policy>
         <query-manager>
            <descriptor-query-manager>
               <existence-check>Check cache</existence-check>
            </descriptor-query-manager>
         </query-manager>
         <event-manager>
            <descriptor-event-manager empty-aggregate="true"/>
         </event-manager>
         <mappings>
            <database-mapping>
               <attribute-name>description</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.ITEM.DESCRIPTION</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>itemId</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.ITEM.ITEM_ID</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>name</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.ITEM.NAME</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>orders</attribute-name>
               <read-only>false</read-only>
               <reference-class>sample.toplink.domain.Order</reference-class>
               <is-private-owned>false</is-private-owned>
               <uses-batch-reading>false</uses-batch-reading>
               <indirection-policy>
                  <mapping-indirection-policy>
                     <type>
                         oracle.toplink.internal.indirection.TransparentIndirectionPolicy
                     </type>
                  </mapping-indirection-policy>
               </indirection-policy>
               <container-policy>
                  <mapping-container-policy>
                     <container-class>
                         oracle.toplink.indirection.IndirectList
                     </container-class>
                     <type>
                         oracle.toplink.internal.queryframework.ListContainerPolicy
                     </type>
                  </mapping-container-policy>
               </container-policy>
               <source-key-fields>
                  <field>OTNUSER.ITEM.ITEM_ID</field>
               </source-key-fields>
               <target-foreign-key-fields>
                  <field>OTNUSER.CORDER.ITEM_ID</field>
               </target-foreign-key-fields>
               <type>oracle.toplink.mappings.OneToManyMapping</type>
            </database-mapping>
         </mappings>
         <type>oracle.toplink.publicinterface.Descriptor</type>
      </descriptor>
      <descriptor>
         <java-class>sample.toplink.domain.Order</java-class>
         <tables>
            <table>OTNUSER.CORDER</table>
         </tables>
         <primary-key-fields>
            <field>OTNUSER.CORDER.ORDER_ID</field>
         </primary-key-fields>
         <descriptor-type-value>Normal</descriptor-type-value>
         <identity-map-class>
             oracle.toplink.internal.identitymaps.SoftCacheWeakIdentityMap
         </identity-map-class>
         <remote-identity-map-class>
             oracle.toplink.internal.identitymaps.SoftCacheWeakIdentityMap
         </remote-identity-map-class>
         <identity-map-size>100</identity-map-size>
         <remote-identity-map-size>100</remote-identity-map-size>
         <should-always-refresh-cache>false</should-always-refresh-cache>
         <should-always-refresh-cache-on-remote>false
         </should-always-refresh-cache-on-remote>
         <should-only-refresh-cache-if-newer-version>false
         </should-only-refresh-cache-if-newer-version>
         <should-disable-cache-hits>false</should-disable-cache-hits>
         <should-disable-cache-hits-on-remote>false
         </should-disable-cache-hits-on-remote>
         <alias>Order</alias>
         <copy-policy>
            <descriptor-copy-policy>
               <type>
                   oracle.toplink.internal.descriptors.CopyPolicy
               </type>
            </descriptor-copy-policy>
         </copy-policy>
         <instantiation-policy>
            <descriptor-instantiation-policy>
               <type>
                   oracle.toplink.internal.descriptors.InstantiationPolicy
               </type>
            </descriptor-instantiation-policy>
         </instantiation-policy>
         <query-manager>
            <descriptor-query-manager>
               <existence-check>Check cache</existence-check>
            </descriptor-query-manager>
         </query-manager>
         <event-manager>
            <descriptor-event-manager empty-aggregate="true"/>
         </event-manager>
         <mappings>
            <database-mapping>
               <attribute-name>customer</attribute-name>
               <read-only>false</read-only>
               <reference-class>sample.toplink.domain.Customer</reference-class>
               <is-private-owned>false</is-private-owned>
               <uses-batch-reading>false</uses-batch-reading>
               <indirection-policy>
                  <mapping-indirection-policy>
                     <type>
                         oracle.toplink.internal.indirection.BasicIndirectionPolicy
                     </type>
                  </mapping-indirection-policy>
               </indirection-policy>
               <uses-joining>false</uses-joining>
               <foreign-key-fields>
                  <field>OTNUSER.CORDER.CUST_ID</field>
               </foreign-key-fields>
               <source-to-target-key-field-associations>
                  <association>
                     <association-key>OTNUSER.CORDER.CUST_ID</association-key>
                     <association-value>OTNUSER.CUSTOMER.CUST_ID</association-value>
                  </association>
               </source-to-target-key-field-associations>
               <type>oracle.toplink.mappings.OneToOneMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>item</attribute-name>
               <read-only>false</read-only>
               <reference-class>sample.toplink.domain.Item</reference-class>
               <is-private-owned>false</is-private-owned>
               <uses-batch-reading>false</uses-batch-reading>
               <indirection-policy>
                  <mapping-indirection-policy>
                     <type>
                         oracle.toplink.internal.indirection.BasicIndirectionPolicy
                     </type>
                  </mapping-indirection-policy>
               </indirection-policy>
               <uses-joining>false</uses-joining>
               <foreign-key-fields>
                  <field>OTNUSER.CORDER.ITEM_ID</field>
               </foreign-key-fields>
               <source-to-target-key-field-associations>
                  <association>
                     <association-key>OTNUSER.CORDER.ITEM_ID</association-key>
                     <association-value>OTNUSER.ITEM.ITEM_ID</association-value>
                  </association>
               </source-to-target-key-field-associations>
               <type>oracle.toplink.mappings.OneToOneMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>orderId</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.CORDER.ORDER_ID</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>quantity</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.CORDER.QUANTITY</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
            <database-mapping>
               <attribute-name>shipAddr</attribute-name>
               <read-only>false</read-only>
               <field-name>OTNUSER.CORDER.SHIP_ADDR</field-name>
               <type>oracle.toplink.mappings.DirectToFieldMapping</type>
            </database-mapping>
         </mappings>
         <type>oracle.toplink.publicinterface.Descriptor</type>
      </descriptor>
   </descriptors>
</project>

随分長いマッピングメタデータなのでなにやら設定が大変そうですが、実際にはOracle JDeveloper 10g(10.1.2)の中に含まれるMapping Workbenchの機能や、単体のMapping Workbenchといった完全で使いやすいGUIが提供されていますので、間単に作成・編集をすることが出来ます。


  session.xmlファイルサンプル

TopLinkの実行環境を定義したsession.xmlを作成します。作成したsession.xmlはCLASSPATHの通った場所に配置する必要があります。

【コード】session.xml
<?xml version = '1.0' encoding = 'UTF-8'?>
<!DOCTYPE toplink-configuration PUBLIC
    "-//Oracle Corp.//DTD TopLink Sessions 9.0.4//EN" "sessions_9_0_4.dtd">
<toplink-configuration>
   <session>
      <name>pojoSession</name>
      <project-xml>
          META-INF/TopLink/toplink-deployment-descriptor.xml
      </project-xml>
      <session-type>
         <server-session/>
      </session-type>
      <login>
         <datasource>jdbc/OTNUSERDS</datasource>
         <uses-external-connection-pool>true
         </uses-external-connection-pool>
         <uses-external-transaction-controller>true
         </uses-external-transaction-controller>
      </login>
      <external-transaction-controller-class>
          oracle.toplink.jts.oracle9i.Oracle9iJTSExternalTransactionController
      </external-transaction-controller-class>
   </session>
</toplink-configuration>

こちらもプロジェクトデプロイメントXML同様に、直接編集し作成する必要はなく、Oracle JDeveloper 10g(10.1.2)に含まれるSession Editorの機能や、単体のSession Editorといった完全で使いやすいGUIにより簡単に作成・編集が行えます。
設定のポイントは、アプリケーションサーバのコネクションプールを利用できるように<datasource>タグでデータソースの設定がされ、<uses-external-connection-pool>タグにより有効の設定がされている、というのと。ToplinkのトランザクションがJTAトランザクションの包含されるように、<external-transaction-controller-class>タグに外部コントローラのクラスが指定してあり、<uses-external-transaction-controller>タグにより、有効の設定がされている、という事です。



  OrderSystemBeanサンプル

POJOであるCustomer/Item/Orderにアクセスするファサード的な役割を持ち、12の機能(要件)を実装したStateless Session Beanです。メソッド命名規則はEJB2.xのOrderSystemBeanのサンプルと同様にしてあります。


Homeインタフェース

OrderSystemはSession Beanであり、リモートからのアクセスを想定しているのでjavax.ejb.EJBHomeを実装します。

// OrderSystemHome.java
package sample.toplink.business;

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

/**
 *  OrderSystemBeanのHomeインタフェース
 */
public interface OrderSystemHome extends EJBHome {
    // コンポーネントJNDI名
    public static final String COMP_NAME = "java:comp/env/ejb/OrderSystem";
    OrderSystem create() throws RemoteException, CreateException;
}


コンポーネントインタフェース

OrderSystemはSession Beanであり、リモートからのアクセスを想定しているのでjavax.ejb.EJBObjectを実装します。

// OrderSystem.java
package sample.toplink.business;

import java.math.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

/**
 *  OrderSystemBeanのコンポーネントインタフェース
 */
public interface OrderSystem extends EJBObject {
    public long createNewCustomer(String name, String address)
        throws RemoteException;

    public long createNewItem(String name, String description)
        throws RemoteException;

    public long placeOrder(Long customerId, Long itemId, Long quantity)
        throws RemoteException;

    public long getAllCustomers() throws RemoteException;

    public long getAllItems() throws RemoteException;

    public long getOrdersForCustomer(Long customerId) throws RemoteException;

    public long getOrdersForItem(Long itemId) throws RemoteException;

    public long getMatchingCustomers(String searchString)
        throws RemoteException;

    public long getMatchingItems(String searchString) throws RemoteException;

    public long findOrderDetailFor(Long orderId)
        throws RemoteException, FinderException;

    public long changeOrderQuantity(Long orderId, Long quantity)
        throws RemoteException, FinderException;


    public long cancelOrder(Long orderId) throws RemoteException;
}


Beanクラス

OrderSystemはSession Beanなのでjavax.ejb. SessionBeanを実装します。

// OrderSystemBean
package sample.toplink.business;

import oracle.toplink.expressions.*;
import oracle.toplink.queryframework.*;
import oracle.toplink.sessions.*;
import oracle.toplink.threetier.*;
import oracle.toplink.tools.sessionmanagement.*;
import org.apache.log4j.*;
import sample.toplink.domain.*;
import sample.toplink.util.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

/**
 *  OrderSystemBeanのBeanクラス
 */
public class OrderSystemBean implements SessionBean {
    // session.xmlに定義したセッション名
    private static final String SESSION_NAME = "pojoSession";
    // Loggerの取得
    private static Logger logger = Logger.getLogger(OrderSystemBean.class);
    // サーバセッション
    private Server serverSession = null;

    public void ejbCreate() throws CreateException {
        logger.debug("ejbCreate() is called");

        // サーバセッションの取得
        serverSession = getServerSession();
    }

    public void ejbActivate() {
        logger.debug("ejbActivate() is called");
    }

    public void ejbPassivate() {
        logger.debug("ejbPassivate() is called");
    }

    public void ejbRemove() {
        logger.debug("ejbRemove() is called");
    }

    public void setSessionContext(SessionContext ctx) {
        logger.debug("setSessionContext() is called");
    }

    // 1) 顧客(Customer)の新規作成
    public long createNewCustomer(String name, String addr)
        throws RemoteException {
        logger.debug("createNewCustomer() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // UnitOfWorkの取得
        UnitOfWork uow = clientSession.getActiveUnitOfWork();

        // Customerの生成
        Customer cust = new Customer(Sequence.getNextCustId(), name, addr);

        // UnitOfWorkへの登録
        uow.registerObject(cust);

        return (System.currentTimeMillis() - startTime);
    }

    // 2) 品目(Item)の新規作成
    public long createNewItem(String name, String description) {
        logger.debug("createNewItem() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // UnitOfWorkの取得
        UnitOfWork uow = clientSession.getActiveUnitOfWork();

        // Itemの生成
        Item item = new Item(Sequence.getNextItemId(), name, description);

        // UnitOfWorkへの登録
        uow.registerObject(item);

        return (System.currentTimeMillis() - startTime);
    }

    // 3) 注文(Order)の追加
    public long placeOrder(Long custId, Long itemId, Long quantity) {
        logger.debug("placeOrder() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // UnitOfWorkの取得
        UnitOfWork uow = clientSession.getActiveUnitOfWork();

            // 主キーによるCustomerとItemの検索     
            ExpressionBuilder builder = new ExpressionBuilder();
        Expression custExp = builder.get("custId").equal(custId);
        Customer cust = (Customer) uow.readObject(Customer.class, custExp);
        Expression itemExp = builder.get("itemId").equal(itemId);
        Item item = (Item) uow.readObject(Item.class, itemExp);

        // Orderの生成
        Order order = new Order(Sequence.getNextOrderId(), quantity,
                cust.getAddr());

        order.setItem(item);
        order.setCust(cust);

        // Orderの追加
        cust.addOrder(order);

        return (System.currentTimeMillis() - startTime);
    }

    // 4) 全ての顧客(Customer)情報の取得
    public long getAllCustomers() {
        logger.debug("getAllCustomers() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        
        // Customerの全件検索によるコレクションの取得
        Collection customers = clientSession.readAllObjects(Customer.class);
        Iterator customerIterator = customers.iterator();

        // コレクションからのCustomerの取り出し
        while (customerIterator.hasNext()) {
            Customer customer = (Customer) customerIterator.next();

            // 属性の取得
            Long custId = customer.getCustId();
            String custName = customer.getName();
            String addr = customer.getAddr();

            // 属性の表示
            logger.info("====== 顧客情報 ======");
            logger.info("顧客ID: " + custId);
            logger.info("顧客名: " + custName);
            logger.info("住所: " + addr);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 5) 全ての品目(Item)の取得
    public long getAllItems() {
        logger.debug("getAllItems() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        
        // Itemの全件検索によるコレクションの取得
        Collection items = clientSession.readAllObjects(Item.class);
        Iterator itemIterator = items.iterator();

        // コレクションからItemの取り出し
        while (itemIterator.hasNext()) {
            Item item = (Item) itemIterator.next();

            // 属性の取得
            Long itemId = item.getItemId();
            String itemName = item.getName();
            String itemDesc = item.getDescription();

            // 属性の表示
            logger.info("====== 品目情報 ======");
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + itemDesc);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 6) 顧客(Customer)に関連した注文の取得
    public long getOrdersForCustomer(Long custId) {
        logger.debug("ejbOrdersForCustomer() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();

        // 主キーによるCustomerの検索
        Expression exp = new ExpressionBuilder().get("custId").equal(custId);
        Customer customer = (Customer) clientSession.readObject(Customer.class,
                exp);

        // Orderコレクションの取得
        Collection orders = customer.getOrders();
        Iterator orderIterator = orders.iterator();

        // 属性の取得
        String custName = customer.getName();

        // コレクションからOrderの取り出し
        while (orderIterator.hasNext()) {
            Order order = (Order) orderIterator.next();
            Item item = (Item) order.getItem();

            // 属性の取得
            Long orderId = order.getOrderId();
            Long itemId = item.getItemId();
            String itemName = item.getName();
            String desc = item.getDescription();
            Long quantity = order.getQuantity();

            // 属性の表示
            logger.info("====== " + custName + "の注文" + " ======");
            logger.info("顧客名" + custName);
            logger.info("注文ID: " + orderId);
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + desc);
            logger.info("注文量: " + quantity);
        }

        return (System.currentTimeMillis() - startTime);
    }

    // 7) 品目(Item)に関連した注文の取得
    public long getOrdersForItem(Long itemId) {
        logger.debug("getOrdersForItem() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();

        // 品目IDによるOrderの検索によるコレクションの取得
        Expression exp = new ExpressionBuilder().get("itemId").equal(itemId);
        Collection orders = (Collection) clientSession.readAllObjects(Order.class, exp);
        Iterator orderIterator = orders.iterator();

        // コレクションからOrderの取り出し
        while (orderIterator.hasNext()) {
            Order order = (Order) orderIterator.next();
            Customer customer = order.getCust();
            Item item = order.getItem();

            // 属性の取得
            String custName = customer.getName();
            Long orderId = order.getOrderId();
            String itemName = item.getName();
            String desc = item.getDescription();
            Long quantity = order.getQuantity();

            // 属性の表示
            logger.info("====== " + itemName + "の注文" + " ======");
            logger.info("顧客名" + custName);
            logger.info("注文ID: " + orderId);
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + desc);
            logger.info("注文量: " + quantity);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 8) 顧客(Customer)情報の名前での検索
    public long getMatchingCustomers(String searchString) {
        logger.debug("getMatchingCustomers() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // 顧客名によるCustomerの検索によるCollectiionの取得
        ReadAllQuery readAllQuery = new ReadAllQuery(Customer.class);
        Expression exp = new ExpressionBuilder().get("name").like("%" +
                searchString + "%");
        readAllQuery.setSelectionCriteria(exp);
        Collection customers = (Collection) clientSession.executeQuery(readAllQuery);
        Iterator customerIterator = customers.iterator();

        // コレクションからCustomerの取得
        while (customerIterator.hasNext()) {
            Customer customer = (Customer) customerIterator.next();

            // 属性の取得
            Long custId = customer.getCustId();
            String custName = customer.getName();
            String addr = customer.getAddr();

            // 属性の表示
            logger.info("====== " + searchString + "の顧客検索結果" + " ======");
            logger.info("顧客ID: " + custId);
            logger.info("顧客名: " + custName);
            logger.info("住所: " + addr);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 9) 品目(Item)の名前での検索
    public long getMatchingItems(String searchString) {
        logger.debug("getMatchingItems() is called");

        long startTime = System.currentTimeMillis();

        // コレクションからからのCustomerの取得
        ClientSession clientSession = getClientSession();
        // 品目名によるItemrの検索によるCollectiionの取得
        ReadAllQuery readAllQuery = new ReadAllQuery(Item.class);
        Expression exp = new ExpressionBuilder().get("name").like("%" +
                searchString + "%");
        readAllQuery.setSelectionCriteria(exp);
        Collection items = (Collection) clientSession.executeQuery(readAllQuery);
        Iterator itemIterator = items.iterator();

        // コレクションからのItemの取り出し
        while (itemIterator.hasNext()) {
            Item item = (Item) itemIterator.next();

            // 属性の取得
            Long itemId = item.getItemId();
            String itemName = item.getName();
            String desc = item.getDescription();

            // 属性の表示
            logger.info("====== " + searchString + "品目検索結果" + " ======");
            logger.info("品目ID: " + itemId);
            logger.info("品目名: " + itemName);
            logger.info("説明: " + desc);
        }

        return (System.currentTimeMillis() + startTime);
    }

    // 10) 注文(Order)IDでの注文検索
    public long findOrderDetailFor(Long orderId) {
        logger.debug("findOrderFor() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // 主キーによるOrderの検索
        Expression exp = new ExpressionBuilder().get("orderId").equal(orderId);
        Order order = (Order) clientSession.readObject(Order.class, exp);

        // Customerの取得
        Customer customer = order.getCust();
        // Itemの取得
        Item item = order.getItem();
        
        // 属性の取得
        Long ordId = order.getOrderId();
        Long orderQuentity = order.getQuantity();
        String shipAddr = order.getShipAddr();
        Long custId = customer.getCustId();
        String custName = customer.getName();
        String custAddress = customer.getAddr();
        Long itemId = item.getItemId();
        String itemName =item.getName();
        String itemDesc = item.getDescription();

        // 属性の表示
        logger.info("====== " + "注文ID" + orderId + "の詳細情報" + " ======");
        logger.info("注文ID: " + ordId);
        logger.info("数量: " + orderQuentity);
        logger.info("配送住所: " + shipAddr);
        logger.info("顧客ID: " + custId);
        logger.info("顧客名: " + custName);
        logger.info("顧客住所: " + custAddress);
        logger.info("品目ID: " + itemId);
        logger.info("品目名: " + itemName);
        logger.info("説明: " + itemDesc);

        return (System.currentTimeMillis() - startTime);
    }

    // 11) 注文(Order)の数量変更
    public long changeOrderQuantity(Long orderId, Long quantity) {
        logger.debug("changeOrderQuantity() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // UnitOfWorkの取得
        UnitOfWork uow = clientSession.acquireUnitOfWork();

        // 主キーによるOrderの検索
        Expression exp = new ExpressionBuilder().get("orderId").equal(orderId);
        Order order = (Order) clientSession.readObject(Order.class, exp);
        Order orderClone = (Order) uow.registerObject(order);

        // 数量の更新
        orderClone.setQuantity(quantity);
        
        // 属性の取得
        Long quant = orderClone.getQuantity();

        // 属性の表示
        logger.info("====== " + "注文ID" + orderId + "の数量変更後情報" + " ======");
        logger.info("注文ID: " + orderId);
        logger.info("数量: " + quant);

        return (System.currentTimeMillis() - startTime);
    }

    // 12) 注文(Order)のキャンセル
    public long cancelOrder(Long orderId) {
        logger.debug("cancelOrder() is called");

        long startTime = System.currentTimeMillis();

        // クライアントセッションの取得
        ClientSession clientSession = getClientSession();
        // UnitOfWorkの取得
        UnitOfWork uow = clientSession.acquireUnitOfWork();

        // 注文IDによるOrderの検索
        Expression exp = new ExpressionBuilder().get("orderId").equal(orderId);
        Order order = (Order) clientSession.readObject(Order.class, exp);
        uow.deleteObject(order);
        logger.info("====== " + "注文ID" + orderId + "はキャンセルされました" + " ======");

        return (System.currentTimeMillis() - startTime);
    }

    // ServerSessionの取得
    private Server getServerSession() {
        if (serverSession == null) {
            serverSession = (Server) SessionManager.getManager().getSession(SESSION_NAME,
                    this.getClass().getClassLoader());
        }

        return serverSession;
    }

    // clientSessionの取得
    private ClientSession getClientSession() {
        if (serverSession == null) {
            serverSession = (Server) SessionManager.getManager().getSession(SESSION_NAME,
                    this.getClass().getClassLoader());
        }

        return serverSession.acquireClientSession();
    }
}


メソッド名/引数/戻り値等は、EJB2.xの例と同じにし、実装はPOJOを利用する様に変更してあります。
大きな違いは、EJB2.xの場合、検索条件はejb-qlの中にEJB-QLで記述を行います。POJOの場合、検索条件は呼び出し側で行います。これに関しては今回、同じ機能をEJB2.xとPOJOにより実装しましたが、圧倒的にPOJOの方が実装しやすかったというのが印象です。というのも、POJOの場合、一回ドメインクラスを作成しマッピングメタデータ(プロジェクトデプロイメントXML)を作成してしまえば、後はほとんど変更する必要はないのですが、EJB2.xの方は、検索条件が追加されるたびに、finderXXX()メソッドを作って・・・EJB-QLを設定して・・・等、あっちこっちに作業が飛ぶ感じがし、今回の様なものすごく簡単なサンプルでさえ少し時間がかかってしました。(POJOとEJB2.x)の作成時間測っておけばよかった・・・)。



 おわりに

今回は、EJB2.xとPOJOを用いたサンプルを作成しました。次回以降は、このサンプルにアクセスするクライアント(OrderSystemBeanに対するクライアント)を作成し、更なる比較をしていきたいと思います。


【参考文献】
「Enterprise JavaBeans Specification 2.1」
http://java.sun.com/products/ejb/

OracleAS ToplLink
http://otn.oracle.co.jp/products/ias/toplink/index.html