2006年 6月 24日 土曜日 以前のエントリで,SOAにはレイヤ構成が重要だと書きました。例えば,現在は一般的なエンタープライズJavaの開発を行っていたとしても,将来的にSOAを実施する可能性があるようであれば,SOAのレイヤ構成を想定した開発を行っておけば,スムーズに移行できるようになります。
今回は,最近リリースされたNetBeans Enterprise Pack 5.5を使って,SOAのレイヤ構成に従ったJava EE 5開発を試して見ましょう。(チュートリアル風に手順を追っていきますので,Java EE 5やNetBeansを使われたことのない方も是非試してみてください。とても簡単です。)
以下のような雰囲気のアプリケーションを作りたいと思います。
【0. 前提条件】
NetBeans Enterprise Pack 5.5 EA版をダウンロードしてインストールしてください。それだけでO.K.です。
【1. Java DBの準備】
NetBeansにはデータベースとして,Java DBであるDerbyがバンドルされています。今回はこれを使います。
(1) Java DB起動
NetBeansのメニューで,[Tools]-[Java DB]-[Java DB Database]-[Start Java DB Server]を選択します。コンソール"Java DB Database Process"が開始され,以下のようなメッセージが表示されます。
サーバーは、ポート 1527 で接続を受け入れる準備ができました。
接続番号: 1。
(2) 接続の設定
左側のペインで[Runtime]タブを選択,[Databases]-[Drivers]-[Java DB(Net)]を右クリックして,[Connect Using ...]を選択すると,"New Database Connection"ダイアログが開きます。ここで,
を入力すると,あらかじめ構築済みのサンプルDBに接続できるようになります。
(3) テーブル,データの作成
接続先DBの[Tables]-[Execute Command]を選択すると,SQLコマンドを実行するコンソールが立ち上がります。ここで,3つのテーブルを作成します。
create table books_data(
product_id varchar(8) CONSTRAINT book_data_pk PRIMARY KEY,
product_name varchar(64),
price numeric(6,2)
);
insert into books_data values('SUNW-001', 'Java EE and .NET Interoperability',49.99);
insert into books_data values('SUNW-002', 'Core Security Patterns', 59.99);
insert into books_data values('SUNW-003', 'NetBeans IDE Field Guide', 44.99);
insert into books_data values('SUNW-004', 'Core JavaServer Faces', 54.99);
insert into books_data values('SUNW-005', 'Java? Studio Creator Field Guide', 39.99);
create table foo_price_list(
product_id varchar(8) CONSTRAINT foo_price_pk PRIMARY KEY,
price numeric(6,2)
);
insert into foo_price_list values('SUNW-001', 44.99);
insert into foo_price_list values('SUNW-002', 49.99);
insert into foo_price_list values('SUNW-003', 39.99);
insert into foo_price_list values('SUNW-004', 44.99);
insert into foo_price_list values('SUNW-005', 34.99);
create table bar_price_list(
product_id varchar(8) CONSTRAINT bar_price_pk PRIMARY KEY,
price numeric(6,2)
);
insert into bar_price_list values('SUNW-001', 39.99);
insert into bar_price_list values('SUNW-002', 54.99);
insert into bar_price_list values('SUNW-003', 34.99);
insert into bar_price_list values('SUNW-004', 49.99);
insert into bar_price_list values('SUNW-005', 29.99);
作成されたテーブルのアイコンを右クリックして[View Data]を選択して,データの一覧を確認してください。
【2. エンティティ,サービスの開発】
(1) プロジェクトの作成
[File]-[New Project]を選択すると,"New Project"ダイアログが起動します。カテゴリ"Enterprise"から"EJB Module"を選択して,プロジェクト"PricingService"を作成してください。
(2) エンティティの作成
プロジェクト"PricingService"を右クリック,[New]-[Entity Classes from Database]を選択します。
Data Sourceとして"jdbc/sample"を選択します。(もし存在しないようなら,[New Data Source]から作成しましょう。)
"Available Tables"から"BOOKS_DATA"をADDして[Next],Package: persistを入力します。
[Create Persistence Unit]を押して,Persistence Unitを作成します。今回は,テーブルをcreateする必要は無いので,Table Generation Strategy: Noneを選択して[OK]を押します。
[Finish]でエンティティクラスが作成されます。
以下のように,Java EE 5のアノテーションを使ったEntity Objectが生成されます。
@Entity
@Table(name = "BOOKS_DATA")
@NamedQueries( {@NamedQuery(name = "BooksData.findByProductId", query = "SELECT b FROM BooksData b WHERE b.productId = :productId"), @NamedQuery(name = "BooksData.findByProductName", query = "SELECT b FROM BooksData b WHERE b.productName = :productName"), @NamedQuery(name = "BooksData.findByPrice", query = "SELECT b FROM BooksData b WHERE b.price = :price")})
public class BooksData implements Serializable {
@Id
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@Column(name = "PRODUCT_NAME")
private String productName;
@Column(name = "PRICE")
private BigDecimal price;
/** Creates a new instance of BooksData */
public BooksData() {
}
public BooksData(String productId) {
this.productId = productId;
}
public String getProductId() {
return this.productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getProductName() {
return this.productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getPrice() {
return this.price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public int hashCode() {
int hash = 0;
hash += (this.productId != null ? this.productId.hashCode() : 0);
return hash;
}
public boolean equals(Object object) {
if (object == null || !this.getClass().equals(object.getClass())) {
return false;
}
BooksData other = (BooksData)object;
if (this.productId != other.productId && (this.productId == null || !this.productId.equals(other.productId))) return false;
return true;
}
public String toString() {
//TODO change toString() implementation to return a better display name
return "" + this.productId;
}
}
(3) サービスの生成
プロジェクト"PricingService"を右クリック,[New]-[Session Facade]を選択します。
"Available Entity Classes"から"BooksData"をADDして[NEXT],Package: serviceを入力します。
[Finish]でセッションファサードが生成されます。
こちらも,Java EE 5のアノテーションを使ったStateless SessionBeanです。
@Stateless
public class BooksDataFacade implements BooksDataFacadeLocal {
@PersistenceContext
private EntityManager em;
/** Creates a new instance of BooksDataFacade */
public BooksDataFacade() {
}
public void create(BooksData booksData) {
em.persist(booksData);
}
public void edit(BooksData booksData) {
em.merge(booksData);
}
public void destroy(BooksData booksData) {
em.merge(booksData);
em.remove(booksData);
}
public BooksData find(Object pk) {
return (BooksData) em.find(BooksData.class, pk);
}
public List findAll() {
return em.createQuery("select object(o) from persist.BooksData as o").getResultList();
}
}
(4) 他のテーブルに対するサービスの作成
上記(1)-(3)の手順を,FOO_PRICE_LISTテーブル,BAR_PRICE_LISTテーブルに対しても同様に実施します。
(*)ここまで,ソースコードは一行も編集していません。
【3. プロセスの開発】
(1) ビジネスプロセスを実行するSession Beanの生成
プロジェクト"PricingService"を右クリックして,[New]-[Session Bean]を選択,以下のような値を入力します。
(2) Dependency Injectionの設定
クラスが生成されたら,ソースコードエディタ上で右クリック,[Enterprise Resources]-[Call Enterprise Bean]を選択します。BooksDataFacade, FooPriceListFacade, BarPriceFacadeをそれぞれ選択すると,Injectionされるメンバー変数が生成されますので,以下のようにアノテーションを追記します。
従来のDDに記述していたejb-refと,ソースコードに記述していたInitialContext#lookupをまとめて記述しているとイメージしてください。このように記述するだけで,メンバー変数にEJB参照が自動的に設定された状態でメソッドからアクセスできるようになります。
@EJB(name="ejb/BooksDataFacade", beanInterface=BooksDataFacadeLocal.class)
private BooksDataFacadeLocal booksDataFacade;
@EJB(name="ejb/FooPriceListFacade", beanInterface=FooPriceListFacadeLocal.class)
private FooPriceListFacadeLocal fooPriceListFacade;
@EJB(name="ejb/BarPriceListFacade", beanInterface=BarPriceListFacadeLocal.class)
private BarPriceListFacadeLocal barPriceListFacade;
(3) ビジネスメソッドの追加
ソースコードエディタ上で右クリック,[EJB Methods]-[Add Business Method]を選択します。
としてメソッドを生成します。
生成したメソッド内で実装を行います。ようやくJavaプログラミングの出番ですが,以前のJ2EEに比べると,とても素っ気無いプログラムです。
public BooksData findBookPrice(String productId) {
// 3つのサービスにアクセスします。
BooksData booksData = booksDataFacade.find(productId);
FooPriceList fooPriceList = fooPriceListFacade.find(productId);
BarPriceList barPriceList = barPriceListFacade.find(productId);
// 戻り値用のインスタンスにBooksDataをコピー
BooksData to_return = new BooksData();
to_return.setProductId(booksData.getProductId());
to_return.setProductName(booksData.getProductName());
to_return.setPrice(booksData.getPrice());
// Foo, Barのうち,安い方の価格を設定します。
if(to_return.getPrice().doubleValue() > fooPriceList.getPrice().doubleValue())
to_return.setPrice(fooPriceList.getPrice());
if(to_return.getPrice().doubleValue() > barPriceList.getPrice().doubleValue())
to_return.setPrice(barPriceList.getPrice());
return to_return;
}
(4) Webサービス化
それでは,このプロセスをWebサービスとして呼び出せるようにしましょう。
プロジェクト"PricingService"を右クリック,[New]-[Web Service]を選択します。
を入力,選択,[Finish]でWebサービスが生成されます。
ただし,ただし,このWebサービスにはpublicメソッドが一切定義されていません。
"BooksPricingProcessWebServiceBean.java"のソースコードエディタ上で右クリック,
[Web Service]-[Add Operation]を選択します。
としてメソッドを生成します。そして,3(2)と同様の手順で,Dependency Injectionの設定した上で,BooksPricingProcessBean#findBookPriceに処理を委譲するように実装します。
(5) Webサービスによる呼び出しテスト
[PricingService]-[Web Services]-[BooksPricingProcessWebServiceBean]を右クリック,[Test Web Service]を選択すると,テスト用のWebページが開きます。
プロダクトID(例えば,SUNW-001)を入力,送信すると結果が表示されます。