[CONTENTSへ戻る]

サンプルその2(検索画面)

サンプル1を作成したプロジェクトに、商品検索機能を作成してみる。
画面は検索キーを入力する画面と、検索結果を一覧表示する画面の2つ。
また、検索キー入力画面の表示と、この画面で検索キーが押されたときにDBを検索して一覧画面を表示する機能を
1つのコントローラで行う。
先にurlやコントローラ名などを以下のように決めておく。

url/productSearch.htm
コントローラ名productSearchController
コントローラクラスcom.ziqoo.web.controller.ProductSearchController
JSPファイル WEB-INF/jsp/productSearch.jsp商品検索のための検索キーを入力する画面
WEB-INF/jsp/productList.jsp検索結果一覧を表示する画面

準備

ライブラリのコピー

プロジェクトのWEB-INF/libフォルダに以下のjarファイルを追加し、
ビルドパスを通す。
コピー元はSpring frameworkをダウンロード、展開したフォルダからの相対パス。

jarファイル名コピー元フォルダ
ibatis-2.3.0.677.jarlib/ibatis
commons-dbcp.jarlib/jakarta-commons
commons-pool.jarlib/jakarta-commons
mysql-connector-java-5.0.7-bin.jarMySQLのサイトから入手

テーブルの作成

検索対象となるカテゴリマスタテーブル、商品テーブルを作成する。
DBサーバはMySQLを使用し、springdbという名前のデータベースを作成した。
ここへカテゴリマスタ、商品テーブルを作成する。
ちなみにMySQLのデフォルト文字コードはutf8を指定している。
カテゴリマスタ、商品テーブルのDDLは以下のとおり。

CREATE TABLE `springdb`.`categorys` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `category_name` VARCHAR(100),
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;

CREATE TABLE `springdb`.`products` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `category_id` INTEGER UNSIGNED NOT NULL DEFAULT 1,
  `product_name` VARCHAR(100),
  `description` TEXT,
  `image_url` VARCHAR(200),
  `price` DECIMAL(10),
  PRIMARY KEY(`id`),
  CONSTRAINT `FK_products_1` FOREIGN KEY `FK_products_1` (`category_id`)
    REFERENCES `category` (`id`)
    ON DELETE RESTRICT
    ON UPDATE RESTRICT
)
ENGINE = InnoDB;

ここへ適当なデータを登録しておく。
データの登録は"MySQL Query Browser"と、LOADコマンドを使用した。

LOADコマンドを使用した登録例を示す。
まずテキストエディタで以下の内容のファイルを作成する。
文字コードをテーブルの文字コードと同じに指定し保存する。

categors.csv
1,その他
2,野菜
3,くだもの

"MySQL Command Line Client"を起動し、
以下のコマンドを実行する。

load data infile 'c:/ファイルのあるフォルダ/categorys.csv' into table categorys fields terminated by ',' lines terminated by '\r\n';

同様に、products.csvを以下のような内容で作成し、LOADコマンドを使用して商品テーブルにデータを登録する。

products.csv
1,2,キュウリ,キュウリです。,,100
2,2,にんじん,にんじんです。,,100
3,2,さつまいも,さつまいもです。,,100
4,2,ジャガイモ,ジャガイモです。,,100
5,2,トマト,トマトです。,,100
6,1,バナナ,バナナです。,,100

商品クラスの作成

検索結果を格納する商品クラスを作成する。
クラス名はProductとする。
内容は以下のとおり。

WEB-INF/src/com/ziqoo/biz/product/Product.java
package com.ziqoo.product;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 *
 */
public class Product implements Serializable {
	private Integer id;
	private Integer categoryId;
	private String category_name;
	private String product_name;
	private String description;
	private String image_url;
	private BigDecimal price;
	
