DeltaSpike DataをJava EEアプリケーションサーバー上で動かす(1)

前回のブログで紹介したDeltaSpike DataモジュールをJava EEアプリケーションサーバー上で動かしてみましょう。どのアプリケーションサーバーを使ってもよいのですが、このブログではWildFlyを使った手順を紹介します。WildFlyにはCDIコンテナとしてWeldが組み込まれていますし、H2データベースが最初からインストールされているので、DeltaSpike Dataモジュールをすぐに試してみることができます。このページの最後にサンプルプログラムのリンクをつけていますので実際にプログラムを動かしながら確認してください。

WildFlyのインストール

WildFlyはJBoss.orgにおいてオープンソースで開発されているJava EE 7に対応したアプリケーションサーバーです(以前はJBossプロジェクトのアプリケーションサーバーはJBoss ASと呼ばれていましたが、JBoss ASはVersion 7までで、Version 8からWildFlyはという名前に変更されました)。

WildFlyの最新安定バージョンは9.0.1.Finalですが、今回は10.0.0.CR4を使うことにします。以下のサイトからwildfly-10.0.0.CR4.zipをダウンロードして、適当なディレクトリで解凍してください。

WildFlyの起動と停止

WildFlyの起動方法にはStandaloneモードとDomainモードの二つがあります。ここでは、単一サーバーで確認するためStandaloneモードで起動します。

$ cd wildfly-10.0.0.CR4/bin
$ ./standalone.sh

 

以後、WildFlyを起動したままの状態で作業を進めますが、サーバーの停止の際は起動したコンソール上でCntrl-Cを押してください。

ベースとなるアプリケーションの準備

DeltaSpike Dataモジュールのサンプルはスクラッチから作るのではなくて、ありもののJava EEアプリケーションを修正して作ることにします。WildFly用のJava EE 7 Webアプリケーションを作成するMavenアーキタイプを使うことにしましょう。以下のコマンドを叩くと、tanoseam-webappという名前のMavenプロジェクトが作成されます。

$ mvn archetype:generate -DarchetypeGroupId=org.wildfly.archetype -DarchetypeArtifactId=wildfly-javaee7-webapp-archetype -DarchetypeVersion=8.2.0.Final -DgroupId=org.tanoseam.examples -DartifactId=tanoseam-webapp -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false

 

このアプリケーションはkitchensinkと呼ばれるJSF/REST/EJB/JPAを使ったWebアプリケーションです。このJPAの部分をDeltaSpike Dataモジュールを使って置き換えます。

DeltaSpikeに対応させるためのアプリケーションの修正

アプリケーション内で直接EntityManagerを操作している箇所をDeltaSpike Dataを使うように修正します。このアプリケーションでは、メンバーの検索はMemberRepository、メンバー登録はMemberRegistrationというクラスが担当していますので、それぞれ修正が必要です。

1. アプリケーションのpom.xmlにDeltaSpike Dataの依存ライブラリを追加します。バージョンは最新版の1.5.2を指定しましょう。

		<dependency>
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-data-module-api</artifactId>
			<version>${version.deltaspike}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-data-module-impl</artifactId>
			<version>${version.deltaspike}</version>
		</dependency>

2. tanoseam-webappプロジェクトのソースを修正して、データベースの検索部分をDeltaSpike Dataを使うように変更します。修正箇所は3つのファイルです。

  • org.tanoseam.examples.data.data.MemberRepository.javaを(前回のブログで紹介した)DeltaSpike Dataモジュールを使ったリポジトリ実装で置き換えてください。
package org.tanoseam.examples.data;

import org.tanoseam.examples.model.Member;
import org.apache.deltaspike.data.api.EntityRepository;
import org.apache.deltaspike.data.api.Query;
import org.apache.deltaspike.data.api.Repository;
import java.util.List;

@Repository
public interface MemberRepository extends EntityRepository<Member, Long> {

    @Query("select m from Member m where m.email = ?1")
	public Member findByEmail(String email);

    @Query("select m from Member m order by name")
	public List<Member> findAllOrderedByName();
}
  • 次に、RESTサービスを上記リポジトリが提供するインタフェースに合わせて修正します。
    org.tanoseam.examples.rest.MemberResourceRESTService.javaのlookupMemberById()メソッドにおいてfindById(id)をfindBy(id)へ変更してください。
@GET
@Path("/{id:[0-9][0-9]*}")
@Produces(MediaType.APPLICATION_JSON)
public Member lookupMemberById(@PathParam("id") long id) {
    //Member member = repository.findById(id);
    Member member = repository.findBy(id);
    if (member == null) {
        throw new WebApplicationException(Response.Status.NOT_FOUND);
    }
    return member;
}
  • 最後に、org.tanoseam.examples.service.MemberRegistration.javaを修正します。MemberRepositoryのBeanを変数repositoryにインジェクトして、em.persist(member)をrepository.save(member)に置き換えてください。
