Cart問題
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[FrontPage]]
#contents
2008/01/24からのアクセス回数 &counter;
* Cart問題 [#c949db9e]
Agile Web Development with Railsの例題と同じ問題をSpring...
メモです。
もう一つの目的は、Spring-MVCプラグインがどの程度実際の問...
プラグインのインストールについては、[[Spring-MVCプラグイ...
** プロジェクトの作成 [#y8873d2a]
mavenを使ってプロジェクトを生成します。
- groupIdは、example.cart
- artifactIdは、cart
とします。ecliseでプロジェクトを管理できるようにeclipseプ...
#pre{{
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/we...
#pre{{
db.url=jdbc:hsqldb:hsql://localhost
}}
HsqlDBのインストールは、[[こちら>http://www.pwv.co.jp/tak...
HsqlDBサーバの起動は、HSqlDBをインストールしたディレクト...
#pre{{
./runServer.sh
}}
と入力してください。これでHsqlDBサーバ起動しています。
しかし、HSqlDBサーバにT_MEMBERのテーブルができていません...
#pre{{
mvn jetty:run
}}
ブラウザーでhttp://localhost:8080/cart/
と入力してください。addリンクだけのページが表示された成功...
CTRL-Cでjettyが終了します。
* [[session scopeについて]] [#fddab324]
長くなったので別タイトルにしました。
* Product(製品の)の管理 [#r6b52203]
最初にProductを管理するページを作成します。
さしあたり、管理機能として
- 製品の一覧表示
- 製品の追加、編集、削除
ができるようにします。
** Productドメインモデルの作成 [#g8855bc8]
最初にProductのドメインモデルを作成します。
ドメインモデルは、example.cart.domainパッケージ内に定義し...
eclipseで以下のように入力した後、getter/setterを自動生成...
#pre{{
package example.cart.domain;
public class Product {
private Integer id;
private String title;
private String description;
private String image_url;
}
}}
#ref(Product.java);
** GenMVCプラグインの起動 [#x97dd396]
GenMVCプラグインのscaffoldゴールを指定して、ProductのDao...
を自動生成します。
その前に、GenMVCプラグインは、Productのクラスファイルを見...
#pre{{
mvn package
mvn GenMVC:scaffold
}}
このコマンドで、
#pre{{
[INFO] --------------------------------------------------...
[INFO] Building Unnamed - example.cart:cart:war:1.0-SNAPS...
[INFO] task-segment: [GenMVC:scaffold]
[INFO] --------------------------------------------------...
[INFO] [GenMVC:scaffold]
[INFO] pkgName:example.cart
[INFO] runtime.classpath:/Users/take/Documents/workspace/...
[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プラグインを実行して、
#pre{{
mvn jetty:run
}}
ブラウザーでhttp://localhost:8080/cart/productops/list.ht...
以下のような画面が表示されますので、
#ref(product_empty.jpg);
これだけで、Productのリスト表示、編集の画面が生成されます。
| id | description | image_url | title |
| 1 | グンタースブルム村の畑はフランスのシャブリ地区に似...
| 2 | このワインは手入れの良く行き届いたシュタイグテラッ...
| 3 | このワインは手入れの良く行き届いたグンタースブルム...
と入力したのが、以下の表示例です
#ref(product_list.jpg);
** 属性の追加 [#b2ed20d1]
scaffoldの後にProductに属性を追加したくなることはよくあり...
Product の属性を変更したときの手順は以下の通りです。
- テーブルの削除
- velocity/*_stub.vmファイルのバックアップ
- validation.xmlファイルのバックアップ
通常は、これで十分ですが、以下のファイルを修正した場合に...
- webapp/WEB-INF/hbm-dir/Product.hbm.xml
- main/resources/form-messages.properties
今回は、自動生成されたファイルを全く変更していないので、...
*** テーブルの削除 [#x9330700]
開発の途中ではデータベースのテーブルを変更したり、値を参...
Ecl,ipseのプラグインDbEditです。
DbEditのインストール方法は[[ここ>http://www.pwv.co.jp/tak...
を参照してください。
DbEditのTableタグを開くと以下のようにT_MEMBERとT_PRODUCT...
#ref(DbEdit.jpg);
GenMVCプラグインでは、クラス名の前にT_を付けたテーブルが...
T_PRODUCTを削除するには、 T_PRODUCTで右マウスクリックか...
- すでに沢山のデータが入っていたらどうすれば良いのか?
この答えは、テストケースのデバッグで紹介する予定です。
*** Productクラスの変更 [#j28b6d69]
Productに価格(price)を追加します。
以下のように属性priceを追加し、getter/setterを自動生成す...
#pre{{
private Double price;
}}
日本では価格に小数点はないのですが、ここでは例としてDoubl...
それでは、先ほどと同様にGenMVCプラグインを起動します。
#pre{{
mvn package
mvn GenMVC:scaffold
}}
** 画面(Velocityテンプレート)の変更 [#b82e17e4]
GenMVCプラグインが生成する画面は、属性の出力順がProductク...
ProductのVelocityテンプレートは、main/webapp/WEB-INF/velo...
- list_stub.vm
- list.vm
が一覧を表示するテンプレートです。
list.vmを見ると
#pre{{
parse ( "productops/list_stub.vm" )
}}
だけです。
これは、GenMVCプラグインがlist.vmを変更しないようするため...
2段階で処理しています。
従ってユーザvelocityテンプレートを変更する場合には、list_...
以下に順序を入れ替えたlist.vmを示します。
#pre{{
<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>...
#set( $deleteLink = "/productops/delete.htm?id=${produ...
<td><a href="#springUrl(${deleteLink})">[delete]...
</tr>
#end
</table>
<a href='#springUrl("/editproduct.htm")'>add</a>
</body>
</html>
}}
同様に編集画面editProduct.vmも順序を変え、Descriptionをte...
#pre{{
<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=...
<td>
#springBind("product.title")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>description:</td>
<td>#springFormTextarea( "product.descriptio...
<td>
#springBind("product.description")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>image_url:</td>
<td>#springFormInput( "product.image_url" "s...
<td>
#springBind("product.image_url")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>price:</td>
<td>#springFormInput( "product.price" "size=...
<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>
}}
** Validationの変更 [#pda78f32]
現在の入力フォームは、各フィールドが必須だけのチェックし...
priceに文字を入力して、Save Chageボタンを押すと
#ref(validation_error1.jpg);
が出力されます。
これでは、エラーの原因が分かりづらいので、validation.xml...
#pre{{
<field property="price" depends="required">
<arg0 key="product.price" />
</field>
}}
を
#pre{{
<field property="price" depends="required,double">
<arg0 key="product.price" />
</field>
}}
としたが、ダメでした。
原因は、Validationが行われる前に、Productの値がHTTPのパラ...
*** Validationエラーへの対応 [#l2ea6018]
ソースをトレースした結果、bindAndValidationを使用する場合...
対応策としては、ProductionのPriceをDoubleからStringに変え...
これでは、GenMVCプラグインを起動すると間違ったCreate tabl...
*** commandクラスの追加 [#x8f77839]
そこで、domainクラスに対応するcommandクラスをGenMVCプラグ...
更に、EditProductControllerでcomanndオブジェクトとdomain...
#pre{{
protected void bind(Object target, Object source) throw...
CustomDataBinder binder = new CustomDataBinder(target, ...
this.prepareBinder(binder);
binder.bind();
}
protected Object commandToDomain(Object source) throws E...
Order object = manager.findById(((ProductCommand)source...
bind(object, source);
return (object);
}
protected Object domainToCommand(Object source) throws E...
Object object = new ProductCommand();
bind(object, source);
return (object);
}
}}
これでようやく、priceのValidationが正常に行えるようになり...
#ref(validation_error2.jpg);
*** スタイルシートの設定 [#x0bfaf64]
最後にスタイルシート使って衣装替えをします。
スタイルシートについては、詳しくないのでAgile Web Develop...
スタイルシートを追加したlist.vmは、以下の通りです。
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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.ima...
</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=${prod...
<a href="#springUrl(${deleteLink})">[delete]</a>
</td>
</tr>
#end
</table>
<a href='#springUrl("/editproduct.htm")'>New product<...
</div>
</body>
</html>
}}
ここで、スタイルシートの指定を
#pre{{
<link rel="stylesheet" href="#springUrl('/css/cart.css...
}}
でしているところと、説明文を一部カットするためにStringのs...
スタイルシートは、src/main/webapp/cssディレクトリに入れて...
#ref(cart.css);
画像ファイルは、src/main/webapp/imagesディレクトリに入れ...
#ref(images.zip);
データベースは、HsqlDBのdata以下のtest.scriptを以下のファ...
#ref(test.script);
スタイルシートの出力結果は、以下の通りです。
#ref(styled_list.jpg);
* カタログページの作成 [#e0b5394f]
次にカタログ表示ページを作成します。Agile Web Development...
その理由は、ProductOpsControllerがProductを扱うコントロー...
** メソッドとテンプレートの追加 [#r8bd7e78]
手順は以下の通りです。
- ProductOpsControllerのlistメソッドをコピーしてcatalogに...
- velocity/productops/list.vmをコピーしてcatalog.vmにファ...
#pre{{
public ModelAndView catalog(HttpServletRequest reques...
return new ModelAndView().addObject(manager.findA...
}
}}
catalog.vmは以下の通りです。
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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....
</div>
#end
</body>
</html>
}}
ブラウザーでhttp://localhost:8080/cart/productops/catalog...
以下のようなカタログページが表示されます。
#ref(catalog1.jpg);
** カートへの追加ボタンの追加 [#haf70d03]
最後にカートへの追加ボタンを入れます。
価格の後に次の行を挿入します。
#pre{{
<form method="post" action="#springUrl("/cartops/add.ht...
#springFormHiddenInput( "product.id" "" )
<input type="submit" value="Add to Cart"/>
</form>
}}
#ref(catalog.vm);
画面では次のように表示されます。
#ref(catalog2.jpg);
* 注文項目(LineItem)の追加 [#afe2afe0]
カートの処理に進む前に注文項目を作成します。
ここでのポイントはカートの注文項目がそのまま注文にリンク...
それでは、注文項目のdomainクラスを作りましょう。
#pre{{
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() +...
}
public Double getPrice() {
return (new Double(quantity.intValue()*product.getPrice...
}
}
}}
#ref(LineItem.java);
として、getter/setterを自動生成してください。
- addQuantityは、個数を追加する
- getPriceは注文項目の小計を返す
この後は、いつものようにGenMVC:scaffoldを実行します。
#pre{{
mvn package
mvn GenMVC:scaffold
}}
* カートの処理 [#fa471eed]
カートの処理を行う、CartServiceとカートに対する要求を処理...
** CartServiceの追加 [#re732448]
最初にCartServiceを追加します。CartServiceでは
- lineItemMapで注文項目を管理
- lineItemの追加、合計の計算
を行います。
#pre{{
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;
}
}
}}
#ref(CartService.java);
ここで、session-def.xmlが正しくweb.xmlに追加されているこ...
#pre{{
<param-value>
/WEB-INF/custom-editor.xml
/WEB-INF/db-def.xml
/WEB-INF/session-def.xml
/WEB-INF/applicationContext.xml
</param-value>
}}
session-def.xmlでcartServiceを以下の様に定義します。
#pre{{
<!-- a HTTP Session-scoped bean exposed as a proxy -->
<bean id="cartService" class="example.cart.service.CartS...
}}
これで、セッション毎に新しいcartServiceを取得することがで...
#ref(session-def.xml);
** CartOpsControllerの追加 [#e9b3ae9e]
ProductOpsContollerをコピーしてCarOptsControllerを作成し...
- setupCartServiceでは、session scopeオブジェクトをWebApp...
セットします
#pre{{
private CartService cartService;
private ProductManager manager;
private void setupCartService(HttpServletRequest request...
ApplicationContext co = WebApplicationContextUt...
getRequiredWebApplicationContext( request....
cartService = (CartService)co.getBean("cartServ...
}
}}
listでは、
- lineItemのリストをcartServiceから取得し、"lineItemList"...
- テンプレートは、デフォルトでcartops/list.vmがセットされ...
#pre{{
public ModelAndView list(HttpServletRequest request, ...
setupCartService(request);
return new ModelAndView().addObject("lineItemList", ...
}
}}
addでは、
- HTTP要求からProductのidを取得し、ProductManagerを使って...
- 新たにLineItemを生成し、productId, productをセットし、c...
- lineItemのリストをcartServiceから取得し、"lineItemList"...
- テンプレートにcartops/list.vmをセットします
#pre{{
public ModelAndView add(HttpServletRequest request, H...
Integer productId = new Integer(ServletRequestUti...
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.g...
modelAndView.setViewName("cartops/list");
return modelAndView;
}
}}
** cartops/list.vmの追加 [#r4acda56]
cartopsフォルダをsrc/main/webapp/WEB-INF/velocityに作成し...
簡単な項目一覧をlist.vmで出力します。
#pre{{
<html>
<head>
<title>Cart</title>
</head>
<body>
<h1>Your Wine Cart</h1>
<ul>
#foreach ($lineItem in $lineItemList)
<li> $lineItem.quantity × $lineItem.product.title ...
#end
</ul>
</body>
</html>
}}
** コンフィグファイルへのCartOpsControllerの追加 [#t701a7...
CartOpsControllerは、GenMVCプラグインの影響を受けないよう...
#pre{{
<bean id="cartOpsController" class="example.cart.web.Car...
parent="baseProductController"/>
}}
jettyプラグインを起動し、
#pre{{
mvn jetty:run
}}
ブラウザーからカタログ画面を表示(http://localhost:8080/c...
#ref(cart1.jpg);
** 価格とEmpty cartボタンの追加 [#kcab59f9]
次に小計と合計の表示とEmpty cartボタンを追加し、ひとまずc...
- list, addにtotalを追加登録します
#pre{{
modelAndView.addObject("total", cartService.getTotal...
}}
- emptyCartを追加します
#pre{{
public ModelAndView emptyCart(HttpServletRequest requ...
setupCartService(request);
cartService.getLineItemMap().clear();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catal...
return modelAndView;
}
}}
#ref(CartOpsController.java);
cartops/list.vmにempty cartボタンを追加します。
#pre{{
<form method="post" action="#springUrl("/cartops/emptyCar...
<input type="submit" value="Empty cart"/>
</form>
}}
#ref(list.vm);
最終的な画面は、以下のようになります。
#ref(cart2.jpg);
* チェックアウトの処理 [#y471a9d2]
最後にチェックアウトの処理を追加します。
** Orderの作成 [#a8ebd5e7]
注文書には、以下の項目を入れます。
- name 購入者の氏名
- address 購入者の住所
- email 購入者のe-mailアドレス
- payType 支払い方法
domainクラスとしてOrderを作成します。
#pre{{
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を実行します。
#pre{{
mvn package
mvn GenMVC:scaffold
}}
** テーブルの関連づけ [#bf432f40]
すべてのテーブルが出そろったので、テーブルの関連づけをし...
詳しくは、[[モデル中心プログラミング>http://www.pwv.co.jp...
- LineItemから参照しているProductを結合する場合、LineItem...
#pre{{
<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>
}}
- Orderと複数のLineItem結合する場合、Order.hbm.xmlに以下...
#pre{{
<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, Li...
** checkoutの追加 [#f04ead94]
OrderOpsControllerにcheckoutメソッドを追加します。
それと同時にcartService属性を追加して、session scopeのcar...
#pre{{
private CartService cartService;
private void setupCartService(HttpServletRequest request...
ApplicationContext co = WebApplicationContextUt...
getRequiredWebApplicationContext( request.getSe...
cartService = (CartService)co.getBean("cartServ...
}
}}
#pre{{
public ModelAndView checkout(HttpServletRequest reque...
setupCartService(request);
Order order = new Order();
order.setLineItemList(new ArrayList(cartService.getL...
manager.saveOrUpdate(order);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/editorder.htm?id...
return modelAndView;
}
}}
#ref(CartOpsController.java);
次にvelocity/cartopgs/list.vmにcheckoutボタンを追加します。
#pre{{
<form method="post" action="#springUrl("/orderops/checkou...
<input type="submit" value="Checkout"/>
</form>
}}
#ref(list.vm);
これだけの変更でカートの項目一覧と注文がデータベースにセ...
*** 注文一覧の画面 [#c2445dc1]
注文が空の状態から始めます。
ブラウザーからhttp://localhost:8080/cart/orderops/list.ht...
#ref(order1.jpg);
*** カートに2つの商品を入れます [#la347a13]
ブラウザーからhttp://localhost:8080/cart/productops/catal...
#ref(order2.jpg);
*** 注文情報を入力します [#y7ec79be]
#ref(order3.jpg);
*** 注文一覧に1個の注文が入っています [#z1454c28]
#ref(order4.jpg);
*** データベースの内容を確認します [#pbe30976]
#ref(order5.jpg);
*** チェックアウトの衣装替え [#yca7323d]
EditOrderControllerは、戻り場所をコンフィグファイルで固定...
#pre{{
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 EditOrderContro...
private Map payTypeOptions;
protected Map referenceData(HttpServletRequest request, ...
Map refData = new HashMap();
refData.put("payTypeOptions", payTypeOptions);
return refData;
}
public void setPayTypeOptions(Map payTypeOptions) {
this.payTypeOptions = payTypeOptions;
}
}
}}
#ref(InputOrderController.java);
payTypeのオプションはマップで指定するため、Velocityの中で...
inputOrder.vmのVelocityテンプレートを以下の様に修正します。
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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='...
</p>
<p>
<label>E-Mail:</label>
#springFormInput( "order.email" "size='40'" )
</p>
<p>
<label>Pay with:</label>
#springFormSingleSelect( "order.paytype" $payTypeOpti...
</p>
<input type="submit" value="Place Order" class="submit...
</form>
</fieldset>
</div>
</body>
</html>
}}
#ref(inputOrder.vm);
これで、InputOrderControllerが完成しましたので、checkout...
inputOrder.htmに変更します。
#pre{{
modelAndView.setViewName("redirect:/inputorder.htm?i...
}}
Springのコンフィグファイルは、server-def.xmlに以下の項目...
#pre{{
<bean id="inputOrderController" class="example.cart.web....
parent="orderController">
<property name="formView" value="inputOrder"/>
<property name="successView" value="redirect:orde...
<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の選択肢をセットします。
注文入力画面は以下のようになります。
#ref(order6.jpg);
** 注文確認と注文確定、キャンセル処理 [#d3e29de9]
最後に、注文確認と注文確定、キャンセル処理を組み込みます。
*** 注文確認 [#m43ca5d4]
OrderOpsControllerに以下のようなcheckorderメソッドを追加...
#pre{{
public ModelAndView checkorder(HttpServletRequest req...
setupCartService(request);
int id = ServletRequestUtils.getRequiredIntParame...
Order order = manager.findById(new Integer(id));
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject(order);
modelAndView.addObject("total", cartService.getTotal...
return modelAndView;
}
}}
- cartServiceは、合計を計算するのを省略するために使いました
- データベースからorderを取得し、それをmodelAndViewに登録
- totalをmodelAndViewに登録
の処理をしています。
Velocityテンプレートのvelocity/orderops/checkorder.vmは以...
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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", $lineI...
</tr>
#end
<tr class="total-line">
<td colspan="2">Total</td>
<td class="totale-cell">$numberTool.format("##0", $tota...
</tr>
</table>
<div>
<form method="post" action="#springUrl("/orderops/confir...
#springFormHiddenInput( "order.id" "" )
<input type="submit" value="Confirm order"/>
</form>
<form method="post" action="#springUrl("/orderops/cancel...
#springFormHiddenInput( "order.id" "" )
<input type="submit" value="Cancel order"/>
</form>
</div>
</body>
</html>
}}
#ref(checkorder.vm);
InputOrderからの遷移画面をcheckorderにするために、InputOr...
#pre{{
protected void doSubmitAction(Object object) throws Exce...
OrderCommand command = (OrderCommand)object;
this.setSuccessView("redirect:orderops/checkorder.htm?i...
super.doSubmitAction(object);
}
}}
注文確認画面は以下の通りです。
#ref(order7.jpg);
*** 注文の確定 [#gc797501]
OrderOpsControllerに以下のようなconfirmメソッドを追加しま...
#pre{{
public ModelAndView confirm(HttpServletRequest reques...
setupCartService(request);
cartService.getLineItemMap().clear();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catal...
return modelAndView;
}
}}
注文確定の主な処理は、カートを空にし、カタログページに飛...
*** キャンセル [#j8d87e35]
OrderOpsControllerに以下のようなcancelメソッドを追加しま...
#pre{{
public ModelAndView cancel(HttpServletRequest request...
setupCartService(request);
cartService.getLineItemMap().clear();
int id = ServletRequestUtils.getRequiredIntParame...
Order order = manager.findById(new Integer(id));
manager.delete(order);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catal...
return modelAndView;
}
}}
- 注文をデータベースから削除する
- カートを空にする
- カタログページにジャンプする
の処理をしています。
*** lazily initializeエラーへの対処 [#j2d7d59b]
hibernateのlazy initialize機能ではhibernateのsessionを使...
対応としては、
- lazy="false"をbag, many-to-one設定に追加する
- web.xmlにOpenSessionInViewFilterを追加する
がありますが、lazy="false"とするとパフォーマンスが落ちる...
web.xmlに以下の行を追加します。
#pre{{
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.suppor...
<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問題の全ソース [#ye260386]
cart問題の全ソースを以下にアップします。ダウンロードして...
#ref(cart.zip);
このzipファイルからcartを動作させるには、
- [[Mavenのインストール]]に従ってmaven2をインストールします
- [[Spring-MVCプラグイン機能追加2(Validation)]]に従っ...
- [[MavenとSpringによるMVC技法]]に従いjtaのインストールを...
- HSqlDBを[[こちら>http://www.pwv.co.jp/take_public_html/...
- HsqlDbサーバを起動します(demoフォルダのrunServer.batを...
- 添付のtest.scriptをダウンロードして、HsqlDBのdataフォル...
- mvn jetty:runを起動します
これで、ブラウザーからhttp://localhost:8080/cart/producto...
* コメント [#l5b03105]
この記事は、
#vote(おもしろかった[6],そうでもない[4],わかりずらい[5])
終了行:
[[FrontPage]]
#contents
2008/01/24からのアクセス回数 &counter;
* Cart問題 [#c949db9e]
Agile Web Development with Railsの例題と同じ問題をSpring...
メモです。
もう一つの目的は、Spring-MVCプラグインがどの程度実際の問...
プラグインのインストールについては、[[Spring-MVCプラグイ...
** プロジェクトの作成 [#y8873d2a]
mavenを使ってプロジェクトを生成します。
- groupIdは、example.cart
- artifactIdは、cart
とします。ecliseでプロジェクトを管理できるようにeclipseプ...
#pre{{
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/we...
#pre{{
db.url=jdbc:hsqldb:hsql://localhost
}}
HsqlDBのインストールは、[[こちら>http://www.pwv.co.jp/tak...
HsqlDBサーバの起動は、HSqlDBをインストールしたディレクト...
#pre{{
./runServer.sh
}}
と入力してください。これでHsqlDBサーバ起動しています。
しかし、HSqlDBサーバにT_MEMBERのテーブルができていません...
#pre{{
mvn jetty:run
}}
ブラウザーでhttp://localhost:8080/cart/
と入力してください。addリンクだけのページが表示された成功...
CTRL-Cでjettyが終了します。
* [[session scopeについて]] [#fddab324]
長くなったので別タイトルにしました。
* Product(製品の)の管理 [#r6b52203]
最初にProductを管理するページを作成します。
さしあたり、管理機能として
- 製品の一覧表示
- 製品の追加、編集、削除
ができるようにします。
** Productドメインモデルの作成 [#g8855bc8]
最初にProductのドメインモデルを作成します。
ドメインモデルは、example.cart.domainパッケージ内に定義し...
eclipseで以下のように入力した後、getter/setterを自動生成...
#pre{{
package example.cart.domain;
public class Product {
private Integer id;
private String title;
private String description;
private String image_url;
}
}}
#ref(Product.java);
** GenMVCプラグインの起動 [#x97dd396]
GenMVCプラグインのscaffoldゴールを指定して、ProductのDao...
を自動生成します。
その前に、GenMVCプラグインは、Productのクラスファイルを見...
#pre{{
mvn package
mvn GenMVC:scaffold
}}
このコマンドで、
#pre{{
[INFO] --------------------------------------------------...
[INFO] Building Unnamed - example.cart:cart:war:1.0-SNAPS...
[INFO] task-segment: [GenMVC:scaffold]
[INFO] --------------------------------------------------...
[INFO] [GenMVC:scaffold]
[INFO] pkgName:example.cart
[INFO] runtime.classpath:/Users/take/Documents/workspace/...
[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プラグインを実行して、
#pre{{
mvn jetty:run
}}
ブラウザーでhttp://localhost:8080/cart/productops/list.ht...
以下のような画面が表示されますので、
#ref(product_empty.jpg);
これだけで、Productのリスト表示、編集の画面が生成されます。
| id | description | image_url | title |
| 1 | グンタースブルム村の畑はフランスのシャブリ地区に似...
| 2 | このワインは手入れの良く行き届いたシュタイグテラッ...
| 3 | このワインは手入れの良く行き届いたグンタースブルム...
と入力したのが、以下の表示例です
#ref(product_list.jpg);
** 属性の追加 [#b2ed20d1]
scaffoldの後にProductに属性を追加したくなることはよくあり...
Product の属性を変更したときの手順は以下の通りです。
- テーブルの削除
- velocity/*_stub.vmファイルのバックアップ
- validation.xmlファイルのバックアップ
通常は、これで十分ですが、以下のファイルを修正した場合に...
- webapp/WEB-INF/hbm-dir/Product.hbm.xml
- main/resources/form-messages.properties
今回は、自動生成されたファイルを全く変更していないので、...
*** テーブルの削除 [#x9330700]
開発の途中ではデータベースのテーブルを変更したり、値を参...
Ecl,ipseのプラグインDbEditです。
DbEditのインストール方法は[[ここ>http://www.pwv.co.jp/tak...
を参照してください。
DbEditのTableタグを開くと以下のようにT_MEMBERとT_PRODUCT...
#ref(DbEdit.jpg);
GenMVCプラグインでは、クラス名の前にT_を付けたテーブルが...
T_PRODUCTを削除するには、 T_PRODUCTで右マウスクリックか...
- すでに沢山のデータが入っていたらどうすれば良いのか?
この答えは、テストケースのデバッグで紹介する予定です。
*** Productクラスの変更 [#j28b6d69]
Productに価格(price)を追加します。
以下のように属性priceを追加し、getter/setterを自動生成す...
#pre{{
private Double price;
}}
日本では価格に小数点はないのですが、ここでは例としてDoubl...
それでは、先ほどと同様にGenMVCプラグインを起動します。
#pre{{
mvn package
mvn GenMVC:scaffold
}}
** 画面(Velocityテンプレート)の変更 [#b82e17e4]
GenMVCプラグインが生成する画面は、属性の出力順がProductク...
ProductのVelocityテンプレートは、main/webapp/WEB-INF/velo...
- list_stub.vm
- list.vm
が一覧を表示するテンプレートです。
list.vmを見ると
#pre{{
parse ( "productops/list_stub.vm" )
}}
だけです。
これは、GenMVCプラグインがlist.vmを変更しないようするため...
2段階で処理しています。
従ってユーザvelocityテンプレートを変更する場合には、list_...
以下に順序を入れ替えたlist.vmを示します。
#pre{{
<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>...
#set( $deleteLink = "/productops/delete.htm?id=${produ...
<td><a href="#springUrl(${deleteLink})">[delete]...
</tr>
#end
</table>
<a href='#springUrl("/editproduct.htm")'>add</a>
</body>
</html>
}}
同様に編集画面editProduct.vmも順序を変え、Descriptionをte...
#pre{{
<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=...
<td>
#springBind("product.title")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>description:</td>
<td>#springFormTextarea( "product.descriptio...
<td>
#springBind("product.description")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>image_url:</td>
<td>#springFormInput( "product.image_url" "s...
<td>
#springBind("product.image_url")
<font color="red">${status.errorMessage}</font>
</td>
</tr>
<tr>
<td>price:</td>
<td>#springFormInput( "product.price" "size=...
<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>
}}
** Validationの変更 [#pda78f32]
現在の入力フォームは、各フィールドが必須だけのチェックし...
priceに文字を入力して、Save Chageボタンを押すと
#ref(validation_error1.jpg);
が出力されます。
これでは、エラーの原因が分かりづらいので、validation.xml...
#pre{{
<field property="price" depends="required">
<arg0 key="product.price" />
</field>
}}
を
#pre{{
<field property="price" depends="required,double">
<arg0 key="product.price" />
</field>
}}
としたが、ダメでした。
原因は、Validationが行われる前に、Productの値がHTTPのパラ...
*** Validationエラーへの対応 [#l2ea6018]
ソースをトレースした結果、bindAndValidationを使用する場合...
対応策としては、ProductionのPriceをDoubleからStringに変え...
これでは、GenMVCプラグインを起動すると間違ったCreate tabl...
*** commandクラスの追加 [#x8f77839]
そこで、domainクラスに対応するcommandクラスをGenMVCプラグ...
更に、EditProductControllerでcomanndオブジェクトとdomain...
#pre{{
protected void bind(Object target, Object source) throw...
CustomDataBinder binder = new CustomDataBinder(target, ...
this.prepareBinder(binder);
binder.bind();
}
protected Object commandToDomain(Object source) throws E...
Order object = manager.findById(((ProductCommand)source...
bind(object, source);
return (object);
}
protected Object domainToCommand(Object source) throws E...
Object object = new ProductCommand();
bind(object, source);
return (object);
}
}}
これでようやく、priceのValidationが正常に行えるようになり...
#ref(validation_error2.jpg);
*** スタイルシートの設定 [#x0bfaf64]
最後にスタイルシート使って衣装替えをします。
スタイルシートについては、詳しくないのでAgile Web Develop...
スタイルシートを追加したlist.vmは、以下の通りです。
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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.ima...
</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=${prod...
<a href="#springUrl(${deleteLink})">[delete]</a>
</td>
</tr>
#end
</table>
<a href='#springUrl("/editproduct.htm")'>New product<...
</div>
</body>
</html>
}}
ここで、スタイルシートの指定を
#pre{{
<link rel="stylesheet" href="#springUrl('/css/cart.css...
}}
でしているところと、説明文を一部カットするためにStringのs...
スタイルシートは、src/main/webapp/cssディレクトリに入れて...
#ref(cart.css);
画像ファイルは、src/main/webapp/imagesディレクトリに入れ...
#ref(images.zip);
データベースは、HsqlDBのdata以下のtest.scriptを以下のファ...
#ref(test.script);
スタイルシートの出力結果は、以下の通りです。
#ref(styled_list.jpg);
* カタログページの作成 [#e0b5394f]
次にカタログ表示ページを作成します。Agile Web Development...
その理由は、ProductOpsControllerがProductを扱うコントロー...
** メソッドとテンプレートの追加 [#r8bd7e78]
手順は以下の通りです。
- ProductOpsControllerのlistメソッドをコピーしてcatalogに...
- velocity/productops/list.vmをコピーしてcatalog.vmにファ...
#pre{{
public ModelAndView catalog(HttpServletRequest reques...
return new ModelAndView().addObject(manager.findA...
}
}}
catalog.vmは以下の通りです。
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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....
</div>
#end
</body>
</html>
}}
ブラウザーでhttp://localhost:8080/cart/productops/catalog...
以下のようなカタログページが表示されます。
#ref(catalog1.jpg);
** カートへの追加ボタンの追加 [#haf70d03]
最後にカートへの追加ボタンを入れます。
価格の後に次の行を挿入します。
#pre{{
<form method="post" action="#springUrl("/cartops/add.ht...
#springFormHiddenInput( "product.id" "" )
<input type="submit" value="Add to Cart"/>
</form>
}}
#ref(catalog.vm);
画面では次のように表示されます。
#ref(catalog2.jpg);
* 注文項目(LineItem)の追加 [#afe2afe0]
カートの処理に進む前に注文項目を作成します。
ここでのポイントはカートの注文項目がそのまま注文にリンク...
それでは、注文項目のdomainクラスを作りましょう。
#pre{{
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() +...
}
public Double getPrice() {
return (new Double(quantity.intValue()*product.getPrice...
}
}
}}
#ref(LineItem.java);
として、getter/setterを自動生成してください。
- addQuantityは、個数を追加する
- getPriceは注文項目の小計を返す
この後は、いつものようにGenMVC:scaffoldを実行します。
#pre{{
mvn package
mvn GenMVC:scaffold
}}
* カートの処理 [#fa471eed]
カートの処理を行う、CartServiceとカートに対する要求を処理...
** CartServiceの追加 [#re732448]
最初にCartServiceを追加します。CartServiceでは
- lineItemMapで注文項目を管理
- lineItemの追加、合計の計算
を行います。
#pre{{
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;
}
}
}}
#ref(CartService.java);
ここで、session-def.xmlが正しくweb.xmlに追加されているこ...
#pre{{
<param-value>
/WEB-INF/custom-editor.xml
/WEB-INF/db-def.xml
/WEB-INF/session-def.xml
/WEB-INF/applicationContext.xml
</param-value>
}}
session-def.xmlでcartServiceを以下の様に定義します。
#pre{{
<!-- a HTTP Session-scoped bean exposed as a proxy -->
<bean id="cartService" class="example.cart.service.CartS...
}}
これで、セッション毎に新しいcartServiceを取得することがで...
#ref(session-def.xml);
** CartOpsControllerの追加 [#e9b3ae9e]
ProductOpsContollerをコピーしてCarOptsControllerを作成し...
- setupCartServiceでは、session scopeオブジェクトをWebApp...
セットします
#pre{{
private CartService cartService;
private ProductManager manager;
private void setupCartService(HttpServletRequest request...
ApplicationContext co = WebApplicationContextUt...
getRequiredWebApplicationContext( request....
cartService = (CartService)co.getBean("cartServ...
}
}}
listでは、
- lineItemのリストをcartServiceから取得し、"lineItemList"...
- テンプレートは、デフォルトでcartops/list.vmがセットされ...
#pre{{
public ModelAndView list(HttpServletRequest request, ...
setupCartService(request);
return new ModelAndView().addObject("lineItemList", ...
}
}}
addでは、
- HTTP要求からProductのidを取得し、ProductManagerを使って...
- 新たにLineItemを生成し、productId, productをセットし、c...
- lineItemのリストをcartServiceから取得し、"lineItemList"...
- テンプレートにcartops/list.vmをセットします
#pre{{
public ModelAndView add(HttpServletRequest request, H...
Integer productId = new Integer(ServletRequestUti...
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.g...
modelAndView.setViewName("cartops/list");
return modelAndView;
}
}}
** cartops/list.vmの追加 [#r4acda56]
cartopsフォルダをsrc/main/webapp/WEB-INF/velocityに作成し...
簡単な項目一覧をlist.vmで出力します。
#pre{{
<html>
<head>
<title>Cart</title>
</head>
<body>
<h1>Your Wine Cart</h1>
<ul>
#foreach ($lineItem in $lineItemList)
<li> $lineItem.quantity × $lineItem.product.title ...
#end
</ul>
</body>
</html>
}}
** コンフィグファイルへのCartOpsControllerの追加 [#t701a7...
CartOpsControllerは、GenMVCプラグインの影響を受けないよう...
#pre{{
<bean id="cartOpsController" class="example.cart.web.Car...
parent="baseProductController"/>
}}
jettyプラグインを起動し、
#pre{{
mvn jetty:run
}}
ブラウザーからカタログ画面を表示(http://localhost:8080/c...
#ref(cart1.jpg);
** 価格とEmpty cartボタンの追加 [#kcab59f9]
次に小計と合計の表示とEmpty cartボタンを追加し、ひとまずc...
- list, addにtotalを追加登録します
#pre{{
modelAndView.addObject("total", cartService.getTotal...
}}
- emptyCartを追加します
#pre{{
public ModelAndView emptyCart(HttpServletRequest requ...
setupCartService(request);
cartService.getLineItemMap().clear();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catal...
return modelAndView;
}
}}
#ref(CartOpsController.java);
cartops/list.vmにempty cartボタンを追加します。
#pre{{
<form method="post" action="#springUrl("/cartops/emptyCar...
<input type="submit" value="Empty cart"/>
</form>
}}
#ref(list.vm);
最終的な画面は、以下のようになります。
#ref(cart2.jpg);
* チェックアウトの処理 [#y471a9d2]
最後にチェックアウトの処理を追加します。
** Orderの作成 [#a8ebd5e7]
注文書には、以下の項目を入れます。
- name 購入者の氏名
- address 購入者の住所
- email 購入者のe-mailアドレス
- payType 支払い方法
domainクラスとしてOrderを作成します。
#pre{{
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を実行します。
#pre{{
mvn package
mvn GenMVC:scaffold
}}
** テーブルの関連づけ [#bf432f40]
すべてのテーブルが出そろったので、テーブルの関連づけをし...
詳しくは、[[モデル中心プログラミング>http://www.pwv.co.jp...
- LineItemから参照しているProductを結合する場合、LineItem...
#pre{{
<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>
}}
- Orderと複数のLineItem結合する場合、Order.hbm.xmlに以下...
#pre{{
<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, Li...
** checkoutの追加 [#f04ead94]
OrderOpsControllerにcheckoutメソッドを追加します。
それと同時にcartService属性を追加して、session scopeのcar...
#pre{{
private CartService cartService;
private void setupCartService(HttpServletRequest request...
ApplicationContext co = WebApplicationContextUt...
getRequiredWebApplicationContext( request.getSe...
cartService = (CartService)co.getBean("cartServ...
}
}}
#pre{{
public ModelAndView checkout(HttpServletRequest reque...
setupCartService(request);
Order order = new Order();
order.setLineItemList(new ArrayList(cartService.getL...
manager.saveOrUpdate(order);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/editorder.htm?id...
return modelAndView;
}
}}
#ref(CartOpsController.java);
次にvelocity/cartopgs/list.vmにcheckoutボタンを追加します。
#pre{{
<form method="post" action="#springUrl("/orderops/checkou...
<input type="submit" value="Checkout"/>
</form>
}}
#ref(list.vm);
これだけの変更でカートの項目一覧と注文がデータベースにセ...
*** 注文一覧の画面 [#c2445dc1]
注文が空の状態から始めます。
ブラウザーからhttp://localhost:8080/cart/orderops/list.ht...
#ref(order1.jpg);
*** カートに2つの商品を入れます [#la347a13]
ブラウザーからhttp://localhost:8080/cart/productops/catal...
#ref(order2.jpg);
*** 注文情報を入力します [#y7ec79be]
#ref(order3.jpg);
*** 注文一覧に1個の注文が入っています [#z1454c28]
#ref(order4.jpg);
*** データベースの内容を確認します [#pbe30976]
#ref(order5.jpg);
*** チェックアウトの衣装替え [#yca7323d]
EditOrderControllerは、戻り場所をコンフィグファイルで固定...
#pre{{
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 EditOrderContro...
private Map payTypeOptions;
protected Map referenceData(HttpServletRequest request, ...
Map refData = new HashMap();
refData.put("payTypeOptions", payTypeOptions);
return refData;
}
public void setPayTypeOptions(Map payTypeOptions) {
this.payTypeOptions = payTypeOptions;
}
}
}}
#ref(InputOrderController.java);
payTypeのオプションはマップで指定するため、Velocityの中で...
inputOrder.vmのVelocityテンプレートを以下の様に修正します。
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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='...
</p>
<p>
<label>E-Mail:</label>
#springFormInput( "order.email" "size='40'" )
</p>
<p>
<label>Pay with:</label>
#springFormSingleSelect( "order.paytype" $payTypeOpti...
</p>
<input type="submit" value="Place Order" class="submit...
</form>
</fieldset>
</div>
</body>
</html>
}}
#ref(inputOrder.vm);
これで、InputOrderControllerが完成しましたので、checkout...
inputOrder.htmに変更します。
#pre{{
modelAndView.setViewName("redirect:/inputorder.htm?i...
}}
Springのコンフィグファイルは、server-def.xmlに以下の項目...
#pre{{
<bean id="inputOrderController" class="example.cart.web....
parent="orderController">
<property name="formView" value="inputOrder"/>
<property name="successView" value="redirect:orde...
<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の選択肢をセットします。
注文入力画面は以下のようになります。
#ref(order6.jpg);
** 注文確認と注文確定、キャンセル処理 [#d3e29de9]
最後に、注文確認と注文確定、キャンセル処理を組み込みます。
*** 注文確認 [#m43ca5d4]
OrderOpsControllerに以下のようなcheckorderメソッドを追加...
#pre{{
public ModelAndView checkorder(HttpServletRequest req...
setupCartService(request);
int id = ServletRequestUtils.getRequiredIntParame...
Order order = manager.findById(new Integer(id));
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject(order);
modelAndView.addObject("total", cartService.getTotal...
return modelAndView;
}
}}
- cartServiceは、合計を計算するのを省略するために使いました
- データベースからorderを取得し、それをmodelAndViewに登録
- totalをmodelAndViewに登録
の処理をしています。
Velocityテンプレートのvelocity/orderops/checkorder.vmは以...
#pre{{
<html>
<head>
<link rel="stylesheet" href="#springUrl('/css/cart.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", $lineI...
</tr>
#end
<tr class="total-line">
<td colspan="2">Total</td>
<td class="totale-cell">$numberTool.format("##0", $tota...
</tr>
</table>
<div>
<form method="post" action="#springUrl("/orderops/confir...
#springFormHiddenInput( "order.id" "" )
<input type="submit" value="Confirm order"/>
</form>
<form method="post" action="#springUrl("/orderops/cancel...
#springFormHiddenInput( "order.id" "" )
<input type="submit" value="Cancel order"/>
</form>
</div>
</body>
</html>
}}
#ref(checkorder.vm);
InputOrderからの遷移画面をcheckorderにするために、InputOr...
#pre{{
protected void doSubmitAction(Object object) throws Exce...
OrderCommand command = (OrderCommand)object;
this.setSuccessView("redirect:orderops/checkorder.htm?i...
super.doSubmitAction(object);
}
}}
注文確認画面は以下の通りです。
#ref(order7.jpg);
*** 注文の確定 [#gc797501]
OrderOpsControllerに以下のようなconfirmメソッドを追加しま...
#pre{{
public ModelAndView confirm(HttpServletRequest reques...
setupCartService(request);
cartService.getLineItemMap().clear();
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catal...
return modelAndView;
}
}}
注文確定の主な処理は、カートを空にし、カタログページに飛...
*** キャンセル [#j8d87e35]
OrderOpsControllerに以下のようなcancelメソッドを追加しま...
#pre{{
public ModelAndView cancel(HttpServletRequest request...
setupCartService(request);
cartService.getLineItemMap().clear();
int id = ServletRequestUtils.getRequiredIntParame...
Order order = manager.findById(new Integer(id));
manager.delete(order);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/productops/catal...
return modelAndView;
}
}}
- 注文をデータベースから削除する
- カートを空にする
- カタログページにジャンプする
の処理をしています。
*** lazily initializeエラーへの対処 [#j2d7d59b]
hibernateのlazy initialize機能ではhibernateのsessionを使...
対応としては、
- lazy="false"をbag, many-to-one設定に追加する
- web.xmlにOpenSessionInViewFilterを追加する
がありますが、lazy="false"とするとパフォーマンスが落ちる...
web.xmlに以下の行を追加します。
#pre{{
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.suppor...
<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問題の全ソース [#ye260386]
cart問題の全ソースを以下にアップします。ダウンロードして...
#ref(cart.zip);
このzipファイルからcartを動作させるには、
- [[Mavenのインストール]]に従ってmaven2をインストールします
- [[Spring-MVCプラグイン機能追加2(Validation)]]に従っ...
- [[MavenとSpringによるMVC技法]]に従いjtaのインストールを...
- HSqlDBを[[こちら>http://www.pwv.co.jp/take_public_html/...
- HsqlDbサーバを起動します(demoフォルダのrunServer.batを...
- 添付のtest.scriptをダウンロードして、HsqlDBのdataフォル...
- mvn jetty:runを起動します
これで、ブラウザーからhttp://localhost:8080/cart/producto...
* コメント [#l5b03105]
この記事は、
#vote(おもしろかった[6],そうでもない[4],わかりずらい[5])
ページ名:
SmartDoc