	public String getCategory_name() {
		return category_name;
	}
	public void setCategory_name(String category_name) {
		this.category_name = category_name;
	}
	public Integer getCategoryId() {
		return categoryId;
	}
	public void setCategoryId(Integer categoryId) {
		this.categoryId = categoryId;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getImage_url() {
		return image_url;
	}
	public void setImage_url(String image_url) {
		this.image_url = image_url;
	}
	public BigDecimal getPrice() {
		return price;
	}
	public void setPrice(BigDecimal price) {
		this.price = price;
	}
	public String getProduct_name() {
		return product_name;
	}
	public void setProduct_name(String product_name) {
		this.product_name = product_name;
	}
}

SQLマップファイルの作成

データベースアクセスにはiBATISを使用するので、
SQLマップファイルを作成する。
その前に、商品検索のSQLを考える。

SELECT
  p.id
 ,P.category_id
 ,c.category_name
 ,p.product_name
 ,p.description
 ,p.image_url
 ,p.price
FROM products p
 JOIN categorys c ON c.id = p.category_id
ORDER BY p.category_id, p.id

これに検索条件に合わせたWHERE句を追加すればよい。
検索キーは
p.id, p.category_id, p.product_name
を指定するつもりなので、SQLマップは以下のようになる。
WEB-INF/src/mapsフォルダを作成し、products.xmlというファイル名で配置する。

WEB-INF/src/maps/products.xml
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//E" "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap namespace="products">

    <resultMap id="resultProducts" class="com.ziqoo.biz.product.Product">
        <result property="id" column="id" />
        <result property="categoryId" column="category_id" />
        <result property="productName" column="product_name" />
        <result property="description" column="description" />
        <result property="imageUrl" column="image_url" />
        <result property="price" column="price" />
    </resultMap>
    
    <select id="selectProducts" parameterClass="com.ziqoo.biz.product.Product" resultMap="resultProducts">
        SELECT
          p.id
         ,p.category_id
         ,c.category_name
         ,p.product_name
         ,p.description
         ,p.image_url
         ,p.price
        FROM products p
         JOIN categorys c ON c.id = p.category_id
        <dynamic prepend="where">
             <isNotEmpty prepend="and" property="id">
                 p.id = #id#
             </isNotEmpty>
             <isNotEmpty prepend="and" property="categoryId">
                 p.category_id = #categoryId#
             </isNotEmpty>
             <isNotEmpty prepend="and" property="productName">
                 p.product_name = #productName#
             </isNotEmpty>
        </dynamic>
        ORDER BY p.category_id, p.id
    </select>
</sqlMap>

iBATIS用設定ファイルの作成

iBATIS用の設定ファイルをsqlmap-config.xmlという名前でWEB-INFの下に作成する。
内容は以下のとおり。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
    <sqlMap resource="maps/products.xml"/>
</sqlMapConfig>

また、ログが出力されるようにlog4j.propertiesを以下のように修正する

WEB-INF/src/log4j.properties
# ログファイルの設定
log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n

# SqlMap logging configuration...
log4j.logger.com.ibatis=DEBUG
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG

データソースの登録

SpringからiBATISを使えるようにするため、applicationContext.xmlに
データソースとsqlMapClientの設定を追加する。

WEB-INF/applicationContext.xm
<beans>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:mysql://localhost/springdb</value>
        </property>
        <property name="username">
            <value>root</value>
        </property>
    </bean>
    
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="configLocation" value="/WEB-INF/sqlmap-config.xml"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>

    --- 以下省略 ---

DAOクラスの作成

商品テーブルを検索するDAOクラスを作成する。
クラス名はProductDAOとする。
内容は以下のとおり。

WEB-INF/src/com/ziqoo/dao/ProductDao.java
package com.ziqoo.dao;

import java.util.List;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

import com.ziqoo.biz.product.Product;

public class ProductDao extends SqlMapClientDaoSupport {

	@SuppressWarnings("unchecked")
	public List find(Product product){
		return getSqlMapClientTemplate().queryForList("selectProducts", product);
	}
}

ビジネスロジックの作成

ProductDaoクラスを使用して商品情報を取得する商品マネジャクラスを作成する。
クラス名はProductManager、内容は以下のとおり。

WEB-INF/src/com/ziqoo/biz/product/ProductManager.java
package com.ziqoo.biz.product;

import java.util.List;

import com.ziqoo.dao.ProductDao;

public class ProductManager {
	private ProductDao prodDao;
	
	public List getProductList(Product product){
		return prodDao.find(product);
	}

	public ProductDao getProdDao() {
		return prodDao;
	}

