| |
 |
日本オラクル
プロダクトSC本部 テクノロジーSC部
佐藤 裕之 |
前回「データアクセスことはじめ( 前編/ 後編)」では、システムの論理的な階層の、ドメイン層、パーシステンス層とそのアプリケーションアーキテクチャという観点から、シンプルなJDBCプログラムを用い考えてみました。なんとなく「Domain Model」+「Data Mapper」が良さそうだと思いましたか?O/Rマッピングインピーダンスミスマッチをうまく吸収するためには、高度なData Mapperの機能を持った技術・フレームワークを選択することが必須になります。現状、考えられるData Mapperの機能を実現するための技術・フレームワークは以下の様なものがあります。
| 1) |
EJB CMP(Container Managed Persistence) |
| 2) |
JDO準拠のO/Rマッピングフレームワーク |
| 3) |
独自のO/Rマッピングフレームワーク |
今回は、最近何かと話題のEJBについて少しだけ触れてみたいと思います。通常ならば、EJBの現状とか色々説明してといった所ですが、今回はいきなり単純なサンプルを見ていきたいと思います。
今回もいままでと同様に、単純なサンプルで考えます。前回、「 データアクセスことはじめ(後編)」でO/Rインピーダンスミスマッチについて触れたときに、オブジェクト指向技術とリレーショナルデータデータベース技術は本質的に異なる技術なので、其々のモデリングの結果作られるオブジェクトモデルとデータモデルは異なる可能性があると述べました。つまりドメインクラスとデータベーススキーマ他のテーブルは必ず1対1に成るとは限らないわけですが、今回は、単純な2つのデータベーステーブルを元にして、そのテーブルに対応したドメインオブジェクトをEJB CMPで実装した場合のサンプルに触れてみます。
今回利用するデータベーススキーマは以下のようになります。
【コード】EJBサンプル用のシンプルなスキーマ作成DDL
CREATE TABLE "DEPARTMENT"
(
"DEPTNO" NUMBER (2,0) NOT NULL,
"DEPTNAME" VARCHAR2 (14)
);
CREATE TABLE "EMPLOYEE"
(
"EMPNO" NUMBER (4,0) NOT NULL,
"EMPNAME" VARCHAR2 (10),
"DEPTNO" NUMBER (2,0)
);
|
このスキーマを元に現行のEJB2.x/EJB2.x+XDoclet/EJB3.0の違いを見ていきます。また、今回は細かな技術的な内容を気にせず、EJB3.0の変化に対する大体の感覚をつかんでいただきたいため、コード全体を掲載し細かな技術的解説はしません。
今回ご紹介するEJBのサンプルはこのスキーマを元にEMPLOYEEテーブルに対するEntity BeanであるEmployee、DEPARTMENTテーブルに対するEntity BeanであるDepartment、Employee/Departmentにアクセスするためのファサードとしての役割を持つStateless Session BeanであるWorkerInfo、その他、DTO(Data Transfer Object)を作成している場合もあります。
では、現状のEJB2.xのサンプルを見ていきましょう。EJB2.x仕様でEJB開発する上では、以下のファイルが必要になります。
| ファイル |
役割 |
| Homeインタフェース |
Local/Remote Object生成・検索のためのファクトリとなるHome Objectのためのインタフェース |
| コンポーネントインタフェース |
ビジネスロジックを公開したLocal/Remote Objectのためのインタフェース |
| Beanクラス |
ビジネスロジックの実装 |
デプロイメント
ディスクリプタ |
J2EE汎用 |
EJB2.Xで定められたデプロイメントディスクリプタ |
| EJBコンテナ固有 |
EJBコンテナ(アプリケーションサーバ)で定めされたデプロイメントディスクリプタ |
![]() |
| EmployeeBeanサンプル |
Employeeテーブルに対応したCMP(Container Managed Persistence)のEntity Beanです。
Homeインタフェース
EJB2.x仕様のHomeインタフェースはLocalかRemoteかにより、javax.ejb.EJBLocalHomeもしくはjavax.ejb.EJBHomeを実装し作成します。EmployeeBeanはEntity Beanなのでjavax.ejb.EJBLocalHomeを実装します。
【コード】EmployeeLocalHome.java
// EmployeeLocalHome.java
package ejb2;
import java.util.Collection;
import javax.ejb.CreateException;
import javax.ejb.EJBLocalHome;
import javax.ejb.FinderException;
public interface EmployeeLocalHome extends EJBLocalHome {
public static final String COMP_NAME =
"java:comp/env/ejb/EmployeeBeanLocal";
public static final String JNDI_NAME = "EmployeeBeanLocal";
public EmployeeLocal create(Employee emp) throws CreateException;
public Collection findAll() throws FinderException;
public EmployeeLocal findByPrimaryKey(Long pk) throws FinderException;
}
|
コンポーネントインタフェース
EJB2.x仕様のコンポーネントインタフェースはLocalかRemoteかにより、javax.ejb.EJBLocalObjectもしくはjavax.ejb.EJBObjectを実装し作成します。EmployeeBeanはEntity Beanなのでjavax.ejb.EJBLocalObjectを実装します。
【コード】EmployeeLocal.java
// EmployeeLocal.java
package ejb2;
import javax.ejb.EJBLocalObject;
public interface EmployeeLocal extends EJBLocalObject {
public Long getEmpNo();
public java.lang.String getEmpName();
public void setEmpName(java.lang.String empName);
public DepartmentLocal getDept();
public void setDept(DepartmentLocal deptLocal);
}
|
Beanクラス
EJB2.x仕様のEntity Beanクラスは、javax.ejb.EntityBeanインタフェースを実装し作成します。EmployeeBeanはEntity Beanなのでjavax.ejb.EntityBeanインタフェースを実装します。また、javax.ejb.EntityBeanインタフェース内でされているコンテナ契約メソッド(ejbXXX()メソッド)をオーバライドする必要があります。
【コード】EmployeeBean.java
// EmployeeBean.java
package ejb2;
import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
public abstract class EmployeeBean implements EntityBean {
private EntityContext ectx;
public abstract Long getEmpNo();
public abstract void setEmpNo(Long empNo);
public abstract String getEmpName();
public abstract void setEmpName(String empName);
public abstract DepartmentLocal getDept();
public abstract void setDept(DepartmentLocal deptLocal);
public Long ejbCreate(Employee emp) throws CreateException {
setEmpNo(emp.getEmpNo());
setEmpName(emp.getEmpName());
return null;
}
public void ejbPostCreate(Employee emp) throws CreateException {
}
public void ejbStore() {
}
public void ejbLoad() {
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setEntityContext(EntityContext ectx) {
this.ectx = ectx;
}
public void unsetEntityContext() {
this.ectx = null;
}
}
|
![]() |
| DepartmentBeanサンプル |
Departmentテーブルに対応したCMP(Container Managed Persistence)のEntity Beanです。
Homeインタフェース
DepartmentBeanはEntity Beanなのでjavax.ejb.EJBLocalHomeインタフェースを実装します。
【コード】DepartmentLocalHome.java
// DepartmentLocalHome.java
package ejb2;
import javax.ejb.CreateException;
import javax.ejb.EJBLocalHome;
import javax.ejb.FinderException;
public interface DepartmentLocalHome extends EJBLocalHome {
public static final String COMP_NAME =
"java:comp/env/ejb/DepartmentBeanLocal";
public static final String JNDI_NAME = "DepartmentBeanLocal";
public DepartmentLocal create(Department dept) throws CreateException;
public java.util.Collection findAll() throws FinderException;
public DepartmentLocal findByPrimaryKey(Long pk) throws FinderException;
}
|
コンポーネントインタフェース
DepartmentBeanはEntity Beanなのでjavax.ejb.EJBLocalObjectインタフェースを実装します。
【コード】DepartmentLocal.java
// DepartmentLocal.java
package ejb2;
import java.util.Collection;
import javax.ejb.EJBLocalObject;
public interface DepartmentLocal extends EJBLocalObject {
public Long getDeptNo();
public String getDeptName();
public void setDeptName(java.lang.String deptName);
public Collection getEmployees();
public void setEmployees(java.util.Collection employees);
}
|
Beanクラス
DepartmentBeanはEntity Beanなのでjavax.ejb.EntityBeanインタフェースを実装します。また、javax.ejb.EntityBeanインタフェース内でされているコンテナ契約メソッド(ejbXXX()メソッド)をオーバライドする必要があります。
【コード】DepartmentBean.java
//DepartmentBean.java
package ejb2;
import java.util.Collection;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
public abstract class DepartmentBean implements EntityBean {
private EntityContext context;
public Long ejbCreate() {
return null;
}
public void ejbPostCreate() {
}
public Long ejbCreate(Long deptno) {
setDeptno(deptno);
return deptno;
}
public void ejbPostCreate(Long deptno) {
}
public void ejbActivate() {
}
public void ejbLoad() {
}
public void ejbPassivate() {
}
public void ejbRemove() {
}
public void ejbStore() {
}
public void setEntityContext(EntityContext ctx) {
this.context = ctx;
}
public void unsetEntityContext() {
this.context = null;
}
public abstract Long getDeptno();
public abstract void setDeptno(Long deptno);
public abstract String getDname();
public abstract void setDname(String dname);
public abstract Collection getEmployee_deptno();
public abstract void setEmployee_deptno(Collection employee_deptno);
}
|
![]() |
| WorkerInfoBeanサンプル |
Entity BeanであるEmployee/Departmentにアクセスするファサード的な役割を持つStateless Session Beanです。
Homeインタフェース
WorkerInfoBeanはSession Beanであり、リモートからのアクセスを想定しているのでjavax.ejb.EJBHomeを実装します。
【コード】WorkerInfoHome.java
// WorkerInfoHome.java
package ejb2;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
public interface WorkerInfoHome extends EJBHome {
public static final String COMP_NAME = "java:comp/env/ejb/WorkerInfoBean";
public static final String JNDI_NAME = "WorkerInfoBean";
public WorkerInfo create() throws CreateException, RemoteException;
}
|
コンポーネントインタフェース
WorkerInfoBeanはSession Beanであり、リモートからのアクセスを想定しているのでjavax.ejb.EJBObjectを実装します。
【コード】WorkerInfo.java
// WorkerInfo.java
package ejb2;
import java.util.Collection;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBObject;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;
public interface WorkerInfo extends EJBObject {
public void addDept(Department dept)
throws CreateException, RemoteException;
public void removeDept(Department dept)
throws RemoveException, RemoteException;
public void setDept(Department dept)
throws FinderException, RemoteException;
public Collection getDepts()
throws FinderException, RemoteException;
public Collection getDeptEmployees(Department dept)
throws FinderException, RemoteException;
public void addEmp(Employee emp)
throws CreateException, RemoteException;
public void removeEmp(Employee emp)
throws RemoveException, RemoteException;
public void setEmp(Employee emp)
throws FinderException, RemoteException;
public Collection getEmps()
throws FinderException, RemoteException;
public Department getEmpDepartment(Employee emp)
throws FinderException, RemoteException;
public void setEmpDepartment(Employee emp, Department dept)
throws FinderException, RemoteException;
}
|
Beanクラス
DepartmentBeanはSession Beanなのでjavax.ejb. SessionBeanを実装します。また、javax.ejb.SessionBeanインタフェース内でされているコンテナ契約メソッド(ejbXXX()メソッド)をオーバライドする必要があります。
【コード】WorkerInfoBean.java
// WorkerInfoBean.java
package ejb2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
public class WorkerInfoBean implements SessionBean {
private static Context nctx = null;
public static final String empBean = "java:comp/env/ejb/EmployeeBean";
public static EmployeeLocalHome empHome = null;
public static final String deptBean = "java:comp/env/ejb/DepartmentBean";
public static DepartmentLocalHome deptHome = null;
public SessionContext sctx = null;
public transient EmployeeLocal empLocal = null;
public Employee emp = null;
public transient DepartmentLocal deptLocal = null;
public Department dept = null;
public void ejbCreate() throws CreateException {
try {
nctx = new InitialContext();
deptHome = (DepartmentLocalHome)
PortableRemoteObject.narrow(nctx.lookup(
deptBean), DepartmentLocalHome.class);
empHome = (EmployeeLocalHome)
PortableRemoteObject.narrow(nctx.lookup(
empBean), EmployeeLocalHome.class);
} catch (NamingException e) {
e.printStackTrace();
}
}
public void addDept(Department dept) throws CreateException {
deptLocal = (DepartmentLocal) deptHome.create(dept);
}
public void removeDept(Department dept) throws RemoveException {
try {
deptLocal = (DepartmentLocal)
deptHome.findByPrimaryKey(dept.getDeptNo());
} catch (FinderException e) {
throw new RemoveException(e.getMessage());
}
deptLocal.remove();
}
public void setDept(Department dept) throws FinderException {
deptLocal = (DepartmentLocal)
deptHome.findByPrimaryKey(dept.getDeptNo());
deptLocal.setDeptName(dept.getDeptName());
}
public Collection getDepts() throws FinderException {
ArrayList depts = new ArrayList();
Collection deptLocals = deptHome.findAll();
Iterator iterator = deptLocals.iterator();
while (iterator.hasNext()) {
deptLocal = (DepartmentLocal) iterator.next();
dept = new Department
(deptLocal.getDeptNo(), deptLocal.getDeptName());
depts.add(dept);
}
return depts;
}
public Collection getDeptEmployees(Department dept)
throws FinderException {
ArrayList emps = new ArrayList();
deptLocal = (DepartmentLocal)
deptHome.findByPrimaryKey(dept.getDeptNo());
Collection empLocals = deptLocal.getEmployees();
Iterator iterator = empLocals.iterator();
while (iterator.hasNext()) {
empLocal = (EmployeeLocal) iterator.next();
emp = new Employee(empLocal.getEmpNo(), empLocal.getEmpName());
emps.add(emp);
}
return emps;
}
public void addEmp(Employee emp) throws CreateException {
empLocal = (EmployeeLocal) empHome.create(emp);
}
public void removeEmp(Employee emp) throws RemoveException {
try {
empLocal = (EmployeeLocal)
empHome.findByPrimaryKey(emp.getEmpNo());
} catch (FinderException e) {
throw new RemoveException(e.getMessage());
}
empLocal.remove();
}
public void setEmp(Employee emp) throws FinderException {
empLocal = (EmployeeLocal) empHome.findByPrimaryKey(emp.getEmpNo());
empLocal.setEmpName(emp.getEmpName());
}
public Collection getEmps() throws FinderException {
ArrayList emps = new ArrayList();
Collection empLocals = empHome.findAll();
Iterator iterator = empLocals.iterator();
while (iterator.hasNext()) {
empLocal = (EmployeeLocal) iterator.next();
emp = new Employee(empLocal.getEmpNo(), empLocal.getEmpName());
emps.add(emp);
}
return emps;
}
public Department getEmpDepartment(Employee emp) throws FinderException {
empLocal = (EmployeeLocal) empHome.findByPrimaryKey(emp.getEmpNo());
deptLocal = (DepartmentLocal) empLocal.getDept();
if (deptLocal == null) {
return null;
}
dept = new Department(deptLocal.getDeptNo(), deptLocal.getDeptName());
return dept;
}
public void setEmpDepartment(Employee emp, Department dept)
throws FinderException {
empLocal = (EmployeeLocal) empHome.findByPrimaryKey(emp.getEmpNo());
deptLocal = deptHome.findByPrimaryKey(dept.getDeptNo());
empLocal.setDept(deptLocal);
}
public void setSessionContext(SessionContext sctx) {
this.sctx = sctx;
}
public void unsetSessionContext() {
this.sctx = null;
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
}
|
![]() |
| DTO(Data Transfer Object)サンプル |
EntityBeanであるEmployee/Departmentから取得した各々のデータを格納するためのDTO(Data Transfer Object)の実装です。
EmployeeBean用DTO
EmployeeBean用のシリアライザブル(java.io.Serializableを実装した)な、Javaクラスです。
【コード】Employee.java
//Employee.java
package ejb2;
import java.io.Serializable;
public class Employee implements Serializable {
private Long empNo;
private String empName;
public Employee(Long empNo, String empName) {
this.empNo = empNo;
this.empName = empName;
}
public Long getEmpNo() {
return empNo;
}
public void setEmpNo(Long empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public boolean equals(Object other) {
if (other instanceof Employee) {
Employee emp = (Employee) other;
if ((empNo.compareTo(emp.getEmpNo()) == 0) &&
empName.equals(emp.getEmpName())) {
return true;
}
}
return false;
}
}
|
Department用DTO
DepartmentBean用のシリアライザブル(java.io.Serializableを実装した)な、Javaクラスです。
【コード】Department.java
//Department.java
package ejb2;
import java.io.Serializable;
public class Department implements Serializable {
private Long deptNo;
private String deptName;
public Department(Long deptNo, String deptName) {
this.deptNo = deptNo;
this.deptName = deptName;
}
public Long getDeptNo() {
return deptNo;
}
public void setDeptNo(Long deptNo) {
this.deptNo = deptNo;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public boolean equals(Object other) {
if (other instanceof Department) {
Department dept = (Department) other;
if ((deptNo.compareTo(dept.getDeptNo()) == 0) &&
deptName.equals(dept.getDeptName())) {
return true;
}
}
return false;
}
}
|
![]() |
| デプロイメントディスクリプタのサンプル |
EJBのソースコード内には記述しない環境に依存する情報等を設定するデプロイメントディスクリプタを作成します。
J2EE汎用のデプロイメントディスクリプタ
EJB2.x仕様で定めされたejb-jar.xmlという名前のデプロイメントディスクリプタを作成します。
【DD】ejb-jar.xml
<?xml version = '1.0' encoding = 'UTF-8'?>
<!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>
<description>No Description.</description>
<display-name>EJB2.x DD</display-name>
<enterprise-beans>
<!-- Session Beans -->
<session>
<description>no description</description>
<display-name>WorkerInfoBean</display-name>
<ejb-name>WorkerInfoBean</ejb-name>
<home>ejb2.WorkerInfoHome</home>
<remote>ejb2.WorkerInfo</remote>
<ejb-class>ejb2.WorkerInfoBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<ejb-local-ref>
<ejb-ref-name>ejb/DepartmentBean</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>ejb2.DepartmentLocalHome</local-home>
<local>ejb2.DepartmentLocal</local>
<ejb-link>DepartmentBean</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/EmployeeBean</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>ejb2.EmployeeLocalHome</local-home>
<local>ejb2.EmployeeLocal</local>
<ejb-link>EmployeeBean</ejb-link>
</ejb-local-ref>
</session>
<!-- Entity Beans -->
<entity>
<description>no description</description>
<display-name>DepartmentBean</display-name>
<ejb-name>DepartmentBean</ejb-name>
<local-home>ejb2.DepartmentLocalHome</local-home>
<local>ejb2.DepartmentLocal</local>
<ejb-class>ejb2.DepartmentBean</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>Department</abstract-schema-name>
<cmp-field>
<description>
</description>
<field-name>deptNo</field-name>
</cmp-field>
<cmp-field>
<description>
</description>
<field-name>deptName</field-name>
</cmp-field>
<primkey-field>deptNo</primkey-field>
<query>
<query-method>
<method-name>findAll</method-name>
<method-params>
</method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql>
Select OBJECT(d) From Department d
</ejb-ql>
</query>
</entity>
<entity>
<description>
no description
</description>
<display-name>EmployeeBean</display-name>
<ejb-name>EmployeeBean</ejb-name>
<local-home>ejb2.EmployeeLocalHome</local-home>
<local>ejb2.EmployeeLocal</local>
<ejb-class>ejb2.EmployeeBean</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>Employee</abstract-schema-name>
<cmp-field>
<description>
</description>
<field-name>empNo</field-name>
</cmp-field>
<cmp-field>
<description>
</description>
<field-name>empName</field-name>
</cmp-field>
<primkey-field>empNo</primkey-field>
<query>
<query-method>
<method-name>findAll</method-name>
<method-params>
</method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql>
Select OBJECT(e) From Employee e
</ejb-ql>
</query>
</entity>
</enterprise-beans>
<!-- Relationships -->
<relationships>
<ejb-relation>
<ejb-relation-name>Dept-Emps</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>Emps-have-Dept</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<relationship-role-source>
<ejb-name>EmployeeBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>dept</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>Dept-has-Emps</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>DepartmentBean</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>employees</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>DepartmentBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>WorkerInfoBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>EmployeeBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
|
EJBコンテナ固有のデプロイメントディスクリプタ
EJB2.0仕様でカバーしきれないEJBコンテナ(アプリケーションサーバ)固有のデプロイメントディスクリプタを作成します。下記の例は、Oracle Application Server10g(9.0.4)の例です。EJB CMPの場合には、データベースとのマッピングは、このEJBコンテナ固有のデプロイメントディスクリプタ内に記述します。
【DD】orion-ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE orion-ejb-jar PUBLIC
"-//ORACLE//DTD OC4J Enterprise JavaBeans runtime 9.04//EN"
"http://xmlns.oracle.com/ias/dtds/orion-ejb-jar-9_04.dtd">
<orion-ejb-jar deployment-version="9.0.4.0.0">
<enterprise-beans>
<session-deployment name="WorkerInfoBean" location="WorkerInfoBean">
<ejb-ref-mapping name="ejb/DepartmentBean"/>
<ejb-ref-mapping name="ejb/EmployeeBean"/>
</session-deployment>
<entity-deployment name="DepartmentBean" location="DepartmentBean"
table="department">
<primkey-mapping>
<cmp-field-mapping name="deptNo" persistence-name="deptno"/>
</primkey-mapping>
<cmp-field-mapping name="deptName" persistence-name="deptname"/>
<cmp-field-mapping name="employees">
<collection-mapping table="employee">
<primkey-mapping>
<cmp-field-mapping name="dept">
<entity-ref home="DepartmentBean">
<cmp-field-mapping name="dept" persistence-name="dept"/>
</entity-ref>
</cmp-field-mapping>
</primkey-mapping>
<value-mapping type="EmployeeLocal">
<cmp-field-mapping name="employees">
<entity-ref home="EmployeeBean">
<cmp-field-mapping name="dept" persistence-name="empNo"/>
</entity-ref>
</cmp-field-mapping>
</value-mapping>
</collection-mapping>
</cmp-field-mapping>
<finder-method>
<method>
<ejb-name>DepartmentBean</ejb-name>
<method-name>findAll</method-name>
<method-params>
</method-params>
</method>
</finder-method>
</entity-deployment>
<entity-deployment name="EmployeeBean" location="EmployeeBean"
table="employee">
<primkey-mapping>
<cmp-field-mapping name="empNo" persistence-name="empNo"/>
</primkey-mapping>
<cmp-field-mapping name="empName" persistence-name="empName"/>
<cmp-field-mapping name="salary" persistence-name="salary"/>
<cmp-field-mapping name="dept">
<entity-ref home="DepartmentBean">
<cmp-field-mapping name="dept" persistence-name="dept"/>
</entity-ref>
</cmp-field-mapping>
<finder-method>
<method>
<ejb-name>EmployeeBean</ejb-name>
<method-name>findAll</method-name>
<method-params>
</method-params>
</method>
</finder-method>
</entity-deployment>
</enterprise-beans>
<assembly-descriptor>
<default-method-access>
<security-role-mapping name="<default-ejb-caller-role>"
impliesAll="true"/>
</default-method-access>
</assembly-descriptor>
</orion-ejb-jar>
|
XDocletは、オープンソースのコード生成エンジンであり、アトリビュート指向プログラミングを実現します。簡単に言えばJavaソースコードの特殊なJavaDocタグにXDocletタグによる付加情報を追加する事により、そのソースコードからファイルを生成することが出来ます。EJBに関して言えば、Beanクラス内にXDocletタグを記述しておく事により、Homeインタフェース・コンポーネントインタフェース・デプロイメントディスクリプタを自動的に生成することが出来ます。つまり、XDocletを用いてEJB2.x仕様でEJBを開発するには以下のファイルを作成する必要があります。
| ファイル |
役割 |
| Beanクラス |
ビジネスロジックの実装 |
![]() |
| EmployeeBeanサンプル |
EJB2.xでXDocletタグを記述したBeanクラスEmployeeBeanクラスを作成します。本来デプロイメントディスクリプタ内に記述する内容とHomeインタフェース/コンポーネントインタフェースの生成に必要な情報をBeanクラス内にXDocletタグとして記述します。
【コード】EmployeBean..java
// EmployeeBean.java
package ejb2;
import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
/**
* @ejb.bean
* name = "EmployeeBean"
* description = "XDoclet DD"
* display-name = "EmployeeBean"
* view-type = "local"
* type = "CMP"
* cmp-version = "2.x"
* reentrant = "false"
* primkey-field = "empNo"
* @ejb.transaction
* type = "Required"
* @ejb.persistence
* table-name = "employee"
* @ejb.interface
* local-class = "ejb2.EmployeeLocal"
* @ejb.home
* local-class = "ejb2.EmployeeLocalHome"
* @ejb.finder
* signature = "java.util.Collection findAll()"
* query = "Select OBJECT(e) From Emp e"
* result-type-mapping = "Local"
*/
public abstract class EmployeeBean implements EntityBean {
private EntityContext ectx;
/**
* @ejb.persistent-field
* @ejb.interface-method
* view-type = "local"
* @ejb.pk-field
*/
public abstract String getEmpNo();
/**
* @ejb.interface-method
* view-type = "local"
*/
public abstract void setEmpNo(String empNo);
/**
* @ejb.persistent-field
* @ejb.interface-method
* view-type = "local"
*/
public abstract String getEmpName();
/**
* @ejb.interface-method
* view-type = "local"
*/
public abstract void setEmpName(String empName);
/**
* @ejb.interface-method
* view-type = "local"
* @ejb.relation
* name = "Dept-Emps"
* role-name = "Emps-have-Dept"
*/
public abstract DepartmentLocal getDept();
/**
* @ejb.interface-method
* view-type = "local"
*/
public abstract void setDept(DepartmentLocal deptLocal);
/**
* @ejb.create-method
*/
public String ejbCreate(Employee emp) throws CreateException {
setEmpNo(emp.getEmpNo());
setEmpName(emp.getEmpName());
return null;
}
public void ejbPostCreate(Employee emp) throws CreateException {
}
public void ejbStore() {
}
public void ejbLoad() {
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setEntityContext(EntityContext ectx) {
this.ectx = ectx;
}
public void unsetEntityContext() {
this.ectx = null;
}
}
|
![]() |
| DepartmentBeanサンプル |
XDocletタグを記述したBeanクラスDepartmentBeanクラスを作成します。本来デプロイメントディスクリプタ内に記述する内容とHomeインタフェース/コンポーネントインタフェースの生成に必要な情報をBeanクラス内にXDocletタグとして記述します。
【コード】DepartmentBean.java
// DepartmentBean.java
package ejb2;
import java.util.Collection;
import javax.ejb.CreateException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
/**
* @ejb.bean
* name = "DepartmentBean"
* description = "no description"
* display-name = "DepartmentBean"
* view-type = "local"
* type = "CMP"
* cmp-version = "2.x"
* reentrant = "false"
* primkey-field = "deptNo"
* @ejb.transaction
* type = "Required"
* @ejb.persistence
* table-name = "department"
* @ejb.interface
* local-class = "ejb2.DepartmentLocal"
* @ejb.home
* local-class = "ejb2.DepartmentLocalHome"
* @ejb.finder
* signature = "java.util.Collection findAll()"
* query = "Select OBJECT(d) From Department d"
* result-type-mapping = "Local"
*/
public abstract class DepartmentBean implements EntityBean {
private EntityContext ectx;
/**
* @ejb.persistence
* column-name = "deptno"
* @ejb.interface-method
* view-type = "local"
* @ejb.pk-field
*/
public abstract String getDeptNo();
/**
* @ejb.interface-method
* view-type = "local"
*/
public abstract void setDeptNo(String deptNo);
/**
* @ejb.persistence
* column-name = "deptname"
* @ejb.interface-method
* view-type = "local"
*/
public abstract String getDeptName();
/**
* @ejb.interface-method
* view-type = "local"
*/
public abstract void setDeptName(String deptName);
/**
* @ejb.interface-method
* view-type = "local"
* @ejb.relation
* name = "Dept-Emps"
* role-name = "Dept-has-Emps"
*/
public abstract Collection getEmployees();
/**
* @ejb.interface-method
* view-type = "local"
*/
public abstract void setEmployees(Collection employees);
/**
* @ejb.create-method
*/
public String ejbCreate(Department dept) throws CreateException {
setDeptNo(dept.getDeptNo());
setDeptName(dept.getDeptName());
return null;
}
public void ejbPostCreate(Department dept) throws CreateException {
}
public void ejbStore() {
}
public void ejbLoad() {
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setEntityContext(EntityContext ectx) {
this.ectx = ectx;
}
public void unsetEntityContext() {
this.ectx = null;
}
}
|
![]() |
| WorkerInfoBeanサンプル |
XDocletタグを記述したBeanクラスWorkerInfoBeanクラスを作成します。本来デプロイメントディスクリプタ内に記述するHomeインタフェース/コンポーネントインタフェースの生成に必要な情報をBeanクラス内にXDocletタグとして記述します。
【コード】WorkerInfoBean.java
// WorkerInfoBean.java
package ejb2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
/**
* @ejb.bean
* name = "WorkerInfoBean"
* description = "XDoclet"
* display-name = "WorkerInfoBean"
* view-type = "remote"
* type = "Stateless"
* @ejb.transaction
* type = "Required"
* @ejb.interface
* remote-class = "ejb2.WorkerInfo"
* @ejb.home
* remote-class = "ejb2.WorkerInfoHome"
* @ejb.ejb-ref
* ejb-name = "DepartmentBean"
* view-type = "local"
* @ejb.ejb-ref
* ejb-name = "EmployeeBean"
* view-type = "local"
*/
public class WorkerInfoBean implements SessionBean {
private static Context nctx = null;
public static final String empBean = "java:comp/env/ejb/EmployeeBean";
public static EmployeeLocalHome empHome = null;
public static final String deptBean = "java:comp/env/ejb/DepartmentBean";
public static DepartmentLocalHome deptHome = null;
public SessionContext sctx = null;
public transient EmployeeLocal empLocal = null;
public Employee emp = null;
public transient DepartmentLocal deptLocal = null;
public Department dept = null;
public void ejbCreate() throws CreateException {
try {
nctx = new InitialContext();
deptHome = (DepartmentLocalHome)
PortableRemoteObject.narrow(nctx.lookup(
deptBean), DepartmentLocalHome.class);
empHome = (EmployeeLocalHome)
PortableRemoteObject.narrow(nctx.lookup(
empBean), EmployeeLocalHome.class);
} catch (NamingException e) {
e.printStackTrace();
}
}
public void ejbPostCreate() throws CreateException {
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void addDept(Department dept) throws CreateException {
deptLocal = (DepartmentLocal) deptHome.create(dept);
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void removeDept(Department dept) throws RemoveException {
try {
deptLocal = (DepartmentLocal)
deptHome.findByPrimaryKey(dept.getDeptNo());
} catch (FinderException e) {
throw new RemoveException(e.getMessage());
}
deptLocal.remove();
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void setDept(Department dept) throws FinderException {
deptLocal = (DepartmentLocal)
deptHome.findByPrimaryKey(dept.getDeptNo());
deptLocal.setDeptName(dept.getDeptName());
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public Collection getDepts() throws FinderException {
ArrayList depts = new ArrayList();
Collection deptLocals = deptHome.findAll();
Iterator iterator = deptLocals.iterator();
while (iterator.hasNext()) {
deptLocal = (DeptLocal) iterator.next();
dept = new Department
(deptLocal.getDeptNo(), deptLocal.getDeptName());
depts.add(dept);
}
return depts;
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public Collection getDeptEmployees(Department dept)
throws FinderException {
ArrayList emps = new ArrayList();
deptLocal = (DepartmentLocal)
deptHome.findByPrimaryKey(dept.getDeptNo());
Collection empLocals = deptLocal.getEmployees();
Iterator iterator = empLocals.iterator();
while (iterator.hasNext()) {
empLocal = (EmployeeLocal) iterator.next();
emp = new Employee(empLocal.getEmpNo(), empLocal.getEmpName(),
empLocal.getSalary());
emps.add(emp);
}
return emps;
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void addEmp(Employee emp) throws CreateException {
empLocal = (EmployeeLocal) empHome.create(emp);
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void removeEmp(Employee emp) throws RemoveException {
try {
empLocal = (EmployeeLocal)
empHome.findByPrimaryKey(emp.getEmpNo());
} catch (FinderException e) {
throw new RemoveException(e.getMessage());
}
empLocal.remove();
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void setEmp(Employee emp) throws FinderException {
empLocal = (EmployeeLocal) empHome.findByPrimaryKey(emp.getEmpNo());
empLocal.setEmpName(emp.getEmpName());
empLocal.setSalary(emp.getSalary());
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public Collection getEmps() throws FinderException {
ArrayList emps = new ArrayList();
Collection empLocals = empHome.findAll();
Iterator iterator = empLocals.iterator();
while (iterator.hasNext()) {
empLocal = (EmployeeLocal) iterator.next();
emp = new Emp(empLocal.getEmpNo(), empLocal.getEmpName(),
empLocal.getSalary());
emps.add(emp);
}
return emps;
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public Department getEmpDepartment(Employee emp) throws FinderException {
empLocal = (EmployeeLocal) empHome.findByPrimaryKey(emp.getEmpNo());
deptLocal = (DepartmentLocal) empLocal.getDept();
if (deptLocal == null) {
return null;
}
dept = new Department(deptLocal.getDeptNo(), deptLocal.getDeptName());
return dept;
}
/**
* @ejb.interface-method
* view-type = "remote"
*/
public void setEmpDepartment(Employee emp, Department dept)
throws FinderException {
empLocal = (EmployeeLocal) empHome.findByPrimaryKey(emp.getEmpNo());
deptLocal = deptHome.findByPrimaryKey(dept.getDeptNo());
empLocal.setDept(deptLocal);
}
public void setSessionContext(SessionContext sctx) {
this.sctx = sctx;
}
public void unsetSessionContext() {
this.sctx = null;
}
public void ejbRemove() {
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
}
|
現在、EJB3.0はJCP(Java Community Process)によりJSR220:Enterprise JavaBeans3.0のEarly Draftが公開されています。EJB3.0仕様でEJBを開発するには、Session Bean、Entity Bean其々以下のファイルを作成する必要があります。
Session bean
| ファイル |
役割 |
| ビジネスインタフェース |
ビジネスロジックを公開したインタフェース |
| Beanクラス |
ビジネスロジックの実装 |
Entity Bean
| ファイル |
役割 |
| Beanクラス |
ビジネスロジックの実装 |
Early Draftのため今後変更される可能性はありますが、今までご紹介したEJB2.xのサンプルをEJB3.0 Early Draftではどうなるのかサンプルで見ていきます。
![]() |
| Employeeサンプル |
EJB3.0のCMPの実装例であるEmployeeクラスです。EntityBeanアノテーションによりアノテートする事によりPOJO(Plain Old Java Object)として実装できます。POJOはEJB3.0仕様で単純なJavaインタフェースを表す言葉として利用されています。またEJB2.xの時にデプロイメントディスクリプタに記述した内容は、アノテーションによりBeanクラス内に記述します。
【コード】Employee.java
// Employee.java
package ejb3;
import java.io.Serializable;
import javax.ejb.Entity;
import javax.ejb.GeneratorType;
import javax.ejb.Id;
import javax.ejb.JoinColumn;
import javax.ejb.ManyToOne;
@Entity
public class Employee implements Serializable {
private Long empNo;
private String empName;
private Department dept;
@Id(generate = GeneratorType.AUTO)
public Long getEmpNo() {
return empNo;
}
public void setEmpNo(Long empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
@ManyToOne
@JoinColumn(name = "deptNo")
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
}
|
![]() |
| Departmentサンプル |
EJB3.0のCMPの実装例であるDepartmentです。
【コード】Department.java
// Department.java
package ejb3;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import javax.ejb.CascadeType;
import javax.ejb.Entity;
import javax.ejb.FetchType;
import javax.ejb.GeneratorType;
import javax.ejb.Id;
import javax.ejb.JoinColumn;
import javax.ejb.OneToMany;
import javax.ejb.Table;
@Entity
@Table(name = "DEPARTMENT")
public class Department implements Serializable {
private Long deptNo;
private String deptName;
private Collection<Employee> employees;
@Id(generate = GeneratorType.AUTO)
public Long getDeptNo() {
return deptNo;
}
public void setDeptNo(Long deptNo) {
this.deptNo = deptNo;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public void addEmp(String empName) {
if (employees == null) employees = new ArrayList<Employee>();
Employee emp = new Employee();
emp.setDept(this);
emp.setEmpName(empName);
employees.add(emp);
}
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "deptNo")
public Collection<Employee> getEmployees() {
return employees;
}
public void setEmployees(Collection<Employee> employees) {
this.employees = employees;
}
}
|
![]() |
| WorkInfoBeanサンプル |
EJB3.0仕様でのSessionBeanは、ビジネスインタフェースとBeanクラスを作成する必要があります。
ビジネスインタフェース
EJB3.0仕様では、SessionBeanのビジネスインタフェースをPOJI(Plain Old Java Interface)で実装します。POJIはEJB3.0仕様で単純なJavaインタフェースを表す言葉として利用されています。
【コード】WorkerInfo.java
// WorkInfo.java
package ejb3;
import java.util.List;
import java.util.Collection;
import javax.ejb.Remote;
import javax.ejb.Remove;
@Remote
public interface WorkerInfo {
public void addDept(Department dept);
public void removeDept(Department dept);
public void setDept(Department dept);
public java.util.Collection getDepts();
public java.util.Collection getDeptEmployees(Department dept);
public void addEmp(Employee emp);
public void removeEmp(Employee emp);
public void setEmp(Employee emp);
public java.util.Collection getEmps();
public Department getEmpDepartment(Employee emp);
public void setEmpDepartment(Employee emp, Department dept);
}
|
Beanクラス
Stateless Session Beanを作成する場合、Statelessアノテーションによりアノテートする事によりPOJO(Plain Old Java Object)として実装できます。
【コード】WorkerInfoBean.java
// WorkerInfoBean.java
package ejb3;
import java.util.List;
import java.util.Collection;
import javax.ejb.EntityManager;
import javax.ejb.Inject;
import javax.ejb.Remove;
import javax.ejb.Stateless;
@Stateless
public class WorkerInfoBean implements WorkerInfo {
@Inject
private EntityManager manager;
public void addDept(Department dept) {
manager.create(dept);
}
public void removeDept(Department dept) {
manager.remove(dept);
}
public void setDept(Department dept) {
manager.merge(dept);
}
public List getDepts() {
return manager.createQuery("from Department d").listResults();
}
public Collection getDeptEmployees(Department dept) {
return dept.getEmployees();
}
public void addEmp(Employee emp) {
manager.create(emp);
}
public void removeEmp(Employee emp) {
manager.remove(emp);
}
public void setEmp(Employee emp) {
manager.merge(emp);
}
public List getEmps() {
return manager.createQuery("from Employee e").listResults();
}
public Department getEmpDepartment(Employee emp) {
Employee empNew = manager.find(Employee.class, emp.getEmpNo());
return empNew.getDept();
}
public void setEmpDepartment(Employee emp, Department dept) {
Employee empNew = manager.find(Employee.class, emp.getEmpNo());
Department deptNew =
manager.find(Department.class, dept.getDeptNo());
empNew.setDept(deptNew);
}
}
|
EntityManagerは、永続化するエンティティインスタンスの生成・消滅、主キーによるエンティティの検索、エンティティに対するクエリの実行等の操作を行うAPIを提供しています。
今回は、細かい解説をせずにEJB2.x→EJB2.x + XDoclet→EJB3.0のデータアクセスに関わりのある部分の簡単なコードを比較し取り上げて見ました。ただ、これだけでもかなりEJB3.0で期待できるものであるという事を感じた方もいらしゃるかと思われます。
例えば、コードの記述量です。EJB3.0が登場した背景等を何も説明せずにコードをご紹介してしまいましたが、EJB3.0仕様の主要なテーマにEoD(Ease of Development)があります。このEoDを実現するための1つの要素に、既にご覧頂いたEJB2.xがHomeインタフェース、コンポーネントインタフェース、Beanクラス、デプロイメントディスクリプタといった多く?のコードを作成しなければならず、開発・保守等が煩雑に成りがちであると言う反省から、POJOとして記述をし、もっとシンプルにしようとしています。その改善の顕著な例が、コード記述量に現れています。
今回のサンプルの場合、EJB2.x/EJB2.x + XDoclet/EJB3.0の開発者が記述しなければならない文字数は其々、約16000文字/約9500文字/約4000文字と成ります。当然のように開発者の方が気にしなければならない文字が少なければな少ないほど開発は容易になり、バグの少ない保守性の高いコードになるかと思われます。
その他にも、EJB2.xの時には、各Entity Beanに対応し、データを格納するDTO(Data Transfer Object)を作成する(今回は作成しています)こともあるかと思います。これもEntity Beanの変更があった時に変更の同期を取るのが結構煩雑であったり、Entity Beanからデータを取り出し、DTOに格納するロジックが煩雑に成りがちだったりとあまりうれしくはなかったのが、EJB3.0からPOJOになりシリアラザブルなクラスになるため、それ自身がDTOとしての役割を持つようになりシンプルな構造になりやすいです。
とか挙げれば、今回の簡単なサンプルだけでも様々見つかるかと思われます。ただし、EJB3.0が最高にスマートかというと、そうではない気がします。それを理解するには、他のテクノロジを含めてもう少し詳細に触れる必要があるかと思いますので、次回以降触れてみたいと思います。
|