http://www.oracle.com/technetwork/articles/java/springtojavaee2-1414289.html
はじめに
第1部では、SpringのPet ClinicサンプルアプリケーションをJava EEで完全に書き換えはじめました。アプリケーションの永続化レイヤはJava Persistence API (JPA) 2.0で開発しましたが、NetBeansを使うとほとんどの永続化レイヤに関するコードを既存のデータベーススキーマから生成してくれることがわかりました。
また、生成されたコードを分析し、NetBeansがすぐれたJPA機能(表を再生成するために使用できるアノテーション属性を含む)を持っていること、最大許容文字列長やフィールドのNULL可否、Beanバリデーションのサポートなどの情報を保持していることにも言及しました。
第2部では、再度NetBeansで利用できるJava EEツールを存分に活用してPet Clinicアプリケーションの書き換えを続けます。Data Access Object (DAO)として振る舞うEnterprise JavaBeans (EJB) 3.1のsession beanやJavaServer Faces (JSF) 2.0のmanaged beansやページを作成します。
[注]
この記事で説明しているサンプルのソースコードは以下のリンクよりダウンロードできます。
http://www.oracle.com/technetwork/articles/java/petclinicjavaee-1356466.zip
Session Bean、Managed Bean、JSFページの作成
JPAエンティティができたので、JSFページ、JSF Managed Bean、DAOを考えましょう。NetBeansのJSF Pages from Entity Classesウィザードを使えばすべて一揆に作成できます。このウィザードは[
ファイル] >[新規ファイル]>[持続性]から、[エンティティーからのJSFページクラス]を選択します(図1)
図1:エンティティーからのJSFページウィザード |
図2:JPAエンティティの選択 |
図3:パッケージの選択 |
図4:NetBeansで生成したJSFページ |
図5:NetBeansが生成したController |
生成したアプリケーションを動かしてみる
プロジェクトを右クリックしてRunを選択するだけで、アプリケーションを実行できます(図6)。
図6:アプリケーションの実行 |
図7:実行中のアプリケーション |
図8:データベースのレコードを表示 |
任意のアイテムのViewリンクをクリックすると、対応するアイテムの全情報を見ることができます(図9)。
図9:アイテムの情報を表示 |
viewページやlistページからEditリンクをクリックして、既存のデータベース中のレコードを変更できます(図10)。
図10:データベース中の既存のレコードを編集 |
Create New Petのリンクをクリックすれば、新しいレコードをデータベースに追加することもできます(このUIはEditページにほとんど同じですので、ここではお見せしません)。Destroyリンクをクリックすれば、データベースからレコードを削除できます。
生成されたコードの精査
NetBeansのウィザードは多くのコード(JSFページ、JSF Managed Bean、ユーティリティクラス、EJB 3.1 Session Beanなど)を生成します。生成されたJSFページはきわめて単純で、標準的なJSFページなので、詳細には触れません。生成されたJSFページがFaceletsテンプレート機能を使うと、全面的に簡単にレイアウトを変更できることは注目すべきことでしょう。生成したテンプレートは十分に適切な名前、template.xhtmlという名前が付いています(コード1)。
コード1:生成されたFaceletsテンプレート驚くことではありませんが、生成されたテンプレートがFaceletsの<ui:insert>タグを使って、テンプレートで全てのページの変更対象のエリアをマークしています。生成されたほとんどのページには、対応する<ui:define>タグがあることが確認できます(コード2)。
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><ui:insert name="title">Default Title</ui:insert></title>
<h:outputStylesheet name="css/jsfcrud.css"/>
</h:head>
<h:body>
<h1>
<ui:insert name="title">Default Title</ui:insert>
</h1>
<p>
<ui:insert name="body">Default Body</ui:insert>
</p>
</h:body>
</html>
コード2:“View Pet” JSFページJSFページは自動生成されたCSSスタイルシートでスタイルが指定されていることにも言及しておきましょう。生成されたJSFページは、JSF 2.0で導入された、標準リソースディレクトリの機能を使用しています。CSSスタイルシートはFaceletsテンプレート中の以下のマークアップに含まれています。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="/template.xhtml">
<ui:define name="title">
<h:outputText value="#{bundle.ViewPetTitle}"></h:outputText>
</ui:define>
<ui:define name="body">
<!-- Content omitted for brevity -->
</ui:define>
</ui:composition>
</html>
<h:outputStylesheet name="css/jsfcrud.css"/>
デフォルトで、JSF実装はresourcesという名前のディレクトリを探します。これはMETA-INFの中もしくはWARファイルのルートにあります。resourcesディレクトリ中にcssというサブディレクトリを探し、特定のCSSスタイルシートをcssディレクトリで探します。ディレクトリ構造は対応するディレクトリをNetBeansプロジェクトビューで拡大すれば確認できます(図11)。図11:ディレクトリ構造の表示 |
図12:生成されたJSF Managed Bean |
コード3:Controllerメソッド
package com.ensode.petclinicjavaee.managedbean;
import com.ensode.petclinicjavaee.entity.Pet;
/* Imports Omitted */
@ManagedBean(name = "petController")
@SessionScoped
public class PetController implements Serializable {
private Pet current;
private DataModel items = null;
@EJB
private com.ensode.petclinicjavaee.session.PetFacade ejbFacade;
private PaginationHelper pagination;
private int selectedItemIndex;
public PetController() {
}
/* getSelected() method omitted */
/* getFacade() method omitted */
public PaginationHelper getPagination() {
if (pagination == null) {
pagination = new PaginationHelper(10) {
@Override
public int getItemsCount() {
return getFacade().count();
}
@Override
public DataModel createPageDataModel() {
return new ListDataModel(getFacade().findRange(new int[]{
getPageFirstItem(), getPageFirstItem() + getPageSize()}));
}
};
}
return pagination;
}
/* prepareList() method omitted */
public String prepareView() {
current = (Pet) getItems().getRowData();
selectedItemIndex = pagination.getPageFirstItem() + getItems().
getRowIndex();
return "View";
}
public String prepareCreate() {
current = new Pet();
selectedItemIndex = -1;
return "Create";
}
/* create method omitted */
public String prepareEdit() {
current = (Pet) getItems().getRowData();
selectedItemIndex = pagination.getPageFirstItem() + getItems().
getRowIndex();
return "Edit";
}
/* update method omitted */
/* destroy method omitted */
/* destroyAndView() method omitted */
/* performDestroy() method omitted */
/* updateCurrentItem() method omitted */
public DataModel getItems() {
if (items == null) {
items = getPagination().createPageDataModel();
}
return items;
}
private void recreateModel() {
items = null;
}
public String next() {
getPagination().nextPage();
recreateModel();
return "List";
}
public String previous() {
getPagination().previousPage();
recreateModel();
return "List";
}
public SelectItem[] getItemsAvailableSelectMany() {
return JsfUtil.getSelectItems(ejbFacade.findAll(), false);
}
public SelectItem[] getItemsAvailableSelectOne() {
return JsfUtil.getSelectItems(ejbFacade.findAll(), true);
}
@FacesConverter(forClass = Pet.class)
public static class PetControllerConverter implements Converter {
public Object getAsObject(FacesContext facesContext,
UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
PetController controller = (PetController) facesContext.
getApplication().getELResolver().
getValue(facesContext.getELContext(), null, "petController");
return controller.ejbFacade.find(getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
public String getAsString(FacesContext facesContext,
UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Pet) {
Pet o = (Pet) object;
return getStringKey(o.getId());
} else {
throw new IllegalArgumentException("object " + object +
" is of type " + object.getClass().getName() +
"; expected type: " + PetController.class.getName());
}
}
}
}
JSF Managed Beanは@FacesConverterアノテーションが付いた内部クラスを持っています。このアノテーションはJSF 2.0で導入され、注釈が付いたクラスはJSFコンバータとして振る舞います。JSFコンバータはJavaオブジェクトを文字列に変換(その逆もあり)します。getAsObject()メソッドは文字列を受け取り対応するオブジェクトに変換します。生成されたこのメソッドの実装は、JPAエンティティのidプロパティの文字列を取り、それを対応する整数型に変換し、生成されたSessionファサードでデータベースをクエリして、クエリ結果に対応するJPAエンティティオブジェクトを取得します。生成されたgetAsString() メソッドは単にJPAエンティティのIdプロパティの値を文字列として返すだけです。
注目すべきことがまだあります。生成されたJSF Managed BeanはJSF DataModel実装を使います(コード4)。JSF DataModelインターフェースを使うと、JSFデータテーブルに表示されるデータの操作が簡単になります。生成されたJSF Managed BeanもまたPaginationHelperというユーティリティクラスを使います。このクラスは、改ページ位置の自動修正機能を提供します。つまり、リストの残りの部分に"前"と"次"のリンクを提供しながら、ページ上で同時にいくつかの項目を表示する機能です。
コード4:PaginationHelper.javaおわかりのように、PaginationHelperは抽象クラスで、2個の抽象メソッドを提供しています。一つはgetItemsCount()で、もう一つはcreatePageDataModel() です。各々の生成されたJSF Managed Beanは、PaginationHelperクラスを拡張している匿名内部クラスを提供しています。この匿名クラスでは上記2メソッドの具体的な実装を提供しています(図5)。
package com.ensode.petclinicjavaee.managedbean.util;
import javax.faces.model.DataModel;
public abstract class PaginationHelper {
private int pageSize;
private int page;
public PaginationHelper(int pageSize) {
this.pageSize = pageSize;
}
public abstract int getItemsCount();
public abstract DataModel createPageDataModel();
public int getPageFirstItem() {
return page * pageSize;
}
public int getPageLastItem() {
int i = getPageFirstItem() + pageSize - 1;
int count = getItemsCount() - 1;
if (i > count) {
i = count;
}
if (i < 0) {
i = 0;
}
return i;
}
public boolean isHasNextPage() {
return (page + 1) * pageSize + 1 <= getItemsCount();
}
public void nextPage() {
if (isHasNextPage()) {
page++;
}
}
public boolean isHasPreviousPage() {
return page > 0;
}
public void previousPage() {
if (isHasPreviousPage()) {
page--;
}
}
public int getPageSize() {
return pageSize;
}
}
コード5:PaginationHelperクラスを拡張した匿名内部クラス
public PaginationHelper getPagination() {
if (pagination == null) {
pagination = new PaginationHelper(10) {
@Override
public int getItemsCount() {
return getFacade().count();
}
@Override
public DataModel createPageDataModel() {
return new ListDataModel(getFacade().findRange(new int[]{
getPageFirstItem(), getPageFirstItem() + getPageSize()}));
}
};
}
return pagination;
}
getItemsCount() の実装は、生成されたEJBファサードのcount()メソッドを呼び出すだけです。これは、JPAエンティティにマッピングされているデータベース表の行数を順番に返します。createPageDataModel() メソッドはもうちょっと興味深いものです。LazyローディングStrategyを実装しているのです。つまり、ページに表示されているアイテムのみデータベースからリアルタイムに取り出す、というものです。通常はデータベースから全件取り出し、別のアイテムで改ページするためにメモリ上に種々のアイテムを保持しておかねばならないため、これはメモリの節約に役立ちます。
生成されたJSFコードをみてきたので、生成されたEJB 3.1 Session Beanを見ていきましょう。全ての生成されたSession BeanはAbstractFacade.java(コード6)と呼ばれる親クラスを拡張したものです。
コード6:AbstractFacade.java
package com.ensode.petclinicjavaee.session;
/* imports omitted */
public abstract class AbstractFacade {
private Class entityClass;
public AbstractFacade(Class entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public void create(T entity) {
getEntityManager().persist(entity);
}
public void edit(T entity) {
getEntityManager().merge(entity);
}
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
public List findAll() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
public List findRange(int[] range) {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
javax.persistence.Query q = getEntityManager().createQuery(cq);
q.setMaxResults(range[1] - range[0]);
q.setFirstResult(range[0]);
return q.getResultList();
}
public int count() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
javax.persistence.criteria.Root rt = cq.from(entityClass);
cq.select(getEntityManager().getCriteriaBuilder().count(rt));
javax.persistence.Query q = getEntityManager().createQuery(cq);
return ((Long) q.getSingleResult()).intValue();
}
}
AbstractFacadeはFacadeデザインパターンを実装しています。つまり、JPA EntityManagerへのシンプルなインターフェースを提供する、というものです。生成されたメソッドはJPA 2.0 criteria APIを使います。このAPIはJPA仕様への主要な追加項目であり、動的にタイプセーフなJPAクエリを作成することができるというものです。生成されたSession Beanによって行われる作業のほとんどは、親クラスが実施します。子クラスがやることは、コンストラクタを実装することに加え、EntityManagerのインスタンスをJava EE Dependency Injectionメカニズムを使って注入することです(コード7)。
コード7:EntityManagerの注入
package com.ensode.petclinicjavaee.session;
/* imports omitted */
@Stateless
public class PetFacade extends AbstractFacade {
@PersistenceContext(unitName = "PetClinicJavaEEPU")
private EntityManager em;
protected EntityManager getEntityManager() {
return em;
}
public PetFacade() {
super(Pet.class);
}
}
おわかりのように、子クラスのコンストラクタは単に親クラスのコンストラクタを呼び出し、このSession Beanが操作するJPAエンティティの型を渡しているだけです。親クラスは順にgenericsを使って動的に正しい型をentityClassインスタンスの値に追加します。@PersistenceContextアノテーションで注釈付けし、(persistence.xmlで定義されている)Persistence Unitの名前を渡すと、EntityManagerのインスタンスを注入することができます。ここは特に何もありません。
まとめ
この投稿では、生成されたアプリケーションが動作していること、「ボンネットの下」を見て何が起きているのかを確認しました。第3部では、生成されたコードを変更してユーザフレンドリーにし、Pet ClinicアプリケーションのJava EE版とSpring版を比較してみようと思います。
参考
- Spring to Java EE Migration Part 1
http://www.oracle.com/technetwork/articles/java/springtojavaee-522240.html - Spring to Java EE Migration Part 3
http://www.oracle.com/technetwork/articles/java/springtojavaee3-1569377.html - Spring Framework
http://www.springsource.org/ - NetBeans
http://netbeans.org/ - Java EE 6
http://www.oracle.com/technetwork/java/javaee/overview/index.html
David Heffelfinger
ソフトウェアコンサルタンティングファームであるEnsode Technology, LLCのCTO。
1995年よりアーキテクチャ、デザイン、ソフトウェア開発に従事。1996年からJavaを主要なプログラミング言語として活用し、大規模プロジェクト(アメリカ国土安全保障省、Freddie Mac、Fannie Mae、アメリカ国防総省など)に参画。Southern Methodist Universityソフトウェア工学修士。
0 件のコメント:
コメントを投稿