	public void setProdDao(ProductDao prodDao) {
		this.prodDao = prodDao;
	}
}

このクラスはProductDaoをプロパティとして持っている。
そしてこのProductDaoを使用して商品情報を取得する。
ProductDaoのインスタンスの生成と、プロパティへの設定は
Springで行うので、そのための設定をapplicationContext.xmlに
追記する。

WEB-INF/applicationContext.xml
    --- 省略 ---
    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="configLocation" value="/WEB-INF/sqlmap-config.xml"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="productDao" class="com.ziqoo.dao.ProductDao">
        <property name="sqlMapClient" ref="sqlMapClient"/>
    </bean>

    <bean id="productManager" class="com.ziqoo.biz.product.ProductManager">
        <property name="prodDao" ref="productDao"/>
    </bean>

    --- 省略 ---

コマンドクラスの作成

検索キー入力画面のFORMに対応するクラスを作成する。
つまり、画面のFORMに表示する初期値や入力値を保持する。
このクラスのインスタンスが、サブミット時にcommandとして送られてくる。
内容は以下のとおり。

WEB-INF/src/com/ziqoo/web/command/ProductSearch.java
package com.ziqoo.web.command;

public class ProductSearch implements Serializable{
	private Integer id;
	private Integer categoryId;
	private String productName;
	
	public Integer getCategoryId() {
		return categoryId;
	}
	public void setCategoryId(Integer categoryId) {
		this.categoryId = categoryId;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getProductName() {
		return productName;
	}
	public void setProductName(String productName) {
		this.productName = productName;
	}
}

コントローラクラスの作成

コントローラクラスはSpringFrameworkのControllerインターフェースを
実装すればよいが、Springで、すでに実装済みのコントローラクラスが幾つか用意されている。
なので、用途に応じてこれらを継承すればよい。
ここで作成するコントローラはFORMを持つ画面を担当するので、
SimpleFormControllerを継承して作成する。
クラス名はProductSearchController、内容は以下のとおり。

WEB-INF/src/com/ziqoo/web/controller/ProductSearchController.java
package com.ziqoo.web.controller;

import java.util.List;

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

import com.ziqoo.biz.product.Product;
import com.ziqoo.biz.product.ProductManager;
import com.ziqoo.web.command.ProductSearch;

public class ProductSearchController extends SimpleFormController {
	private ProductManager productMan;

	@Override
	protected ModelAndView onSubmit(Object command) throws Exception {
		ProductSearch productSearch = (ProductSearch)obj;
		
		Product product = new Product();
		product.setId(productSearch.getId());
		product.setCategoryId(productSearch.getCategoryId());
		product.setProductName(productSearch.getProductName());
		
		List pList = productMan.getProductList(product);
		
		return new ModelAndView(getSuccessView(), "pList", pList);
	}
	
	public ProductManager getProductMan() {
		return productMan;
	}

	public void setProductMan(ProductManager productMan) {
		this.productMan = productMan;
	}
}

このクラスは検索キー入力画面の表示と検索ボタンが押されたときの
処理を行う。
表示の処理はGETのリクエストがあった場合行われるが、
実装については設定ファイルでビューを指定するだけで、
今回の場合は、特に必要はない。
サブミットの処理はPOSTのリクエストがあった場合に行われ、
これはonSubmitメソッドをオーバーライドして実装する。
引数のcommandとして画面の入力値を保持したProductSearchが
渡される。
また、商品情報を取得するProductManagerクラスのインスタンスを
プロパティとして持たせているので、これを使って商品情報を取得し、
商品一覧のJSP名と商品情報を

return new ModelAndView(getSuccessView(), "pList", pList);
のように戻り値として返す。
ここで、商品一覧のJSP名は直接記述せず。プロパティsuccessViewから
取得する。この値は設定ファイルで指定する。

コントローラの登録

springapp-servlet.xmlファイルへコントローラクラスの設定と
コントローラへのURLのマッピングの設定を記述する。

WEB-INF/springapp-servlet.xml
    --- 省略 ---

