Archive for 2010年4月8日
EJB 3.1 の組み込み可能コンテナ
前回のエントリで、EJB 3.1 の新機能概要を紹介しました。今回は EJB 3.1 の新機能の一つである組み込みコンテナを使用し、EJB の単体テストをかんたんに行う方法を紹介します。
ここでは NetBeans 6.8 と GlassFish v3 を利用して説明しますが、NetBeans 6.8 で「 EJB モジュール」のプロジェクトを作成するとデフォルトで GlassFish v3 の組み込みコンテナが利用できるようになっています。ですので、特に面倒な設定は必要なく JUnit のテストコードの記述に集中できるようになっています。今までは EJB の単体テストを行うためにデプロイして Global JNDI 名を利用してテストを行ったりしていたかと思いますが、この方法を利用すると直接アプリケーションサーバにデプロイしなくてもすむため非常に便利です。
それでは実際に EJB 3.1 の組み込みコンテナを利用してみましょう。
NetBeans 6.8 を使用して「EJB モジュール」の新規プロジェクトを作成します。メニューより「新規プロジェクト」を選択してください。すると下記のウィンドウが表示されます。ここで、「Java EE」→「EJB モジュール」を選択し「次へ>」ボタンを押下します。
次に、プロジェクト名、プロジェクトの場所、プロジェクトフォルダ等の項目を入力し「次へ>」ボタンを押下します。
次に、この EJB モジュールを配備するアプリケーションサーバの選択(GlassFish v3)、Java EE のバージョン(Java EE 6)を選択し「完了(F)」ボタンを押下します。
「完了(F)」ボタンを押下すると、NetBeans のプロジェクトに下記のようなプロジェクトが作成されます。ここでプロジェクトのツリーを展開すると「ソースパッケージ」、「テストパッケージ」、「ライブラリ」、「テストライブラリ」等が表示されます。この中で「テストライブラリ」のツリーに注目してください。「GlassFish v3 (埋め込み可能コンテナ)」→「glassfish-embedded-static-shell.jar」というファイルが組み込まれている事を確認できます。このライブラリはとても重要で、これを利用しEJB の組み込みコンテナを利用できるようになります。
それでは、実際に EJB コンポーネントを作成してみましょう。今回はかんたんなサンプルとして、ステートレス Session Bean を1つ作成します。
実装コードは下記を記載します。
EJB コンポーネントを作成しましたので、次に JUnit のテストコードを記載します。NetBeans のメニューより、「新規」→「JUnit テスト …」を選択してください。メニュー中に表示されない場合は、「その他…」を選択した後「JUnit テスト …」を選択してください。
JUnit のテスト用コードを記述するクラス名、パッケージ等を定義します。
次に、JUnit のバージョンを選択し、「選択(S)」ボタンを押下します。
クラスの雛形が自動生成されますので、テスト用のメソッドを実装します。
記載するコードを下記に記載します。
@Test public void testSayHello() { Map p = new HashMap(); p.put("<strong>org.glassfish.ejb.embedded.glassfish.instance.root</strong>", "<strong>/Applications/GlassFish/glassfishv3-webprofile/glassfish/domains/domain1</strong>"); <span style="color:blue;"><strong>EJBContainer container = EJBContainer.createEJBContainer(p);</strong></span> try{ Hello hello = (Hello)<strong>container.getContext().lookup("java:global/classes/Hello");</strong> System.out.println(hello.sayHello()); }catch(Exception e){ e.printStackTrace(); } container.close(); }
このコード中で重要なポイントは、EJBContainer.createEJBContainer()を呼び出す所です。EJBContainer クラスは、組み込み可能な EJB コンテナ上で EJB コンポーネントを実行するためのクラスです。スタティックメソッドの createEJBContainer() を実行すると組み込み可能 EJB コンテナのインスタンスの作成と初期化を行います。引数 Map 中で設定プロパティを記載する事ができます。
今回、GlassFish の組み込み可能コンテナを利用しますので、org.glassfish.ejb.embedded.glassfish.instance.root に GlassFish のインスタンスのルート(通常 DAS のドメインの場所)を指定して初期化と起動を行います。EJB コンテナの初期化が完了した後、JNDI のルックアップで EJB コンポーネントを取得し適宜評価プログラムを記載することができるようになります。
テスト用のコードを記載した後、テストを実行してみましょう。プロジェクトのメニューより「テスト」を選択実行してください。
テストを実行すると下記のようなテスト結果が得られます。
さて、上記でかんたんなテストは完了しましたが、もう一つ JPA-EJB を使った DB連携の EJBコンポーネントを作成しテストをしてみましょう。まず、メニューより「新規」→「データベースからのエンティティクラス…」を選択して Entity Bean を作成しましょう。
データソース(jdbc/sample)を選択し、「使用可能な表(T)」から DB 上に存在するテーブルを選択し、追加します。ここでは自分で作成した Person テービルを利用しています。
次に、ワーニングが表示されている箇所に注目してください。現在のプロジェクトには持続性ユニットが存在しないため作成してくださいとメッセージが表示されています。そこで「持続性ユニットを作成…」ボタンを押下して作成します。
「持続性ユニット名」、「持続性プロバイダ」、「データソース」を選択し「作成」ボタンを押下します。
持続性ユニットを作成すると、ワーニングが消えますので「次へ>」ボタンを押下します。
最後に、データベースのテーブルと Entity Bean のマッピングに関する設定を行い「完了(F)」ボタンを押下します。
上記で、DB のPERSON テーブルから、ファイル名 Person.java の Entity Bean が自動生成されます。自動生成されるコードは下記のようなコードです。
package test; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; @Entity @Table(name = "PERSON") @NamedQueries({ @NamedQuery(name = "Person.findAll", query = "SELECT p FROM Person p"), @NamedQuery(name = "Person.findById", query = "SELECT p FROM Person p WHERE p.id = :id"), @NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE p.name = :name"), @NamedQuery(name = "Person.findByAge", query = "SELECT p FROM Person p WHERE p.age = :age"), @NamedQuery(name = "Person.findByAddress1", query = "SELECT p FROM Person p WHERE p.address1 = :address1"), @NamedQuery(name = "Person.findByTelephone", query = "SELECT p FROM Person p WHERE p.telephone = :telephone")}) public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @Basic(optional = false) @Column(name = "ID") private Long id; @Basic(optional = false) @Column(name = "NAME") private String name; @Basic(optional = false) @Column(name = "AGE") private long age; @Basic(optional = false) @Column(name = "ADDRESS1") private String address1; @Basic(optional = false) @Column(name = "TELEPHONE") private String telephone; public Person() { } public Person(Long id) { this.id = id; } public Person(Long id, String name, long age, String address1, String telephone) { this.id = id; this.name = name; this.age = age; this.address1 = address1; this.telephone = telephone; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getAge() { return age; } public void setAge(long age) { this.age = age; } public String getAddress1() { return address1; } public void setAddress1(String address1) { this.address1 = address1; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } @Override public int hashCode() { int hash = 0; hash += (id != null ? id.hashCode() : 0); return hash; } @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set if (!(object instanceof Person)) { return false; } Person other = (Person) object; if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { return false; } return true; } @Override public String toString() { return "test.Person[id=" + id + "]"; } }
Entity Bean を作成しましたのでこの Entity Bean を扱う Session Bean を作成します。メニューより「セッション Bean…」を 選択してください。
今回はステートレス Session Bean である PersonController を作成します。「EJB 名(N)」、「プロジェクト名」、「場所」、「パッケージ」、「セッションのタイプ」、「インタフェースを作成」それぞれを入力/選択し「完了(F)」ボタンを押下します。
PersonController には下記のコードを記載してください。
package test; import javax.ejb.Stateless; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; @Stateless public class PersonController { @PersistenceContext(unitName="EmbeddedEJBPU") private EntityManager em; public List<Person> findAllPersons(){ //Entity(Person)クラスの @NamedQuery の記載に基づき検索 Query query = em.createNamedQuery("Person.findAll"); return (List<Person>) query.getResultList(); } public Person createPerson(Person person){ em.persist(person); return person; } }
Session Bean を作成しましたので、最後に JUnit 用のテストコードを記述してみましょう。先ほどと同様に EJBContainer.creaateEJBContainer() を呼び出し、PersonContoroller のオブジェクトを取得した後、PersonController オブジェクト内のメソッドを呼び出してみましょう。
ここで、実装するコードは下記のようになります。
@Test public void testGetPersonCount() { Map p = new HashMap(); p.put("org.glassfish.ejb.embedded.glassfish.instance.root", "/Applications/GlassFish/glassfishv3-webprofile/glassfish/domains/domain1"); EJBContainer container = EJBContainer.createEJBContainer(p); try{ PersonController pController = (PersonController)container.getContext(). lookup("java:global/classes/PersonController"); List persons = pController.findAllPersons(); Iterator iterator = persons.iterator(); while (iterator.hasNext()) { Person person = (Person)iterator.next(); System.out.println("Person name: " + person.getName()); } }catch(Exception e){ e.printStackTrace(); } }
テストコードを記載した後、「テスト」を実行してください。
先ほどと同様、DB アクセス後、テーブルを参照し情報を取得する事も問題なくできました。後は個々のアプリケーションに応じて詳細なテストコードを記載して頂くことができます。
以上のように EJB 3.1 で提供された組み込みコンテナ機能を使うと EJB コンポーネントに対する単体テストがとてもかんたんになります。今後はこの組み込みコンテナの機能を使い EJB のテストを楽に行ってください。
※補足:
実行した際に吐き出されるコンソールのメッセージを下記に表示します。
2010/04/08 17:01:13 com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates 情報: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate 2010/04/08 17:01:13 com.sun.common.util.logging.LoggingConfigImpl openPropFile 情報: Cannot read logging.properties file. 2010/04/08 17:01:13 com.sun.enterprise.web.WebContainer configureHost 致命的: WEB0355: network-listener [http-listener-2] referenced by virtual server [server] does not exist 2010/04/08 17:01:13 com.sun.enterprise.web.WebContainer configureHost 致命的: WEB0355: network-listener [http-listener-1] referenced by virtual server [server] does not exist 2010/04/08 17:01:13 com.sun.enterprise.web.WebContainer createHosts 情報: Created virtual server server 2010/04/08 17:01:13 com.sun.enterprise.web.WebContainer configureHost 致命的: WEB0355: network-listener [admin-listener] referenced by virtual server [__asadmin] does not exist 2010/04/08 17:01:13 com.sun.enterprise.web.WebContainer createHosts 情報: Created virtual server __asadmin 2010/04/08 17:01:14 com.sun.enterprise.web.WebContainer loadSystemDefaultWebModules 情報: Virtual server server loaded system default web module 2010/04/08 17:01:17 com.sun.enterprise.v3.services.impl.WebContainerStarter startWebContainer 情報: Done with starting web container 2010/04/08 17:01:17 com.sun.enterprise.v3.server.AppServerStartup run 情報: GlassFish v3 (74.2) startup time : Embedded(1291ms) startup services(4389ms) total(5680ms) 2010/04/08 17:01:17 org.glassfish.admin.mbeanserver.JMXStartupService$JMXConnectorsStarterThread run 情報: JMXStartupService: JMXConnector system is disabled, skipping. 2010/04/08 17:01:17 AppServerStartup run 情報: [Thread[GlassFish Kernel Main Thread,5,main]] started 2010/04/08 17:01:19 com.sun.enterprise.security.SecurityLifecycle <init> 情報: security.secmgroff 2010/04/08 17:01:19 com.sun.enterprise.security.ssl.SSLUtils checkCertificateDates 致命的: java_security.expired_certificate 2010/04/08 17:01:19 com.sun.enterprise.security.SecurityLifecycle onInitialization 情報: Security startup service called 2010/04/08 17:01:19 com.sun.enterprise.security.PolicyLoader loadPolicy 情報: policy.loading 2010/04/08 17:01:19 com.sun.enterprise.security.auth.realm.Realm doInstantiate 情報: Realm admin-realm of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created. 2010/04/08 17:01:19 com.sun.enterprise.security.auth.realm.Realm doInstantiate 情報: Realm file of classtype com.sun.enterprise.security.auth.realm.file.FileRealm successfully created. 2010/04/08 17:01:19 com.sun.enterprise.security.auth.realm.Realm doInstantiate 情報: Realm certificate of classtype com.sun.enterprise.security.auth.realm.certificate.CertificateRealm successfully created. 2010/04/08 17:01:19 com.sun.enterprise.security.SecurityLifecycle onInitialization 情報: Security service(s) started successfully…. 2010/04/08 17:01:20 org.hibernate.validator.util.Version <clinit> 情報: Hibernate Validator bean-validator-3.0-JBoss-4.0.2 2010/04/08 17:01:20 org.hibernate.validator.engine.resolver.DefaultTraversableResolver detectJPA 情報: Instantiated an instance of org.hibernate.validator.engine.resolver.JPATraversableResolver. 2010/04/08 17:01:21 com.sun.ejb.containers.BaseContainer initializeHome 情報: Portable JNDI names for EJB PersonController : [java:global/classes/PersonController, java:global/classes/PersonController!test.PersonController] 2010/04/08 17:01:21 com.sun.ejb.containers.BaseContainer initializeHome 情報: Portable JNDI names for EJB Hello : [java:global/classes/Hello, java:global/classes/Hello!test.Hello] 2010/04/08 17:01:21 org.jboss.weld.bootstrap.WeldBootstrap <clinit> 情報: WELD-000900 1.0.0 (SP4) 2010/04/08 17:01:21 org.hibernate.validator.engine.resolver.DefaultTraversableResolver detectJPA 情報: Instantiated an instance of org.hibernate.validator.engine.resolver.JPATraversableResolver. nullID: /Users/yt133043/NetBeansProjects/EmbeddedableTest/build/classes/ CLASSES: [class test.exceptions.IllegalOrphanException, class test.exceptions.NonexistentEntityException, class test.exceptions.PreexistingEntityException, class test.exceptions.RollbackFailureException, class test.Hello, class test.Person, class test.PersonController] Hello Embedded TEST 2010/04/08 17:01:25 org.hibernate.validator.engine.resolver.DefaultTraversableResolver detectJPA |