J2EEレガシーアプリケーションのJavaEEアプリケーションへのマイグレーション(1)
何回かに分けてJ2EEレガシーアプリケーションのJavaEEアプリケーションへのマイグレーションについて実際のコードを見ながら解説したいと思います。
変更前のサンプルソースはこちら
https://github.com/megascus/oi-webapp-sample/tree/initial
こちらのソースは、Tomcat上で動く、ビューがServlet2.5+JSP、O/RマッパーとしてHibernateを直接使用するという、大体2005年ぐらいに作られたシステムのイメージになっています。
また、いくつかの点にてきちんと設計されているとは言えず、MVCに沿って作られたことになっていますが、きちんとViewとModelが分離できていません。
それ以外にも問題をいくつか抱えています。
これをGlassFish4.0上でのJavaEE7仕様で作り直したいと思います。
古いシステムを新しい仕様で書き直すとどうなるかを見ていただけると。
今回はweb.xmlの書き換えとHibernateをJPAに置き換える途中まで。
web.xmlの書き換え
JavaEE7ではServlet仕様が3.1になっているので最初に書き換えます。
Servlet2.5から大きく変わった点としては、Servletをアノテーションで定義できるようになったことと、web.xmlの存在自体を省略できるようになったことです。
今回はServletとwelcome-filesがweb.xmlで定義されているので、Servletはアノテーションでの定義、welcome-filesはそのままweb.xmlに残す形としています。
実ServletとURLのマッピングが一つのファイルで直接管理できるようになるので、ある程度の規模までであれば開発が楽になるはずです。
修正前
https://github.com/megascus/oi-webapp-sample/blob/initial/WebContent/WEB-INF/web.xml
https://github.com/megascus/oi-webapp-sample/blob/initial/src/com/oisix/sample/base/ControllerServlet.java
修正後
https://github.com/megascus/oi-webapp-sample/blob/20140430/WebContent/WEB-INF/web.xml#L3-L6
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/base/ControllerServlet.java?L16#L16-L19
web.xmlではweb-appタグの内容が書きかわっていることに注意をしてください。
これでweb.xmlの書き換えが完了しました。
HibernateをJPAに置き換える
さて、次はHibernateをJPAに置き換えます。
JPAはHibernateをもとに策定された標準的なJavaEEのO/Rマッパーの仕様です。
Hibernateを直接使うよりも多くの人間が仕様を知っていることが期待されるため、今現在であればHibernate等のプロバイダを直接使用するよりもJPAを通して使用することが推奨されます。
JPAは仕様であり、実装が必要となります。その実装の一つとしてHibernateを利用することもできますが、今回はGlassFishに同梱されているEclipseLinkを使用します。
Entityの書き換え
まずはEntityを書き換えます。
このアプリではMstCusutomerというEntityが一つだけ用意されています。
https://github.com/megascus/oi-webapp-sample/blob/initial/src/com/oisix/sample/model/MstCustomer.java
単一のテーブルにアクセスするためだけの単純なEntityです。
EntityについてはHibernateとJPA仕様で同じアノテーションを使用して作成することになっているため、実はこのEntityのままでもJPAで使用することができます。
ただし、このまま使用すると以下のような問題があります。
・JPAの仕様上ではアクセサとフィールドのどちらかにしかアノテーションをつけることができない(両方つけても一応動作はする模様だが、仕様範囲外)
・主キーを画面から入力できるようになっている、また、文字列になっている
・Entityに複数のプロパティで合わさって意味をもつものが存在するが、それらがグルーピングされていない
・作成日付、更新日付等Entityのライフサイクルとして管理するべきものが業務ロジックで更新されている
これらについて修正をしていきます。
アクセサについているアノテーションは削除
アクセサについているアノテーションは削除してしまいます。
私はカプセル化の観点からフィールドにアノテーションをつけることを推奨していますが、お好みでフィールドのアノテーションの方を削除してもよいでしょう。
人工キーを作成し、複数のエンティティで使用される項目はスーパークラスにまとめる
JPAを実装したO/Rマッパーでは人工キーをつけることが推奨されます。
理由としては、JPAではいくつかの範囲でEntityのキャッシュを行います。*1そちらについて主キーの同一性によりキャッシュされているかどうかが判断されるため、主キーのequals、hashCodeの実装は軽いほうがよいです。*2JPAの仕様では複合主キーによるマッピングも行えますが、その場合は性能上ビハインドを抱える可能性がある事を覚えておいてください。
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/model/EntityBase.java#L28-L30
また、スーパークラスにまとめる場合は、スーパークラスに@MappedSuperclassをつける必要があるので注意してください。
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/model/EntityBase.java#L25
Entityの新規作成/更新時に行うべき処理をEntityのライフサイクルに含める
新規作成時、更新時に業務ロジックとは関係なく必ず更新しなければならない処理がありました。
https://github.com/megascus/oi-webapp-sample/blob/initial/src/com/oisix/sample/dao/MstCustomerDao.java#L34-L38
個々の処理でいちいち毎回行わなければいけない処理を記述するのは面倒くさいので、Entityのライフサイクルに含めてしまいます。
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/model/EntityBase.java#L80-L94
これでいちいち更新処理を書かなくてもよくなりました。*3
意味がある塊はEmbededクラスに
もともとのMstCustomer Entityではフィールドがたくさんあり、どのフィールドが関連するものなのかが良く分からなくなっていました。
https://github.com/megascus/oi-webapp-sample/blob/initial/src/com/oisix/sample/model/MstCustomer.java
巨大なクラスの作成はオブジェクト指向的ではないのでサクサクっと小さいクラスに分割してしまいます。
その場合に使うのが@Embeddableです。
このアノテーションを使用すると、クラスとしては複数になりますが、DBのテーブルにマッピングされる場合には組み込まれたEntityと同じテーブルにマッピングされます。
なので、テーブルジョインが苦手なMySQL等でもオブジェクト指向を崩さないまま、非正規化ができるという優れものです。
今回は住所と、電話番号を切り出してみました。
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/model/Address.java
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/model/TelephoneNumber.java
切り出された後は以下のようになります。
https://github.com/megascus/oi-webapp-sample/blob/20140430/src/com/oisix/sample/model/MstCustomer.java
*1:https://blogs.oracle.com/carolmcdonald/entry/jpa_caching
*2:正確には実装により影響がない場合もありますが、影響がない実装を見たことがない