はじめに
「AndroMDAでMDAの世界を体験する(コード生成編)」では、モデリングツールで作成したモデルをもとにAndroMDAでアプリケーションを自動生成し、実行するところまでを紹介しました。本稿では、AndroMDAによって自動生成されたアプリケーションコードの解析をします。
対象読者
JavaでWebアプリケーションを開発した事がある方を対象とします。
必要な環境
サンプルは以下の環境で動作確認を行っています。
自動生成されたアプリケーション(PSMとソースコード)
AndroMDAによって自動生成されたリソースは、以下のように分類できます。
分類 | リソース |
ソースコード・ページ | Javaソースコード、JSPファイル |
設定ファイル | JBoss、EARファイルDDファイル、WebアプリケーションDDファイル、Struts、Hibernate |
その他 | SQLファイル |
全体図
以下の図にアプリケーション全体のアーキテクチャーを示します。PIMのモデルと画面仕様がPSMに変換され、Struts、Spring、Hibernateを活用したJ2EEのアーキテクチャーが構成されています。DBのトランザクション設定はSpringによって管理されています。
クラス図
以下の自動生成されたクラス群と設定ファイルをクラス図に示します。
- ドメインクラスとHibernate設定ファイル
- ビジネスサービスとDAO(Data Access Object)
- プレゼンテーション層
ドメインクラスとHibenate設定ファイル
DBのテーブルにO/Rマッピングするクラス(Item
,Category
)とValueObject(ItemValueObject
,CategoryValueObject
)、Hibernateの設定ファイルを図に示しています。
ドメインクラス
自動生成されたドメインクラスはPOJO(Plain Old Java Object)クラスとValue Objectに分類できます。
- POJO(Plain Old Java Object)クラス
- Value Object
Item
クラスとCategory
クラスが作成され、それぞれのクラスを継承したItemImpl
クラスとCategoryImpl
クラスが作成されています。Item
,Category
クラスのそれぞれには、id
,name
などの属性とそれぞれのゲッター・セッターメソッドが定義されており、POJO(Plain Old Java Object)と呼ばれる構造のクラスとなっています。Item
クラスの抜粋です。itemcode
、name
、id
、categories
の属性が定義され、それそれのセッター・ゲッターメソッドが定義されています。public abstract class Item implements java.io.Serializable{ private java.lang.String itemcode; // 省略 itemcode setter,getterメソッド private java.lang.String name; // 省略 name setter,getterメソッド private java.lang.Long id; // 省略 id setter,getterメソッド private java.util.Collection categories = new java.util.HashSet(); // 省略 categories setter,getterメソッド }
ItemValueObject
とCategoryValueObject
が作成されています。AndroMDAが自動生成したソースでは、Hibernateによって取得したPOJO(ItemImpl
,CategoryImpl
)をValueObjectに格納し直しています(ManageableServiceLocator
クラスのtoValueObject
メソッド)。このようにPOJOクラスをラッピングした形で、クラスを作成するとHibernateのマッピングファイルに定義された以外のフィールドをクラスに定義したい場合などに便利です。今回のサンプルではItemValueObject
にcategoryLabels
、CategoryValueObject
にitemLabels
というフィールドが定義されています。ItemValueObject
のコードの抜粋です。public final class ItemValueObject implements java.io.Serializable{ private java.lang.String itemcode; // 省略 itemcode setter,getter private java.lang.String name; // 省略 name setter,getter private java.lang.Long id; // 省略 id setter,getter private java.util.Collection categories; // 省略 categories setter,getter // ValueObjectに追加されたフィールド private java.util.Collection categoriesLabels; // 省略 categoriesLabels setter,getter }
定義ファイル
- Hibernateマッピングファイル
ItemImpl
クラスと「ITEM」テーブル、CategoryImpl
クラスと「CATEGORY」テーブルのマッピングid
要素で主キーフィールド(フィールド名:id
)を定義。id
要素の子要素のgenerator
要素で、新規インスタンスのid
値の設定方法を定義。id
要素のclass
属性のnative
は、データベースの種類(Oracle、DB2、PostgreSQL、MySQL、HSQLDBなど)ごとにもっとも適したID連番採番方法がHibernateによって適用されるという事を意味します。many-to-many
要素で、商品とカテゴリの多対多の関連を定義。マッピングするテーブルは「CATEGORIES2ITEMS」。- Hibernate設定ファイル
many-to-many
の関連が定義されます。<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping default-cascade="none"> <class name="to.msn.wings.codezine.item.ItemImpl" table="ITEM" dynamic-insert="false" dynamic-update="false"> <id name="id" type="java.lang.Long" unsaved-value="null"> <column name="ID" sql-type="BIGINT"/> <generator class="native"> </generator> </id> <property name="itemcode" type="java.lang.String"> <column name="ITEMCODE" not-null="true" unique="true" sql-type="VARCHAR(256)"/> </property> <property name="name" type="java.lang.String"> <column name="NAME" not-null="true" unique="false" sql-type="VARCHAR(256)"/> </property> <set name="categories" table="CATEGORIES2ITEMS" order-by="CATEGORIES_FK" fetch="select" lazy="true" inverse="true"> <key foreign-key="CATEGORY_ITEMS_FKC"> <column name="ITEMS_FK" sql-type="BIGINT"/> </key> <many-to-many class="to.msn.wings.codezine.category.CategoryImpl" foreign-key="ITEM_CATEGORIES_FKC"> <column name="CATEGORIES_FK" sql-type="BIGINT"/> </many-to-many> </set> </class> </hibernate-mapping>
ファイル名 | 概要 |
encache.xml | Hibernateのキャッシュ機構設定ファイル |
hibernate.cfg.xml | HibernateのSessionFactoryのデータソース、マッピングなどを設定する |
hibernate.properties | Hibernateで使用するデータソース、データベースDirectなどを設定。本稿のサンプルでは「hibernate.cfg.xml」を参照する設定となっている |
jbpm.properties | JBPM(Java Business Process Management)の設定ファイル |
ビジネスサービスとDAO(Data Access Object)
SpringによってBeanのライフサイクルが管理されるビジネスサービス、DAOクラスそしてSpring設定ファイルです。
- Service Locator
- ビジネスサービスクラス
- DAO(Data Access Object)
ManageableServiceLocator
は、Springフレームワークが提供しているBeanFactoryReference
クラスを属性として保持し、Spring軽量コンテナへのアクセス手段であるApplicationContext
を返すgetContext
メソッドを実装しています。これによりSpringが管理しているビジネスサービスオブジェクトへのアクセスを提供するgetItemManageableService
メソッドとgetCategoryManageableService
メソッドを提供しています。public class ManageableServiceLocator{ // Spring Bean定義ファイル private final String DEFAULT_BEAN_REFERENCE_LOCATION = "beanRefFactory.xml"; // Spring BeanFactoryReference のインスタンス private org.springframework.beans.factory. access.BeanFactoryReference beanFactoryReference; // SpringのApplicationContextを取得 protected synchronized org.springframework. context.ApplicationContext getContext(){ // 省略 return (org.springframework.context.ApplicationContext) this.beanFactoryReference.getFactory(); } // ItemManageableServiceの取得 public final to.msn.wings.codezine.item. crud.ItemManageableService getItemManageableService(){ return (to.msn.wings.codezine. item.crud.ItemManageableService) getContext().getBean("ItemManageableService"); }
ItemManageableService
、CategoryManageableService
が作成され、その実装クラスとしてItemManageableServiceBase
、CategoryManageableServiceBase
が作成されています。ItemManageableServiceBase
とCategoryManageableServiceBase
はその属性としてDAOを保持しています。ビジネスサービスとDAOオブジェクトの関連付けはSpringの定義ファイルに設定されています。用語 | 説明 |
Aspect | 横断的関心時をモジュール化したもの。トランザクション処理、ロギング処理など。 |
Weaver | 本流の処理にAdviceの処理を組み込むこと。 |
Advice | 処理がJoinPointに達した時に実行されるコード。 |
JoinPoint | Adviceが実行可能なプログラムコード上の位置。フィールドへのアクセス、コンストラクタの呼び出し、メソッド呼び出しなど意味のある位置を表したもの。 |
PointCut | JoinPointの集合。 |
<bean id="ItemManageableService" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- Beanの実装処理 --> <property name="target"> <ref local="ItemManageableServiceBase"/></property> <!-- インターセプト対象のメソッドを定義した Javaインターフェース名--> <property name="proxyInterfaces"> <value>to.msn.wings.codezine.item.crud. ItemManageableService</value> </property> <!-- インターセプター(Interceptor)の名前 --> <property name="interceptorNames"> <list> <!-- トランザクション用 --> <value>manageableServiceTransactionInterceptor </value> <!-- Hibernate用 --> <value>hibernateInterceptor</value> </list> </property> </bean> <bean id="ItemManageableServiceBase" class="to.msn.wings.codezine.item.crud. ItemManageableServiceBase"> <property name="dao"><ref local="ItemManageableDao"/> </property> </bean>
class
属性で「org.springframework.aop.framework.ProxyFactoryBean」を指定しています。Proxy Factory Beanの「Proxy」とは、アスペクト指向の「Weaver」と同義で本処理のコードにAspectを「Weaving」し実行します。interceptorNames
プロパティで指定されている「manageableServiceTransactionInterceptor」は、「applicationContext-manageable.xml」に、「hibernateInterceptor」は、「applicationContext.xml」に定義されています。<bean id="manageableServiceTransactionInterceptor" class="org.springframework.transaction.interceptor. TransactionInterceptor"> <!-- トランザクションマネージャーへの参照 --> <property name="transactionManager"> <ref bean="transactionManager"/></property> <!-- トランザクション属性 --> <property name="transactionAttributeSource"> <value> <!--「PROPAGATION_REQUIRED」 =トランザクションが開始されていない場合 トランザクションを開始 --> to.msn.wings.codezine.item.crud. ItemManageableService.create=PROPAGATION_REQUIRED to.msn.wings.codezine.item.crud. ItemManageableService.read=PROPAGATION_REQUIRED // (省略) </value> </property> </bean>
<bean id="transactionManager" class="org.springframework. transaction.jta.JtaTransactionManager"></bean>
Session
オブジェクトを取得するためのインターフェースです。Hibernateの「Session」とはDBのコネクションと同じようなものと考えて良いでしょう。<!-- Hibernateインターセプター --> <bean id="hibernateInterceptor" class="org.springframework. orm.hibernate3.HibernateInterceptor"> <!-- Hibernate Session Factoryへの参照 --> <property name="sessionFactory"><ref bean="sessionFactory"/> </property> </bean> <!-- Hibernate Session Factory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3. LocalSessionFactoryBean"> <!-- データソースへの参照 --> <property name="dataSource"><ref bean="dataSource"/> </property> <property name="mappingResources"> <list> <value>to/msn/wings/codezine/item/Item.hbm.xml</value> <value>to/msn/wings/codezine/category/Category.hbm.xml </value> </list> </property> <!-- Hibernateのプロパティ --> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.dialect"> org.hibernate.dialect.HSQLDialect</prop> <prop key="hibernate.cache.use_query_cache">false</prop> <prop key="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider</prop> <prop key="hibernate.cache.query_cache_factory"> org.hibernate.cache.StandardQueryCacheFactory</prop> </props> </property> </bean>
<!-- データソースの定義 --> <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"><value>java:/DefaultDS </value></property> </bean>
public final class ItemManageableServiceBase implements ItemManageableService{ private to.msn.wings.codezine.item.crud.ItemManageableDao dao; public void setDao(to.msn.wings.codezine.item. crud.ItemManageableDao dao){ this.dao = dao; } // Item INSERT public void create(java.lang.String itemcode, java.lang.String name, java.lang.Long id, java.lang.Long[] categories) throws Exception{ // 省略(引数チェック) dao.create(itemcode, name, id, categories); } // Item SELECT public java.util.List read(java.lang.String itemcode, java.lang.String name, java.lang.Long id, java.lang.Long[] categories) throws Exception{ return toValueObjects(dao.read( itemcode, name, id, categories)); }
<bean id="ItemManageableDao" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"><ref local="ItemManageableDaoBase"/> </property> <property name="proxyInterfaces"> <value>to.msn.wings.codezine.item.crud.ItemManageableDao </value> </property> <!-- インターセプター --> <property name="interceptorNames"> <list> <value>hibernateInterceptor</value> </list> </property> </bean> <bean id="ItemManageableDaoBase" class="to.msn.wings.codezine.item.crud.ItemManageableDaoBase"> <property name="sessionFactory"><ref bean="sessionFactory"/> </property> <!-- Item DAO参照 --> <property name="dao"><ref bean="itemDao"/></property> <!-- Category DAO参照 --> <property name="categoriesDao"><ref bean="categoryDao"/> </property> </bean>
to.msn.wings.codezine.item.ItemDaoImpl
クラスがDAOの実装クラスになります。<bean id="itemDao" class="org.springframework.aop.framework.ProxyFactoryBean"> <!-- DAO実装への参照 --> <property name="target"><ref bean="itemDaoTarget"/> </property> <!-- インターセプト対象のインターフェース --> <property name="proxyInterfaces"> <value>to.msn.wings.codezine.item.ItemDao</value> </property> <!-- インターセプター --> <property name="interceptorNames"> <list> <value>hibernateInterceptor</value> </list> </property> </bean> <!-- Item DAOの実装 --> <bean id="itemDaoTarget" class="to.msn.wings.codezine.item.ItemDaoImpl"> <property name="sessionFactory"> <ref local="sessionFactory"/> </property> </bean>
HibernateDaoSupport
クラスを継承しています。データを操作しているソースの抜粋を以下の表に示します。データ操作 | ソース |
データ抽出時(SELECT) | this.getHibernateTemplate().find("from to.msn.wings.codezine.item.Item as entity where entity.id = ?", id); |
新規登録時(INSERT) | Object identifier = this.getHibernateTemplate().save(item); |
更新時(UPDATE) | this.getHibernateTemplate().update(item); |
削除時(DELETE) | this.getHibernateTemplate().delete(item); |
public abstract class ItemDaoBase extends org.springframework.orm.hibernate3.support. HibernateDaoSupport implements to.msn.wings.codezine.item.ItemDao{ // SELECT public Object load(final int transform, final java.lang.Long id){ // 省略(引数チェック ) final java.util.List list = this.getHibernateTemplate().find( "from to.msn.wings.codezine.item.Item as entity where entity.id = ?", id); final Object entity = list != null && !list.isEmpty() ? list.iterator().next() : null; return transformEntity( transform, (to.msn.wings.codezine.item.Item)entity); } // INSERT public Object create(final int transform, final to.msn.wings.codezine.item.Item item){ // 省略(引数チェック) Object identifier = this.getHibernateTemplate().save(item); item.setId((java.lang.Long)identifier); return this.transformEntity(transform, item); } // UPDATE,DELETE省略
プレゼンテーション層
プレゼンテーション層は、Strutsによって提供されるDispatchAction
、ValidatorForm
を継承したサブクラス、JSPクラス、Struts設定ファイルなどが自動生成されます。
コントローラクラスは、Strutsが提供しているDispatchAction
を継承しています。各クラスにはcreate
、read
、update
、delete
などデータ操作を実行するためのメソッドが実装されます。以下がItem用のコントローラクラスの抜粋です。
public final class ManageItem extends DispatchAction { public ActionForward create( ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception{ final to.msn.wings.codezine.item.crud.ItemForm form = (to.msn.wings.codezine.item.crud.ItemForm)actionForm; to.msn.wings.codezine.ManageableServiceLocator.instance(). getItemManageableService().create( StringUtils.isBlank(request.getParameter("itemcode"))) ? null : form.getItemcode() , (StringUtils.isBlank(request.getParameter("name"))) ? null : form.getName() , (StringUtils.isBlank(request.getParameter("id"))) ? null : form.getId() , (StringUtils.isBlank(request.getParameter("categories")))? null : form.getCategories() ); return preload(mapping, actionForm, request, response); } public ActionForward read( ActionMapping mapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception{ final to.msn.wings.codezine.item.crud.ItemForm form = (to.msn.wings.codezine.item.crud.ItemForm)actionForm; final java.util.List list = to.msn.wings.codezine.ManageableServiceLocator.instance(). getItemManageableService().read( (StringUtils.isBlank(request.getParameter("itemcode"))) ? null : form.getItemcode() , (StringUtils.isBlank(request.getParameter("name"))) ? null : form.getName() , (StringUtils.isBlank(request.getParameter("id"))) ? null : form.getId() , (StringUtils.isBlank(request.getParameter("categories")))? null : form.getCategories() ); form.setManageableList(list); // 省略 return mapping.getInputForward(); } }
データベース
AndroMDAではPIMモデルからスキーマ作成用のSQL文を生成します。本稿でAndorMDAが自動生成したSQL文では、「CATEGORIES2ITEMS」という関連テーブルが作成され、「ITEM」「CATEGORY」の両テーブルにIDフィールドが主キーとして作成されています。また、「ITEM」と「CATEGORIES2ITEMS」、そして「CATEGORY」と「CATEGORIES2ITEMS」の間に外部キー制約も作成されています。
create table ITEM ( ID BIGINT generated by default as identity (start with 1), ITEMCODE VARCHAR(256) not null, NAME VARCHAR(256) not null, primary key (ID), unique (ITEMCODE) ); create table CATEGORY ( ID BIGINT generated by default as identity (start with 1), CATEGORYCODE VARCHAR(256) not null, NAME VARCHAR(256) not null, primary key (ID), unique (CATEGORYCODE) ); create table CATEGORIES2ITEMS ( CATEGORIES_FK BIGINT not null, ITEMS_FK BIGINT not null, primary key (CATEGORIES_FK, ITEMS_FK) ); alter table CATEGORIES2ITEMS add constraint CATEGORY_ITEMS_FKC foreign key (ITEMS_FK) references ITEM; alter table CATEGORIES2ITEMS add constraint ITEM_CATEGORIES_FKC foreign key (CATEGORIES_FK) references CATEGORY;
データベースモデルへの変換時に適用されるルール
今回のモデルで適用された変換ルールを以下に示します。
- データベースモデルの各テーブルに主キーの連番IDフィールドを付加する。
- 多対多のモデルは、各モデルそれぞれにマッピングするテーブルと関係テーブルの3つを作成。
これらは、システム開発のテーブル設計時に適用される一般的なルールなので、AndroMDAは自動的に変換を行っています。
まとめ
本稿でみたようにPIMのモデルはシンプルでも、Javaの実装に依存したモデル(PSM)になると、モデルは数多くのクラスで構成されるようになります。これらのモデルを正確に作成するには多くの手間が必要となりミスも多くなります。
AndroMDAのようなMDA対応ツールでモデルやソースを自動生成することにより開発の多くの手間は削減され、さらには品質の向上も期待できます。いきなり全てを自動生成する事は難しいですが、MDAのコンセプトを適用可能な部分から実践して行くことにより、MDA実現に一歩一歩近づいていくのではないでしょうか。
参考資料
- AndroMDA
- 『独習オブジェクト指向開発』 古川正寿 著、翔泳社、2004年10月
- 『MDAのエッセンス』 Stephen J. Mellor・Kendall Scott・Axel Uhl・Dirk Weise 著、株式会社テクノロジックアート 訳、二上貴夫・長瀬嘉秀 監訳、翔泳社、2004年12月
- 『MDA導入ガイド』 Anneke Kleppe・Jos Warmer・Wim Bast 著、株式会社テクノロジックアート 訳、長瀬嘉秀 監修、インプレス、2003年12月
- 『Spring入門』 長谷川裕一・伊藤清人・岩永寿来・大野渉 著、技術評論社、2005年5月
- 『Spring Framework』 河村嘉之・首藤智大・竹内祐介・吉尾真祐 著、日経BP社、2005年5月
- 『Hibernate』 James Elliott 著、佐藤直生 訳、オライリー・ジャパン、2004年12月