@Inject
private MemberRepository repository;

public void register(Member member) throws Exception {
    log.info("Registering " + member.getName());
    //em.persist(member);
    repository.save(member);
    memberEventSrc.fire(member);
}

WildFlyへのデプロイ

ソースを修正したtanoseam-webappプロジェクトをビルドし、WildFlyへデプロイします。WildFlyへのデプロイには管理コンソールを使うことも可能ですが、WildFlyへのデプロイ用のMavenプラグインがありますので、今回はそれを使います。

$ cd tanoseam-webapp
$ mvn clean package wildfly:deploy

 

アプリケーションの実行

デプロイしたアプリケーションを実行するにはWebブラウザで以下を指定します。

このアプリケーションはメンバー情報(Name, Emai, Phone#)を入力する画面を表示します。各入力項目を入力してRegisterボタンを押下するとメンバーが登録されるはずです。しかし、今回ソースを修正したものを実行すると下図のようにボタンの右側に”A JTA EntityManager cannot use getTransaction()”というエラーメッセージが表示されてしまいます。

tanoseam-webapp

WildFlyのコンソールログを見ると、DeltaSpike Dataモジュールの処理の途中でIllegalStateException例外がスローされてEJB呼び出しが失敗しているようです。スタックトレースをよく見ると、この例外はResourceLocalTransactionStrategyというクラスのgetTransaction()メソッドで発生しています。

Caused by: java.lang.IllegalStateException: A JTA EntityManager cannot use getTransaction()
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.getTransaction(AbstractEntityManagerImpl.java:1333)
at org.jboss.as.jpa.container.AbstractEntityManager.getTransaction(AbstractEntityManager.java:518)
at org.apache.deltaspike.jpa.impl.transaction.ResourceLocalTransactionStrategy.getTransaction(ResourceLocalTransactionStrategy.java:370)
at org.apache.deltaspike.jpa.impl.transaction.ResourceLocalTransactionStrategy.rollbackAllTransactions(ResourceLocalTransactionStrategy.java:336)
at org.apache.deltaspike.jpa.impl.transaction.ResourceLocalTransactionStrategy.execute(ResourceLocalTransactionStrategy.java:154)
at org.apache.deltaspike.data.impl.tx.TransactionalQueryRunner.executeTransactional(TransactionalQueryRunner.java:72)
at org.apache.deltaspike.data.impl.tx.TransactionalQueryRunner.executeQuery(TransactionalQueryRunner.java:54)
at org.apache.deltaspike.data.impl.handler.QueryHandler.process(QueryHandler.java:122)
… 127 more

 

実行時エラーの回避

DeltaSpike DataはDeltaSpike JPAモジュールを使ってトランザクションを制御しています。このエラーは、DeltaSpike JPAモジュールにおけるデフォルトのトランザクションタイプがRESOURCE_LOCALであることが原因です。JPAではRESOURCE_LOCALなトランザクションの場合はEntityManager APIを使ってEntityTransactionを取得し、明示的にトランザクションを開始する必要があります。

しかし、今回のサンプルではMemberRegistryというEJB内部でDeltaSpike Dataのリポジトリ呼び出していますので、リポジトリの処理を開始する前にEJBによってトランザクションはすでに開始されています。そこで、DeltaSpike JPAモジュールはRESOURCE_LOCALなトランザクションを開始することはできず、今回のエラーに至ったと考えられます。

このエラーを回避するには、DeltaSpike Dataにおけるトランザクションの振る舞いをCMT(Container Managed Transaction)に対応させるように変更する必要があります。これを行うには、META-INF/apache-deltaspike.propertiesに以下のようにTransactionStrategyとしてContainerManagedTransactionStrategyを設定します。

globalAlternatives.org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy=org.apache.deltaspike.jpa.impl.transaction.ContainerManagedTransactionStrategy

 

動作確認

エラーを回避する設定をした後にアプリケーションを再デプロイして、いくつかメンバーを登録してみましょう。表のメンバーの表示はMemberListProducerによって行なわれ、その中でMemberRepositoryのfindAllOrderedByName()メソッドが呼び出されています。表のメンバーが名前の順(order by name)になっていることを確認してください。下図の例では(idから類推できるように)David, Bob, Andyの順に登録しましたが、表示上は名前でソートされてAndy, Bob, Davidの順になっています。

members

注意:今回のプログラムではH2データベースを使っていますが、これはデモやテスト用途に適したインメモリの設定になっているため、アプリケーションの再デプロイやサーバーの再起動によってDBに登録済みのデータは消えてしまいます。本番環境でDBを利用する場合は、H2の設定を変更するか、PostgreSQLやMySQLなどの他のデータベースを検討してみてください。

サンプルプログラム

この修正済みのプログラムはこちらからダウンロードできます。

少し手間がかかりましたが、WildFlyという実行環境が準備できるとDeltaSpike Dataのさまざまな機能を実機で試すことができるようになります。次回は、Arquillianを使ってDeltaSpike Dataの機能をテストする方法を説明します。

DeltaSpike Dataの紹介

DeltaSpike Dataモジュールは、Repositoryパターンを実現したモジュールです。JPAを使ったプログラミングでは同様なコードを何度も書く必要があります。Dataモジュールを使うとデータベースへのCRUD関連の機能やクエリ定義をリポジトリインタフェースに集約することができます。さらに、DTO (Data Transfer Object)とエンティティの間のマッピングを提供するなど、なかなか面白い機能も提供しています。

Dataモジュールを使ってリポジトリを作るには次のようなインタフェースを用意します。

例えば、Memberというエンティティクラスがあって、主キーの型がLongの場合、次のようにしてリポジトリを定義します。

@Repository
public interface MemberRepository extends EntityRepository<Member, Long> {}

たったこれだけで、JPAのAPIを使うことなく、Entityに簡単にアクセスできるようになります。EntityRepositoryインタフェースは、以下のようなメソッドを提供しています(雰囲気を伝えるため代表的なものをピックアップしています)。

  • E save(E entity)
  • void remove(E entity)
  • void refresh(E entity)
  • void flush()
  • E findBy(PK pk)
  • List<E> findAll()
  • Long count();

DeltaSpike Dataの簡単な使い方

リポジトリは@Injectで変数にインジェクトして使います。インジェクトのときに指定する型は@Repositoryをつけたインタフェースです。例えば、エンティティを保存するには次のようにします。

@Inject
private MemberRepository repository;

Member member = new Member();
member.setName("Neverbird");
member.setEmail("neverbird@mail.com");
member.setPhoneNumber("1234567890");

repository.save(member);

実装クラスがなくともリポジトリを利用できる理由

さて、ここまででリポジトリの実装クラスが登場していないのに気付きましたか? DeltaSpikeを使うと、インタフェースを宣言するだけでリポジトリを利用できるのです。実は、これは前回のブログ記事で紹介したPartialBeanモジュールを使っています。@Repositoryアノテーションのソースを確認すると、確かに@PartialBeanBindingを使って定義されています。

@Stereotype
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@PartialBeanBinding
public @interface Repository
{
 Class forEntity() default Object.class;
 String methodPrefix() default "";
}

PartialBeanは、インタフェースへのアクセスからInvocationHandlerへの呼び出しを行う仕組みを提供します。DeltaSpike DataモジュールはEntityRepositoryインタフェースのJPAによる実装をInvocationHandlerとして提供しています。@Repositoryを使ってリポジトリのインタフェースを宣言することで、リポジトリインタフェースへの呼び出しが内部でInvocationHandlerの呼び出しに変換されているのです。

セットアップ

DeltaSpike Dataモジュールを使うためにはpom.xmlに以下の依存ライブラリを追加します。

<dependency>
 <groupId>org.apache.deltaspike.modules</groupId>
 <artifactId>deltaspike-data-module-api</artifactId>
 <version>${version.deltaspike}</version>
</dependency>

<dependency>
 <groupId>org.apache.deltaspike.modules</groupId>
 <artifactId>deltaspike-data-module-impl</artifactId>
 <version>${version.deltaspike}</version>
</dependency>

クエリの定義

EntityRepositoryインタフェースにはfindByとfindAllしか検索方法が提供されていません。これでは実際のアプリケーションでは使い物にならないのでEntity定義に合わせて必要な検索のためのメソッドをリポジトリに追加します。

@Repository
public interface MemberRepository extends EntityRepository {

 @Query("select m from Member m where m.email = ?1")
 public Member findByEmail(String email);
}

DeltaSpike Dataモジュールの最大の特徴はクエリの定義方法です。上の例は検索メソッドに@Queryというアノテーションを付加することでクエリを定義しています。@Queryで指定しているのはJPAでサポートしているJPQLの文字列です。findByEmailメソッドの例のように検索メソッドに引数を指定することもできます。一見してわかるように、この書き方を使うとJPQLの式と検索メソッドの結びつきを直感的に理解することができます。

リポジトリのユーザーは以下のようにリポジトリに定義されたクエリのメソッドを呼び出します。

@Inject
private MemberRepository repository;

Member member = repository.findByEmail("neverbird@email.com")

DeltaSpike Dataの最大の特徴はクエリ定義だと思います。Dataモジュールでは@Query以外にも豊富なクエリの定義方法を提供しています。次回は、WildFly上で実際にDeltaSpike Dataを動かしながら、その機能を確認していきましょう。

%d人のブロガーが「いいね」をつけました。