2008年5月27日火曜日

Springサンプルに新規作成機能を作成する

Spring 2.5 付属のチュートリアル「Developing a Spring Framework MVC application step-by-step」に、Productの新規作成機能を追加します。

【手順概要】

ProductDAOにインターフェースinsertProductを追加します。

JdbcProductDaoでinsertProductを実装します。

Productにequalsメソッドを追加します。(テスト用)

JdbcProductDaoTestsにテストメソッドを追加します。

ProductManagerにinsertProductインターフェースを追加します。

SimpleProductManagerでinsertProductメソッドを実装します。

InsertProduct Beanを追加します。

InsertProductValidatoを追加します。

InsertProductFormControllerを追加します。

InsertProduct画面を追加します。

springapp-servlet.xml、メッセージ、リンクにエントリーを追加します。

【手順詳細】

ProductDAOにインターフェースinsertProductを追加します。
 public void insertProduct(Product prod);

JdbcProductDaoでinsertProductを実装します。
    public void insertProduct(Product prod) {
logger.info("Insert product: " + prod.getDescription());
int count = getSimpleJdbcTemplate().update(
"insert into products(id, description, price) values(:id, :description, :price)",
new MapSqlParameterSource().addValue("id", prod.getId())
.addValue("description", prod.getDescription())
.addValue("price", prod.getPrice()));
logger.info("Inserted: " + count);
}

Productにequalsメソッドを追加します。(テスト用です)
※ Doubleの比較には'=='ではなくequalsを使います。当たり前か。しかしTigerからdoubleとDoubleが等価的に使えるようになったので油断してしまいました。
 @Override
public boolean equals(Object obj) {
if (obj instanceof Product) {
Product prod = (Product)obj;
if (id != prod.getId()) {
logger.info("ID unmatch.");
return false;
}
if (!description.equals(prod.getDescription())){
logger.info("Description unmatch.");
return false;
}
if (!price.equals(prod.getPrice())) {
// '==' では比較できない
logger.info("Price unmatch.");
return false;
}
return true;
} else {
logger.info("Not Product instance.");
return false;
}
}

JdbcProductDaoTestsにテストメソッドを追加します。
やっててよかったJUnit。後で役に立ちました。
    private Product createProduct(int id, String desc, double price) {
Product ret = new Product();
ret.setId(id);
ret.setDescription(desc);
ret.setPrice(price);
return ret;
}
private void dumpProduct(Product p) {
logger.info(p);
}

public void testInsertProduct() {
List<Product> products = new ArrayList<Product>();
Product p1 = createProduct(100, "dummy1", 100.10);
Product p2 = createProduct(101, "dummy2", 100.20);
products.add(p1);
products.add(p2);
for (Product p : products) {
productDao.insertProduct(p);
}
List<Product> insertedProducts = productDao.getProductList();
boolean checked1 = false;
boolean checked2 = false;
for (Product p : insertedProducts) {
if (p.getId() == p1.getId()) {
dumpProduct(p);
dumpProduct(p1);
assertEquals(true, p.equals(p1));
checked1 = true;
}
if (p.getId() == p2.getId()) {
dumpProduct(p);
dumpProduct(p2);
assertEquals(true, p.equals(p2));
checked2 = true;
}
}
if (!checked1 || !checked2) {
fail("Product not inserted.");
}
}

ProductManagerにinsertProductインターフェースを追加します。
 public void insertProduct(Product product);

SimpleProductManagerでinsertProductメソッドを実装します。
 @Override
public void insertProduct(Product product) {
List<Product> products = getProducts();
int ids[] = new int[products.size()];
int i = 0;
// Uniqueキーをゴリゴリと作成
for (Product prod: products) {
ids[i] = prod.getId();
i++;
}
Arrays.sort(ids);
int last = ids[ids.length-1];
last++;
product.setId(last);
productDao.insertProduct(product);
}

InsertProduct Beanを追加します。Nothing specialというやつですね。
package springapp.service;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class InsertProduct {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private String description;
private double price;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
logger.info("description:"+description+" set.");
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
logger.info("price:"+price+" set.");
}
}

InsertProductValidatorを追加します。
ちゃんと個々の項目にエラーがでます。
package springapp.service;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;

public class InsertProductValidator implements Validator {

private int DEFAULT_MIN_PRICE = 1;
private int minPrice = DEFAULT_MIN_PRICE;

/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

public boolean supports(Class clazz) {
return InsertProduct.class.equals(clazz);
}

public void validate(Object obj, Errors errors) {
InsertProduct is = (InsertProduct) obj;
logger.info("Validating ...");
String desc = is.getDescription();
if (desc == null || desc.equals("")) {
logger.info("Description is null or empty string.");
errors.rejectValue("description", "error.description-not-specified", null,
"Value required.");
}
if (is.getPrice() <= minPrice) {
errors.rejectValue("price", "error.too-cheap",
new Object[] { new Integer(minPrice) },
"Value too low.");
}
logger.info("Validation end.");
}

public void setMinPrice(int i) {
minPrice = i;
}

public int getMinPrice() {
return minPrice;
}

}


InsertProductFormControllerを追加します。
package springapp.web;

import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.service.ProductManager;
import springapp.service.InsertProduct;
import springapp.domain.Product;

public class InsertProductFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private ProductManager productManager;

public ModelAndView onSubmit(Object command)
throws ServletException {
InsertProduct is = (InsertProduct) command;
logger.info("Will insert:" + is);
Product product = new Product();
product.setDescription(is.getDescription());
product.setPrice(is.getPrice());
productManager.insertProduct(product);
logger.info(
"returning from PriceIncreaseForm view to " + getSuccessView());
return new ModelAndView(new RedirectView(getSuccessView()));
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}
}

InsertProduct画面を追加します。
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
<head>
<title><fmt:message key="title"/></title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1><fmt:message key="insertproduct.heading"/></h1>
<form:form method="post" commandName="insertProduct">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="right" width="20%">Description</td>
<td width="30%">
<form:input path="description"/>
</td>
<td width="50%">
<form:errors path="description" cssClass="error"/>
</td>
</tr>
<tr>
<td align="right">Price</td>
<td>
<form:input path="price"/>
</td>
<td>
<form:errors path="price" cssClass="error"/>
</td>
</tr>
</table>
<br>
<input type="submit" align="center" value="Execute">
</form:form>
<a href="<c:url value="hello.htm"/>">Home</a>
</body>
</html>

springapp-servlet.xml、メッセージ、リンクにエントリーを追加します。
    <bean name="/insertproduct.htm" class="springapp.web.InsertProductFormController">
<property name="sessionForm" value="true"/>
<property name="commandName" value="insertProduct"/>
<property name="commandClass" value="springapp.service.InsertProduct"/>
<property name="validator">
<bean class="springapp.service.InsertProductValidator"/>
</property>
<property name="formView" value="insertproduct"/>
<property name="successView" value="hello.htm"/>
<property name="productManager" ref="productManager"/>
</bean>

error.description-not-specified=Description not specified!!!

<br><a href="<c:url value="insertproduct.htm"/>">Insert product</a>

以上。少し慣れてきました。

0 件のコメント: