ActiveObjectsを使ってみた(1)
はじめに
ここまでActiveObjectsについていろいろ調べてきました。その結果、どういう仕組みなのかはなんとなーくわかってきたのですが、使いやすいのかどうなのかはさっぱりわかりませんでした。そこで、AOを実際に使ってみて、良し悪しを確かめてみたいと思います。
題材
今回は題材はなんでもよかったのですが、DBを使うようなものを自力で思いつかなかったため、情報処理技術者試験の過去問題に出た内容を実装してみることにしました。
「平成17年のテクニカルエンジニア・データベースの午後Iの問2」のシステムを実装してみます。
過去問
システムの概要
この過去問題は通信販売のシステムに関する問題です。
システムとその周囲の動作を適当にまとめると、
- 顧客がFAX送付する注文書(および会員登録申込書)の内容を、販売会社社員がシステムに登録する。
- 登録された注文をもとに、システムが発送書を作成する。
- 販売会社社員は発送書の内容に基づき、商品を梱包して顧客に発送する。
といった感じだと思っています。(間違っているかも)
必要と思われるエンティティ
おそらく以下は必要でしょう。
- 顧客エンティティ
- 商品エンティティ(Polymorphic)
- 単品商品エンティティ
- パック商品エンティティ
- 注文エンティティ
- 注文明細エンティティ
- 発送エンティティ
- 発送明細エンティティ
過去問題の問題文の中に、「"単品商品"テーブルと"パック商品"テーブルは、"商品"テーブルとして一つにまとめた方がよい」という記述がありますが、ここではあえて分けて作ります。というのは、商品エンティティでポリモフィックを試したいからです。
【今回のメイン】単品商品のメンテナンス画面を作ってみる
まずはじめに、単品商品の一覧表示画面と単品商品の追加画面だけ作ってみました。以下は、実際の画面です。
エンティティのソースファイル
エンティティは二つだけ作成しました。
Product.java - 商品
package sample.entity; import java.util.Date; import net.java.ao.Entity; import net.java.ao.Polymorphic; import net.java.ao.Generator; import net.java.ao.Preload; import net.java.ao.schema.AutoIncrement; import net.java.ao.schema.Unique; import sample.generator.ProductNoGenerator; @Polymorphic // ポリモフィックなので、テーブルは作られません。 public interface Product extends Entity { // 商品番号 @Unique // 一意制約 @Generator(ProductNoGenerator.class) // 一意の商品番号を作成します。 int getProductNo(); // 販売開始日 void setSaleFrom( Date date ); Date getSaleFrom(); // 販売終了日 void setSaleTo( Date date ); Date getSaleTo(); // 単価 void setPrice( Long Price ); Long getPrice(); }
SingleProduct.java - 単品商品
package sample.entity; public interface SingleProduct extends Product { // ポリモフィックなProductを継承 // 商品名 void setName( String name ); String getName(); }
画面のソースファイル
上の画像のとおり、作成した画面は二つです。サーブレットとJSPがそれぞれ二つづつあります。
SingleProductListServlet.java - 単品商品一覧画面(サーブレット)
package sample.servlet; import java.sql.SQLException; import net.java.ao.EntityManager; import sample.entity.SingleProduct; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import java.io.PrintWriter; import java.io.IOException; public class SingleProductListServlet extends HttpServlet { public void doGet( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException { try { // EntityManagerを取得する。 EntityManager manager = new EntityManager( "jdbc:mysql://localhost/sample", "root", "root" ); // SingleProductを全部取得し、JSPに渡す。 req.setAttribute( "SingleProducts", manager.find( SingleProduct.class ) ); // JSPを表示する。 req.getRequestDispatcher( "../jsp/SingleProductList.jsp" ).forward( req, resp ); } catch ( SQLException e ) { // ※一覧の例外処理はきちんとしてない!! throw new ServletException( e ); } } }
SingleProductListServlet.java - 単品商品一覧画面(JSP)
<%@ page contentType="text/html; charset=Shift_JIS" %> <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-logic" prefix="logic" %> <html> <head> <title>単品商品一覧 - ActiveObjectsサンプル</title> </head> <body> <h3>単品商品一覧</h3> <table border="1"> <tr> <th>商品番号</th> <th>販売開始日</th> <th>販売終了日</th> <th>単価</th> <th>商品名</th> </tr> <logic:iterate id="sp" name="SingleProducts" scope="request"> <tr> <td><bean:write name="sp" property="productNo" format="00000000"/></td> <td><bean:write name="sp" property="saleFrom" format="yyyy/MM/dd"/></td> <td><bean:write name="sp" property="saleTo" format="yyyy/MM/dd"/></td> <td><bean:write name="sp" property="price" format="#,##0円"/></td> <td><bean:write name="sp" property="name"/></td> </tr> </logic:iterate> </table> </body> </html>
SingleProductAddServlet.java - 単品商品追加画面(サーブレット)
package sample.servlet; import java.sql.SQLException; import net.java.ao.EntityManager; import net.java.ao.Transaction; import sample.entity.SingleProduct; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import java.io.PrintWriter; import java.io.IOException; import java.text.DateFormat; import java.util.Date; import java.text.ParseException; public class SingleProductAddServlet extends HttpServlet { // GETリクエスト - 入力画面のみ表示する。 public void doGet( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException { req.getRequestDispatcher( "../jsp/SingleProductAdd.jsp" ).forward( req, resp ); } // POSTリクエスト - 登録処理をしてから、入力画面を表示する。 public void doPost( final HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException { req.setCharacterEncoding( "Shift-JIS" ); // EntityManagerを取得する。 EntityManager manager = new EntityManager( "jdbc:mysql://localhost/sample", "root", "root" ); try { // トランザクションを開始する。 new Transaction
SingleProductAdd.jsp - 単品商品追加画面(JSP)
<%@ page contentType="text/html; charset=Shift_JIS" %> <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-logic" prefix="logic" %> <html> <head> <title>単品商品追加 - ActiveObjectsサンプル</title> </head> <body> <h3>単品商品追加</h3> <logic:notEmpty name="Message"> <table border=1> <tr><th>メッセージ</th></tr> <tr><td><bean:write name="Message"/></td></tr> </table> <br> </logic:notEmpty> <table border="1"> <form method="POST" action="../servlet/sample.servlet.SingleProductAddServlet"> <tr> <th>販売開始日</th><td><input type="text" name="saleFrom" value="<logic:notEmpty name="SingleProduct"><bean:write name="SingleProduct" property="saleFrom" format="yyyy/MM/dd" ignore="true"/></logic:notEmpty>"></td> </tr> <tr> <th>販売終了日</th><td><input type="text" name="saleTo" value="<logic:notEmpty name="SingleProduct"><bean:write name="SingleProduct" property="saleTo" format="yyyy/MM/dd" ignore="true"/></logic:notEmpty>"></td> </tr> <tr> <th>単価</th><td><input type="text" name="price" value="<logic:notEmpty name="SingleProduct"><bean:write name="SingleProduct" property="price" format="###" ignore="true"/></logic:notEmpty>"></td> </tr> <tr> <th>商品名</th><td><input type="text" name="name" value="<logic:notEmpty name="SingleProduct"><bean:write name="SingleProduct" property="name"/></logic:notEmpty>"></td> </tr> </table> <br> <input type="submit" value="追加"> </form> </body> </html>
【重要】マイグレーション
この処理は、毎回コンパイルと同時に実行しています。そのため、実行時にはプログラム(エンティティ)とDBのテーブル構造は必ず一致しています。
Migration.java - マイグレーション用クラス
package sample; import java.sql.SQLException; import net.java.ao.EntityManager; import sample.entity.*; public class Migration { public static void main( String[] args ) { // EntityManagerを取得する。 EntityManager manager = new EntityManager( "jdbc:mysql://localhost/sample", "root", "root" ); try { // 商品と単品商品のエンティティから、テーブル構造を作成する。 // ※今後エンティティが増えたら、ここに追加する!!! manager.migrate( Product.class, SingleProduct.class ); } catch ( SQLException e ) { // ここのエラー処理はたぶんこのまま放置。ビルドのタイミングでながすだけだから、どーでもいい。 throw new RuntimeException( e ); } } }