    <bean id="productSearchController" class="com.ziqoo.web.controller.ProductSearchController">
        <property name="sessionForm"><value>true</value></property>
        <property name="commandName"><value>productSearch</value></property>
        <property name="commandClass"><value>com.ziqoo.web.command.ProductSearch</value></property>
        <property name="formView"><value>productSearch</value></property>
        <property name="successView"><value>productList</value></property>
        <property name="productMan">
            <ref bean="productManager"/>
        </property>
    </bean>

    <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/menu.htm">menuController</prop>
                <prop key="/productSearch.htm">productSearchController</prop>
            </props>
        </property>
    </bean>
    --- 省略 ---

ここでJSPファイルWEB-INF/jsp/productList.jspは
設定ファイルでviewResolverを設定しているので、パスと拡張子を省略し
"productList"と書くことが出来る。

ビューの作成

検索キー入力画面のJSPは以下のとおり。

WEB-INF/jsp/productSearch.jsp
<%@page pageEncoding="Shift_JIS" contentType="text/html; charset=Shift_JIS" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
    <link rel="stylesheet" type="text/css" href="css/main.css" >
    <title>springapp</title>
</head>
<body>
    <h3>商品検索</h3>
    <hr>
    <form:form commandName="productSearch">
        <table>
            <tr>
                <th>商品番号</th>
                <td><form:input path="id" /></td>
            </tr>
            <tr>
                <th>カテゴリ番号</th>
                <td><form:input path="categoryId" /></td>
            </tr>
            <tr>
                <th>商品名</th>
                <td><form:input path="productName" /></td>
            </tr>
        </table>
        <br>
        <input type="submit" value="検索" />
    </form:form>
    <hr>
    <a href="<c:url value="menu.htm"/>">メニュー</a>
</body>
</html>

ここでcommandNameとして、設定ファイルで指定したcommandNameを
pathとして値をバインドしたいProductSearchクラスのプロパティ名を指定する。

検索結果一覧のJSPは以下のとおり。

<%@page pageEncoding="Shift_JIS" contentType="text/html; charset=Shift_JIS" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"/>
    <link rel="stylesheet" type="text/css" href="css/main.css" >
    <title>springapp</title>
</head>
<body>
    <h3>商品一覧</h3>
    <hr>
    <table border="1">
    <tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>
    <c:forEach items="${pList}" var="prod">
        <tr>
            <td><c:out value="${prod.id}"/></td>
            <td><c:out value="${prod.productName}"/></td>
            <td align="right">\<c:out value="${prod.price}"/></td>
        </tr>
    </c:forEach>
    </table>
    <hr>
    <a href="<c:url value="menu.htm"/>">メニュー</a> 
    <a href="<c:url value="productSearch.htm"/>">商品検索</a> 
</body>
</html>

最後にサンプル1で作成したメニューから検索画面を呼び出せるように
applicationContext.xmlを以下のように修正する。

WEB-INF/applicationContext.xml
    --- 省略 ---
    <bean id="menuItem1" class="com.ziqoo.biz.menu.MenuItem">
        <property name="itemName"><value>商品検索</value></property>
        <property name="href"><value>productSearch.htm</value></property>
    </bean>
        
    <bean id="menuItem2" class="com.ziqoo.biz.menu.MenuItem">
        <property name="itemName"><value>項目2</value></property>
        <property name="href"><value>/item2.htm</value></property>
    </bean>

    <bean id="menuItem3" class="com.ziqoo.biz.menu.MenuItem">
        <property name="itemName"><value>項目3</value></property>
        <property name="href"><value>/item3.htm</value></property>
    </bean>
    --- 省略 ---

文字化け対応

今のままだと、画面から日本語を入力すると文字化けしてしまうので、
web.xmlにフィルタの設定を追加する。

WEB-INF/web.xml
    --- 省略 ---
<web-app>
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>Shift_JIS</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
            
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    --- 省略 ---

動作確認

ブラウザよりhttp://localhost:8080/springapp/へアクセスし、メニュー画面が表示されることを確認する。

商品検索を選択。

適当な検索キーを入力し、検索ボタンを押す。

検索結果が表示される。

各テーブルの内容は以下のとおり。

カテゴリテーブル(categorys)

商品テーブル(products)


[CONTENTSへ戻る]