2008/01/24からのアクセス回数 12376
Agile Web Development with Railsの例題と同じ問題をSpringを使って実装を試みたときの メモです。
もう一つの目的は、Spring-MVCプラグインがどの程度実際の問題解決に役立つかを検証することです。
プラグインのインストールについては、Spring-MVCプラグイン機能追加2(Validation)を参照してください。
mavenを使ってプロジェクトを生成します。
とします。ecliseでプロジェクトを管理できるようにeclipseプラグインも起動します。
mvn archetype:create \ -DgroupId=example.cart \ -DartifactId=cart \ -DarchetypeArtifactId=spring-mvc-archetype \ -DarchetypeGroupId=jp.co.pwv.spring-mvc-archetype \ -DarchetypeVersion=1.1.1 cd cart mvn eclipse:eclipse -DdownloadSources=true
データベースは、HsqlDBのサーバを使用するため、src/main/webapp/WEB-INF/db.propertiesの内容を修正します。
db.url=jdbc:hsqldb:hsql://localhost
HsqlDBのインストールは、こちら を参照してください。
HsqlDBサーバの起動は、HSqlDBをインストールしたディレクトリの直下にあるdemoフォルダに移動して、
./runServer.sh
と入力してください。これでHsqlDBサーバ起動しています。
しかし、HSqlDBサーバにT_MEMBERのテーブルができていません。Cartサーブレットを起動すると自動的にT_MEMBERテーブルが作成されますので、jettyプラグインを使ってCartサーブレットを起動します。
mvn jetty:run
ブラウザーでhttp://localhost:8080/cart/ と入力してください。addリンクだけのページが表示された成功です。
CTRL-Cでjettyが終了します。
長くなったので別タイトルにしました。
最初にProductを管理するページを作成します。
さしあたり、管理機能として
最初にProductのドメインモデルを作成します。 ドメインモデルは、example.cart.domainパッケージ内に定義します。
eclipseで以下のように入力した後、getter/setterを自動生成してください。
package example.cart.domain;
public class Product {
private Integer id;
private String title;
private String description;
private String image_url;
}
GenMVCプラグインのscaffoldゴールを指定して、ProductのDao、 Controller、 View、データベーステーブル を自動生成します。
その前に、GenMVCプラグインは、Productのクラスファイルを見に行くので、mavenのpackageを実行します。
mvn package mvn GenMVC:scaffold
このコマンドで、
[INFO] ------------------------------------------------------------------------ [INFO] Building Unnamed - example.cart:cart:war:1.0-SNAPSHOT [INFO] task-segment: [GenMVC:scaffold] [INFO] ------------------------------------------------------------------------ [INFO] [GenMVC:scaffold] [INFO] pkgName:example.cart [INFO] runtime.classpath:/Users/take/Documents/workspace/cart/target/classes [INFO] cls: example.cart.domain.Member [INFO] template fullpath:velocity/IDao.vm [INFO] template fullpath:velocity/Dao.vm [INFO] template fullpath:velocity/edit_stub.vm [INFO] template fullpath:velocity/list_stub.vm [INFO] template fullpath:velocity/hbm.vm [INFO] cls: example.cart.domain.Product [INFO] template fullpath:velocity/IDao.vm [INFO] template fullpath:velocity/Dao.vm [INFO] template fullpath:velocity/Manager.vm [INFO] template fullpath:velocity/EditController.vm [INFO] template fullpath:velocity/OpsController.vm [INFO] template fullpath:velocity/edit.vm [INFO] template fullpath:velocity/edit_stub.vm [INFO] template fullpath:velocity/list.vm [INFO] template fullpath:velocity/list_stub.vm [INFO] template fullpath:velocity/hbm.vm [INFO] template fullpath:velocity/servlet-stub.vm [INFO] template fullpath:velocity/sql.vm [INFO] template fullpath:velocity/applicationContext.vm [INFO] template fullpath:velocity/form-messages.vm [INFO] template fullpath:velocity/validation.vm
と出力され、必要なファイルがすべて生成されます。
再度、jettyプラグインを実行して、
mvn jetty:run
ブラウザーでhttp://localhost:8080/cart/productops/list.htmと入力してください。
以下のような画面が表示されますので、
これだけで、Productのリスト表示、編集の画面が生成されます。
| id | description | image_url | title |
| 1 | グンタースブルム村の畑はフランスのシャブリ地区に似た石灰分の多い土壌となっています。ワイングートドクター・シュネルはこの優れた土壌でぶどうの皮に付いた天然酵母だけを使いすべて木樽発酵という古典的な方法でワインを造っています。このワインは手入れの良く行き届いた単一畑で有機農法栽培されたヴァイサーブルグンダーぶどうから誕生した豊かでおだやかなやさしい感じの白ワインです。 | /images/Dr.Schnell_Gunt_Weiss_QbAB.jpg | Guntersblumer Weisserburgunder QbA |
| 2 | このワインは手入れの良く行き届いたシュタイグテラッセ単一畑で有機農法栽培されたシルヴァーナーぶどうから誕生した豊かでおだやかなやさしい感じの白ワインです。 | /images/Dr.Schnell_Steig_Silver1L_QbAL.jpg | Guntersbulmer Steigtrasse Silvaner |
| 3 | このワインは手入れの良く行き届いたグンタースブルム村のフォゲルスゲルテン(鳥の園)単一畑で有機農法栽培されたリースリングぶどうから誕生した豊かでおだやかなやさしい感じの白ワインです。 | /images/VogelRirSpt-L.jpg | Guntersbulmer Vogelsgaerten Riesling Spaetlese |
と入力したのが、以下の表示例です
scaffoldの後にProductに属性を追加したくなることはよくあります。
Product の属性を変更したときの手順は以下の通りです。
通常は、これで十分ですが、以下のファイルを修正した場合にはバックアップを取ってください。
今回は、自動生成されたファイルを全く変更していないので、テーブルの削除だけを行います。
開発の途中ではデータベースのテーブルを変更したり、値を参照します。このような用途に便利なのが Ecl,ipseのプラグインDbEditです。 DbEditのインストール方法はここ を参照してください。
DbEditのTableタグを開くと以下のようにT_MEMBERとT_PRODUCTの2つのテーブルが作られています。
GenMVCプラグインでは、クラス名の前にT_を付けたテーブルが作成されます。 T_PRODUCTを削除するには、 T_PRODUCTで右マウスクリックから削除を選択してください。
Productに価格(price)を追加します。
以下のように属性priceを追加し、getter/setterを自動生成するだけです。
private Double price;
日本では価格に小数点はないのですが、ここでは例としてDouble型を使いました。
それでは、先ほどと同様にGenMVCプラグインを起動します。
mvn package mvn GenMVC:scaffold
GenMVCプラグインが生成する画面は、属性の出力順がProductクラスの定義順に並んでいないので、実際には手で修正する必要があります。
ProductのVelocityテンプレートは、main/webapp/WEB-INF/velocity/productops/以下にあります。
が一覧を表示するテンプレートです。
list.vmを見ると
parse ( "productops/list_stub.vm" )
だけです。 これは、GenMVCプラグインがlist.vmを変更しないようするためにlist_stub.vmをインクルードする 2段階で処理しています。
従ってユーザvelocityテンプレートを変更する場合には、list_stub.vmをlist.vmにコピーして編集します。 以下に順序を入れ替えたlist.vmを示します。
<html>
<head>
<title>Product</title>
</head>
<body>
<h1>Listing product</h1>
<table>
<tr>
<td>id</td>
<td>title</td>
<td>description</td>
<td>image_url</td>
<td>price</td>
</tr>
#foreach (${product} in ${productList})
<tr>
<td>${product.id}</td>
<td>${product.title}</td>
<td>${product.description}</td>
<td>${product.image_url}</td>
<td>${product.price}</td>
#set( $editLink = "/editproduct.htm?id=${product.id}" )
<td><a href="#springUrl(${editLink})">[edit]</a></td>
#set( $deleteLink = "/productops/delete.htm?id=${product.id}" )
<td><a href="#springUrl(${deleteLink})">[delete]</a></td>
</tr>
#end
</table>
<a href='#springUrl("/editproduct.htm")'>add</a>
</body>
</html>
同様に編集画面editProduct.vmも順序を変え、Descriptionをtextareaに変えてます。
<html>
<head>
<title>Products</title>
</head>
<body>
Edit Product
<form method="post" action="#springUrl("/editproduct.htm")">
#springFormHiddenInput( "product.id" "" )
<table>
<tr>
<td>title:</td>
<td>#springFormInput( "product.title" "size='35'" )</td>
<td>
#springBind("product.title")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>description:</td>
<td>#springFormTextarea( "product.description" "rows='4' cols='40'" )</td>
<td>
#springBind("product.description")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>image_url:</td>
<td>#springFormInput( "product.image_url" "size='35'" )</td>
<td>
#springBind("product.image_url")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>price:</td>
<td>#springFormInput( "product.price" "size='10'" )</td>
<td>
#springBind("product.price")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
</html>
</body>
現在の入力フォームは、各フィールドが必須だけのチェックしかしていません。 priceに文字を入力して、Save Chageボタンを押すと
が出力されます。
これでは、エラーの原因が分かりづらいので、validation.xmlを修正してpriceをdoubleとしてふさわしい値になるようにしようと、
<field property="price" depends="required"> <arg0 key="product.price" /> </field>
を
<field property="price" depends="required,double"> <arg0 key="product.price" /> </field>
としたが、ダメでした。
原因は、Validationが行われる前に、Productの値がHTTPのパラメータからセットされるためです。
ソースをトレースした結果、bindAndValidationを使用する場合、最初にHTTPリクエストから値をセットするcommandオブジェクトへのbind操作が先行します、ここでStringからDoubleへの変換に失敗するため、その後のValidationのエラーチェックでは値がセットされていないので、requiredのエラーが追加されますが、表示されません。
対応策としては、ProductionのPriceをDoubleからStringに変えるという方法しかありません。 これでは、GenMVCプラグインを起動すると間違ったCreate table文が生成されてしまいます。
そこで、domainクラスに対応するcommandクラスをGenMVCプラグインで生成し、その属性をすべてString型としました。
更に、EditProductControllerでcomanndオブジェクトとdomainオブジェクトの値を変換するメソッドcommandToDomain, domainToCommandを追加しました。こんなに簡単にデータの設定ができるのは、CustomDataBinderの威力です。
protected void bind(Object target, Object source) throws Exception {
CustomDataBinder binder = new CustomDataBinder(target, source);
this.prepareBinder(binder);
binder.bind();
}
protected Object commandToDomain(Object source) throws Exception {
Order object = manager.findById(((ProductCommand)source).getId());
bind(object, source);
return (object);
}
protected Object domainToCommand(Object source) throws Exception {
Object object = new ProductCommand();
bind(object, source);
return (object);
}
これでようやく、priceのValidationが正常に行えるようになりました。
最後にスタイルシート使って衣装替えをします。 スタイルシートについては、詳しくないのでAgile Web Development with Railsの例題のスタイルを借用します。
スタイルシートを追加したlist.vmは、以下の通りです。
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
<title>Product</title>
</head>
<body>
<div id="product-list">
<h1>Listing product</h1>
<table cellpadding="5" cellspacing="0">
#foreach (${product} in ${productList})
#if($velocityCount %2 == 1)
<tr valign="top" class="list-line-odd">
#else
<tr valign="top" class="list-line-even">
#end
<td>
<img class="list-image" src="#springUrl(${product.image_url})" />
</td>
<td width="60%">
<span class="list-title">${product.title}</span><br/>
#if ($product.description.length() > 80)
#set($size = 80)
#else
#set($size = $product.description.length())
#end
$product.description.substring(0,$size)
</td>
<td class="list-action">
#set( $editLink = "/editproduct.htm?id=${product.id}" )
<a href="#springUrl(${editLink})">[edit]</a><br/>
#set( $deleteLink = "/productops/delete.htm?id=${product.id}" )
<a href="#springUrl(${deleteLink})">[delete]</a>
</td>
</tr>
#end
</table>
<a href='#springUrl("/editproduct.htm")'>New product</a>
</div>
</body>
</html>
ここで、スタイルシートの指定を
<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
でしているところと、説明文を一部カットするためにStringのsubstringメソッドをテンプレートから呼び出しているところに注意してください。このようにVelocityを使うとテンプレートからjavaのメソッド呼び出しができます。
スタイルシートは、src/main/webapp/cssディレクトリに入れてください。
画像ファイルは、src/main/webapp/imagesディレクトリに入れてください。
データベースは、HsqlDBのdata以下のtest.scriptを以下のファイルと入れ替えてください。
スタイルシートの出力結果は、以下の通りです。
次にカタログ表示ページを作成します。Agile Web Development with Railsではカタログの表示にstoreという新しいコントローラを作成していますが、ここではProductOpsControllerを借用します。 その理由は、ProductOpsControllerがProductを扱うコントローラであり、MultiActionControllerのサブクラスなのでメソッド名と同じテンプレートを作成するだけでカタログページが作れるとメリットを示すためです。
手順は以下の通りです。
public ModelAndView catalog(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView().addObject(manager.findAll());
}
catalog.vmは以下の通りです。
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
<title>Store</title>
</head>
<body id="store">
<h1>Your wine catalog</h1>
#foreach (${product} in ${productList})
<div class="entry">
<img src="#springUrl(${product.image_url})" />
<h3>${product.title}</h3>
$product.description <br>
<span class="price">$numberTool.format("##0", $product.price)</span>
</div>
#end
</body>
</html>
ブラウザーでhttp://localhost:8080/cart/productops/catalog.htmと入力すると 以下のようなカタログページが表示されます。
最後にカートへの追加ボタンを入れます。
価格の後に次の行を挿入します。
<form method="post" action="#springUrl("/cartops/add.htm")">
#springFormHiddenInput( "product.id" "" )
<input type="submit" value="Add to Cart"/>
</form>
画面では次のように表示されます。
カートの処理に進む前に注文項目を作成します。
ここでのポイントはカートの注文項目がそのまま注文にリンクされるようにすることです。
それでは、注文項目のdomainクラスを作りましょう。
package example.cart.domain;
public class LineItem {
private Integer id;
private Integer quantity = new Integer(1);
private Integer productId;
private Product product;
private Integer orderId;
public void addQuantity(Integer quantity) {
this.quantity = new Integer(this.quantity.intValue() + quantity.intValue());
}
public Double getPrice() {
return (new Double(quantity.intValue()*product.getPrice().doubleValue()));
}
}
として、getter/setterを自動生成してください。
この後は、いつものようにGenMVC:scaffoldを実行します。
mvn package mvn GenMVC:scaffold
カートの処理を行う、CartServiceとカートに対する要求を処理するCartOpsControllerを追加します。
最初にCartServiceを追加します。CartServiceでは
を行います。
package example.cart.service;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import example.cart.domain.LineItem;
public class CartService {
private Map lineItemMap = new TreeMap();
public void addLineItem(LineItem item) {
Integer key = item.getProductId();
LineItem lineItem = (LineItem)lineItemMap.get(key);
if (lineItem == null)
lineItemMap.put(key, item);
else
lineItem.addQuantity(item.getQuantity());
}
public Double getTotal() {
double total = 0;
Iterator itr = lineItemMap.values().iterator();
while (itr.hasNext()) {
LineItem lineItem = (LineItem)itr.next();
total += lineItem.getPrice().doubleValue();
}
return (new Double(total));
}
public Map getLineItemMap() {
return lineItemMap;
}
}
ここで、session-def.xmlが正しくweb.xmlに追加されていることを確認してください。
<param-value> /WEB-INF/custom-editor.xml /WEB-INF/db-def.xml /WEB-INF/session-def.xml /WEB-INF/applicationContext.xml </param-value>
ProductOpsContollerをコピーしてCarOptsControllerを作成します。
private CartService cartService;
private ProductManager manager;
private void setupCartService(HttpServletRequest request) {
ApplicationContext co = WebApplicationContextUtils.
getRequiredWebApplicationContext( request.getSession().getServletContext());
cartService = (CartService)co.getBean("cartService");
}
listでは、
public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws Exception {
setupCartService(request);
return new ModelAndView().addObject("lineItemList", cartService.getLineItemMap().values());
}
addでは、
public ModelAndView add(HttpServletRequest request, HttpServletResponse response) throws Exception {
Integer productId = new Integer(ServletRequestUtils.getRequiredIntParameter(request, "id"));
setupCartService(request);
Product product = manager.findById(productId);
LineItem item = new LineItem();
item.setProductId(productId);
item.setProduct(product);
cartService.addLineItem(item);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("lineItemList", cartService.getLineItemMap().values());
modelAndView.setViewName("cartops/list");
return modelAndView;
}
cartopsフォルダをsrc/main/webapp/WEB-INF/velocityに作成し、list.vmを追加します。
簡単な項目一覧をlist.vmで出力します。
<html> <head> <title>Cart</title> </head> <body> <h1>Your Wine Cart</h1> <ul> #foreach ($lineItem in $lineItemList) <li> $lineItem.quantity × $lineItem.product.title </li> #end </ul> </body> </html>
CartOpsControllerは、GenMVCプラグインの影響を受けないようにserver-def.xmlに定義します。
<bean id="cartOpsController" class="example.cart.web.CartOpsController"
parent="baseProductController"/>
jettyプラグインを起動し、
mvn jetty:run
ブラウザーからカタログ画面を表示(http://localhost:8080/cart/productops/catalog.htm)し、Add to Cartボタンを押すと以下のような画面に遷移します。
次に小計と合計の表示とEmpty cartボタンを追加し、ひとまずcartの完成としましょう。
modelAndView.addObject("total", cartService.getTotal());
public ModelAndView emptyCart(HttpServletRequest request, HttpServletResponse response) throws Exception {
setupCartService(request);
cartService.getLineItemMap().clear();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catalog.htm");
return modelAndView;
}
cartops/list.vmにempty cartボタンを追加します。
<form method="post" action="#springUrl("/cartops/emptyCart.htm")">
<input type="submit" value="Empty cart"/>
</form>
最終的な画面は、以下のようになります。
最後にチェックアウトの処理を追加します。
注文書には、以下の項目を入れます。
domainクラスとしてOrderを作成します。
package example.cart.domain;
import java.util.List;
public class Order {
private Integer id;
private String name;
private String address;
private String email;
private String paytype;
private List lineItemList;
}
を入力して、getter/setterを自動生成します。
そして、GenMVC:scaffoldを実行します。
mvn package mvn GenMVC:scaffold
すべてのテーブルが出そろったので、テーブルの関連づけをします。
詳しくは、モデル中心プログラミング を参照してください。
<class name="example.cart.domain.LineItem" table="T_LINEITEM"> <id name="id"> <generator class="increment"/> </id> <property name="orderId"/> <property name="productId" insert="false" update="false"/> <property name="quantity"/> <many-to-one name="product" column="productId" cascade="save-update" class="example.cart.domain.Product"/> </class>
<bag name="lineItemList" cascade="all" table="T_LINEITEM"> <key column="orderId" foreign-key="ID"/> <one-to-many class="example.cart.domain.LineItem"/> </bag>
最後に、src/main/webapp/WEB-INF/hbm-dir/Order.hbm.xml, LineItem-hbm.xmlのバックアップを取ってください。
OrderOpsControllerにcheckoutメソッドを追加します。 それと同時にcartService属性を追加して、session scopeのcartServiceから注文項目を取り出します。
private CartService cartService;
private void setupCartService(HttpServletRequest request) {
ApplicationContext co = WebApplicationContextUtils.
getRequiredWebApplicationContext( request.getSession().getServletContext());
cartService = (CartService)co.getBean("cartService");
}
public ModelAndView checkout(HttpServletRequest request, HttpServletResponse response) throws Exception {
setupCartService(request);
Order order = new Order();
order.setLineItemList(new ArrayList(cartService.getLineItemMap().values()));
manager.saveOrUpdate(order);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/editorder.htm?id=" + order.getId());
return modelAndView;
}
次にvelocity/cartopgs/list.vmにcheckoutボタンを追加します。
<form method="post" action="#springUrl("/orderops/checkout.htm")">
<input type="submit" value="Checkout"/>
</form>
これだけの変更でカートの項目一覧と注文がデータベースにセットされてしまいます。
注文が空の状態から始めます。 ブラウザーからhttp://localhost:8080/cart/orderops/list.htmと入力します。
ブラウザーからhttp://localhost:8080/cart/productops/catalog.htmと入力し、カートに入れます。
EditOrderControllerは、戻り場所をコンフィグファイルで固定となるため、EditOrderControllerのサブクラスとして定義InputOrderControllerを作成することにします。
package example.cart.web;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.validation.Errors;
public class InputOrderController extends EditOrderController {
private Map payTypeOptions;
protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception {
Map refData = new HashMap();
refData.put("payTypeOptions", payTypeOptions);
return refData;
}
public void setPayTypeOptions(Map payTypeOptions) {
this.payTypeOptions = payTypeOptions;
}
}
payTypeのオプションはマップで指定するため、Velocityの中では定義できません。そこで、Springのコンフィグから取得し、referenceDataメソッドでマップにセットしVelocityに渡すことにしました。
inputOrder.vmのVelocityテンプレートを以下の様に修正します。
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
<title>Input Order</title>
</head>
<body>
#springBind("order.name")
<font color="red">${status.errorMessage}</font><br>
#springBind("order.address")
<font color="red">${status.errorMessage}</font><br>
#springBind("order.email")
<font color="red">${status.errorMessage}</font><br>
<div class="cart-form">
<fieldset>
<legend>Please Enter Your Details</legend>
<form method="post" action="#springUrl("/inputorder.htm")">
#springFormHiddenInput( "order.id" "" )
<p>
<label>Name:</label>
#springFormInput( "order.name" "size='40'" )
</p>
<p>
<label>Address:</label>
#springFormTextarea( "order.address" "rows='3' cols='40'" )
</p>
<p>
<label>E-Mail:</label>
#springFormInput( "order.email" "size='40'" )
</p>
<p>
<label>Pay with:</label>
#springFormSingleSelect( "order.paytype" $payTypeOptions "" )
</p>
<input type="submit" value="Place Order" class="submit"/>
</form>
</fieldset>
</div>
</body>
</html>
これで、InputOrderControllerが完成しましたので、checkoutメソッドからの遷移先を inputOrder.htmに変更します。
modelAndView.setViewName("redirect:/inputorder.htm?id=" + order.getId());
Springのコンフィグファイルは、server-def.xmlに以下の項目を追加します。
<bean id="inputOrderController" class="example.cart.web.InputOrderController"
parent="orderController">
<property name="formView" value="inputOrder"/>
<property name="successView" value="redirect:orderops/list.htm"/>
<property name="payTypeOptions">
<map>
<entry key="check" value="Check"/>
<entry key="cc" value="Credit card"/>
<entry key="po" value="Purchase order"/>
</map>
</property>
</bean>
このようにparent属性でEditOrderControllerを指定すると、差分だけを記述するだけですみます。 payTypeOptionsにpayTypeOptionsの選択肢をセットします。
注文入力画面は以下のようになります。
最後に、注文確認と注文確定、キャンセル処理を組み込みます。
OrderOpsControllerに以下のようなcheckorderメソッドを追加します。
public ModelAndView checkorder(HttpServletRequest request, HttpServletResponse response) throws Exception {
setupCartService(request);
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Order order = manager.findById(new Integer(id));
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject(order);
modelAndView.addObject("total", cartService.getTotal());
return modelAndView;
}
Velocityテンプレートのvelocity/orderops/checkorder.vmは以下の通りです。
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
<title>Cart</title>
</head>
<body>
<div class="cart-title">Bill to:</div>
<table>
<tr>
<td>Name:</td>
<td>$order.name</td>
</tr>
<tr>
<td>Address:</td>
<td>$order.address</td>
</tr>
<tr>
<td>E-Mail:</td>
<td>$order.email</td>
</tr>
<tr>
<td>Paywith:</td>
<td>$payTypeOption.${order.paytype}</td>
</tr>
</table>
<div class="cart-title">Your order items:</div>
<table>
#foreach ($lineItem in $order.lineItemList)
<tr>
<td>$lineItem.quantity ×</td>
<td>$lineItem.product.title</td>
<td class="item-price">$numberTool.format("##0", $lineItem.price)</td>
</tr>
#end
<tr class="total-line">
<td colspan="2">Total</td>
<td class="totale-cell">$numberTool.format("##0", $total)</td>
</tr>
</table>
<div>
<form method="post" action="#springUrl("/orderops/confirm.htm")">
#springFormHiddenInput( "order.id" "" )
<input type="submit" value="Confirm order"/>
</form>
<form method="post" action="#springUrl("/orderops/cancel.htm")">
#springFormHiddenInput( "order.id" "" )
<input type="submit" value="Cancel order"/>
</form>
</div>
</body>
</html>
InputOrderからの遷移画面をcheckorderにするために、InputOrderControllerのdoSubmitActionを以下の様に追加します。
protected void doSubmitAction(Object object) throws Exception {
OrderCommand command = (OrderCommand)object;
this.setSuccessView("redirect:orderops/checkorder.htm?id=" + command.getId());
super.doSubmitAction(object);
}
注文確認画面は以下の通りです。
OrderOpsControllerに以下のようなconfirmメソッドを追加します。
public ModelAndView confirm(HttpServletRequest request, HttpServletResponse response) throws Exception {
setupCartService(request);
cartService.getLineItemMap().clear();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catalog.htm");
return modelAndView;
}
注文確定の主な処理は、カートを空にし、カタログページに飛ぶことです。
OrderOpsControllerに以下のようなcancelメソッドを追加します。
public ModelAndView cancel(HttpServletRequest request, HttpServletResponse response) throws Exception {
setupCartService(request);
cartService.getLineItemMap().clear();
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Order order = manager.findById(new Integer(id));
manager.delete(order);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catalog.htm");
return modelAndView;
}
hibernateのlazy initialize機能ではhibernateのsessionを使ってビューを描画したとき、many-to-one, one-to-manyで連結されている属性の値を必要になったときに取得しようとするが、そのときにsessionが閉じているので、lazily initializeエラーが発生します。
対応としては、
がありますが、lazy="false"とするとパフォーマンスが落ちるので、OpenSessionInViewFilterを指定します。
web.xmlに以下の行を追加します。
<filter> <filter-name>hibernateFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>flushMode</param-name> <param-value>AUTO</param-value> </init-param> </filter> <filter-mapping> <filter-name>hibernateFilter</filter-name> <url-pattern>*.htm</url-pattern> </filter-mapping>
cart問題の全ソースを以下にアップします。ダウンロードして試してみてください。
このzipファイルからcartを動作させるには、
これで、ブラウザーからhttp://localhost:8080/cart/productops/catalog.htmを表示します。
皆様のご意見、ご希望をお待ちしております。