■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2008年03月02日

    楽しいJava講座 - 初心者から達人へのパスポート
                  vol.094

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.Tomcatのアプリケーション開発
========================================================


今日は下記の部分をプログラミングしましょう。
--------------------------------------------------------
(5) 商品の発送先(= ユーザーの住所・氏名)が表示され、購入
(注文)を確定するかどうか聞いてくる。
[「購入(注文確定)する」ボタンをクリックする
 → CompleteOrderServlet.java → completeOrder.jsp]

(6) 「注文を受け付けました。」のメッセージが表示される。
(これで終わり。)
--------------------------------------------------------


まず、CompleteOrderServletクラスのプログラミングですが、ここでは、
注文情報をデータベースに登録し、不要になったセッション・オブジェクト
を削除(無効化)する、という処理を行います。

このために、ORDERHEADERテーブルとORDERITEMテーブルに注文情報
を書き込むためのクラスを作成しておきます。

なお、注文が確定したものに対して、顧客から代金が振り込まれると、
商品の発送へと進むことになります。この時点で商品の在庫が無いという
ことになっては困りますから、データベースに注文情報を書き込む前には
ITEMテーブルの在庫数をチェックしておき、
注文された個数を満たすだけの在庫があることを確認しておかなければ
なりません。
もし、在庫数が足りない場合は、在庫切れで注文できないことを顧客に
知らせる必要があります。あるいは、仕入れ待ちで発送に時間がかかって
もよいかどうかを顧客に問い合わせ、それでもかまわないということであ
れば、注文を確定させる、というやり方もあります。

また、本来ならば、注文の確定時には、在庫の商品のうち、注文への引き
当て分を別扱いにし、ITEMテーブルの在庫数から引いておくべきです。

しかし、今回のアプリケーションでは、話を簡潔にするために、そこら
へんの細かい機能は省略いたします。



では、ORDERHEADERテーブルとORDERITEMテーブルに注文情報を書き込む
ためのクラスを作成しましょう。下記のようにOrderDbManagerという
クラス名で作成することにしましょう。
必要な情報はOrderというクラスで持たせるようにしていましたから、
Orderオブジェクトからデータを取り出してテーブルに書き込ませる
ことにします。
--------------------------------------------------------
package jp.co.flsi.lecture.webapp.db;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;

import jp.co.flsi.lecture.webapp.entity.Order;
import jp.co.flsi.lecture.webapp.entity.OrderItem;

public class OrderDbManager extends DbManager {
   private String selectMaxONumSql = "SELECT MAX(O_NUM) FROM ORDERHEADER";
   private String insHeaderSql = "INSERT INTO ORDERHEADER  VALUES (?, ?, ?, ?, ?)";
   private String insItemSql = "INSERT INTO ORDERITEM  VALUES (?, ?, ?, ?)";

   public void insertData(Order anOrder) {
      try {
         connect();
         Statement selectSt = conn.createStatement();
         PreparedStatement insHeaderPs = conn.prepareStatement(insHeaderSql);
         PreparedStatement insItemPs = conn.prepareStatement(insItemSql);
         ResultSet rs = selectSt.executeQuery(selectMaxONumSql);
         int newOrderNumber = 1;
         if (rs.next()) newOrderNumber = rs.getInt(1) + 1;
         insHeaderPs.setInt(1, newOrderNumber);
         insHeaderPs.setString(2, anOrder.getCustomerNum());
         insHeaderPs.setDate(3, new java.sql.Date((new Date()).getTime()));
         insHeaderPs.setInt(4, 0);  // 未払い
         insHeaderPs.setInt(5, 0);  // 未配達
         insHeaderPs.executeUpdate();
         int seqNum = 1;
         for (OrderItem oi : anOrder.getOrderItems()) {
            insItemPs.setInt(1, newOrderNumber);
            insItemPs.setInt(2, seqNum);
            insItemPs.setString(3, oi.getNum());
            insItemPs.setInt(4, oi.getOrderQuantity());
            insItemPs.executeUpdate();
            seqNum++;
         }
         selectSt.close();
         insHeaderPs.close();
         insItemPs.close();
         conn.commit();
         disconnect();
      }
      catch (SQLException exception) {
         exception.printStackTrace();
      }
      catch (Exception otherException) {
         otherException.printStackTrace();
      }
   }
  
}
--------------------------------------------------------

このソース・コードの中で

private String selectMaxONumSql = "SELECT MAX(O_NUM) FROM ORDERHEADER";

という行の中のSQL文で使われているMAXというのは、カラムの最大値を
取り出すための関数で、たとえば、ここではORDERHEADERテーブルの
O_NUMカラム(注文番号のカラム)の値を全行に渡って調べ、そのうちの
最大値を取り出してくれます。
新しい注文番号を割り振るためには、既存の注文番号の最大値を調べて、
それに1を加えてやればいいので、上記のSQL文で既存の最大値を取り出し、

if (rs.next()) newOrderNumber = rs.getInt(1) + 1;

という行によって、取り出した最大値に1を加えるようにしているのです。
ただし、一番最初の注文のときはORDERHEADERテーブルは空っぽですから、
上記のrs.next()にはfalseが返ってきて、newOrderNumberの値は初期値(1)
のままになりますね。

あと、

insHeaderPs.setDate(3, new java.sql.Date((new Date()).getTime()));

という行は、現在の日付を注文日としてORDERHEADERテーブルのO_DATEカラム
に書き込むためのものですが、Dateというクラス名が2回出てきているので
紛らわしいかも知れません。この場合、java.sql.Dateというようにjava.sql
で修飾してあるほうは、いうまでもなくjava.sqlパッケージのDateクラスです
が、(new Date())というように修飾していないほうのDateクラスは先頭の
import文で

import java.util.Date;

というように指定されているほう、つまりjava.utilパッケージのDateクラスに
なります。
データベースの日付データとしてはjava.sqlパッケージのDateクラスが使われ
ますが、なぜjava.utilパッケージのDateクラスも使ったかというと、
java.sqlパッケージのDateクラスには現在の日付を生成してくれるコンストラ
クターがないからです。
代わりにjava.utilパッケージのDateクラスのデフォルト・コンストラクターは
現在の日付を生成してくれます(あるいは、vol.048のようにGregorianCalendar
のコンストラクターを利用してもよい)。

あとのソース・コードはもう説明しなくても理解できますね。



では、続いて下記のようにCompleteOrderServletクラスを
作成しましょう。
--------------------------------------------------------
package jp.co.flsi.lecture.webapp.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jp.co.flsi.lecture.webapp.db.OrderDbManager;
import jp.co.flsi.lecture.webapp.entity.Customer;
import jp.co.flsi.lecture.webapp.entity.Order;

public class CompleteOrderServlet extends HttpServlet {
   public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
      req.setCharacterEncoding("Shift_JIS");
      HttpSession session = req.getSession();
      Order anOrder = (Order)session.getAttribute("ORDER");
      Customer aCustomer = (Customer)session.getAttribute("CUSTOMER");
      if (anOrder == null || aCustomer == null) {
         getServletContext().getRequestDispatcher("/ordermissing.html").forward(req,res);
      }
      else {
         anOrder.setCustomerNum(aCustomer.getNum());
         OrderDbManager orderDb = new OrderDbManager();
         orderDb.insertData(anOrder);
         getServletContext().getRequestDispatcher("/completeOrder.jsp").forward(req,res);
      }
   }

}
--------------------------------------------------------
このソース・コードは説明不要ですね。



では、このサーブレットを登録すべく、web.xmlを下記のように編集しま
しょう。
--------------------------------------------------------
<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   version="2.5">

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>jp.flsi.lecture.servlet.TestServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>TestServlet2</servlet-name>
        <servlet-class>jp.flsi.lecture.servlet.TestServlet2</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>AgeCalcServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.AgeCalcServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>CustomerServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.CustomerServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>ItemServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.ItemServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>OrderItemServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.OrderItemServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>OrderProcessServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.OrderProcessServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>ConfirmOrderServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.ConfirmOrderServlet</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>CompleteOrderServlet</servlet-name>
        <servlet-class>jp.co.flsi.lecture.webapp.servlet.CompleteOrderServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/servlet/test</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>TestServlet2</servlet-name>
        <url-pattern>/servlet/test2</url-pattern>
    </servlet-mapping>
   
    <servlet-mapping>
        <servlet-name>AgeCalcServlet</servlet-name>
        <url-pattern>/servlet/age</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>CustomerServlet</servlet-name>
        <url-pattern>/servlet/customers</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ItemServlet</servlet-name>
        <url-pattern>/servlet/items</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>OrderItemServlet</servlet-name>
        <url-pattern>/servlet/orderitems</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>OrderProcessServlet</servlet-name>
        <url-pattern>/servlet/orderprocess</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>ConfirmOrderServlet</servlet-name>
        <url-pattern>/servlet/confirmorder</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>CompleteOrderServlet</servlet-name>
        <url-pattern>/servlet/completeorder</url-pattern>
    </servlet-mapping>
   
</web-app>
--------------------------------------------------------



続いて、下記のようなcompleteOrder.jspファイルを作成しましょう。
--------------------------------------------------------
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="Shift_JIS" %>

<HTML>
<HEAD>
<TITLE>注文完了</TITLE>
</HEAD>
<BODY bgcolor="#77ffff" text="#fa5a00">
<H1>下記のご注文を受け付けました。</H1>
<BR>
<BR>
<TABLE border="1" width="100%">
  <TBODY>
    <TR>
      <TH>商品番号</TH>
      <TH>商品名</TH>
      <TH>価格</TH>
      <TH>カテゴリー</TH>
      <TH>購入個数</TH>
    </TR>

<%@ page import="jp.co.flsi.lecture.webapp.entity.*" %>
<% int totalPrice = 0; %>
<jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="session" />
<% OrderItem anOrderItem;
for (int i = ORDER.getOrderItems().size() - 1; i >= 0 ; i--) {
anOrderItem = ORDER.getOrderItems().elementAt(i); %>
<TR>
      <TD><%= anOrderItem.getNum() %></TD>
      <TD><%= anOrderItem.getName() %></TD>
      <TD><%= anOrderItem.getPrice() %>円</TD>
      <TD><%= anOrderItem.getCategory() %></TD>
      <TD align="right"><%= anOrderItem.getOrderQuantity() %></TD>
    </TR>
<% totalPrice += anOrderItem.getPrice() * anOrderItem.getOrderQuantity();
} %>

  </TBODY>
</TABLE>
<BR>
<TABLE border="1">
    <TR>
      <TH>合計金額:</TH>
      <TH><%= totalPrice %>円</TH>
    </TR>
</TABLE>
<BR>
<BR>
下記の口座に代金をお振込みください。お振込み後に商品の発送手続きにはいります。
<BR>
<TABLE border="0">
    <TR>
      <TH>INTSHOP銀行</TH>
      <TH>XYZ支店</TH>
      <TH>普通預金口座</TH>
      <TH>1234567</TH>
    </TR>
</TABLE>
<BR>
<BR>
ご注文ありがとうございました。
<% session.invalidate(); %>
<BR>
<BR>
<A href="../itemSelect.html">
商品の検索ページに戻るには、ここをクリック
</A>
<BR>
</BODY>
</HTML>
--------------------------------------------------------

このソース・コード中で

<% session.invalidate(); %>

というのは、セッション・オブジェクトを無効化するためのコードです。
JSPの中でsessionと書くとセッション・オブジェクトを表すことになっています。
invalidate()というメソッドはvol.090で紹介したセッション・オブジェクトの
メソッドです。
注文が確定したので、セッションが終わったということでセッション・オブジェ
クトを無効化するのです。



では、またテストしてみましょう。

EclipseでTomcatを起動し、Webブラウザーを起動して、下記のURLを
入力してください。

http://localhost:8080/JStudy2/itemSelect.html

(1) 「キーワード」欄、「カテゴリー」欄ともに何も入力せずに
「送信」ボタンをクリックしてみてください。

(2) あとはいつもと同じように操作し、注文確定まで進んでください。

(3) 最後に、「下記のご注文を受け付けました。」のページになったら、
注文が完了です。
DBViewerでデータベースのORDERHEADERテーブルとORDERITEMテーブルに
注文情報が書き込まれていることを確認してください。



どうですか?うまくいきましたか。


ところで、このアプリケーションでは、やたらにサーブレットがたくさん
作られてゴチャゴチャしてきましたね。
それにweb.xmlの中にもサーブレットがたくさん記述されて、なんだか
ゴチャゴチャしてうっとうしいですね。

これまでのようにサーブレットをたくさん作ってやると、サーブレット→JSP
→サーブレット→JSPサーブレット→JSPという流れが理解しやすくてよいの
ですが、実は実用的なアプリケーションではこのような作り方はしません。

では、どのような作り方をするかというと、それは次回以降にお話します。


(次回に続く)


では、今日はここまでにします。

何か、わからないところがありましたら、下記のWebページまで質問を
お寄せください。



========================================================
◆ 02.演習問題
================================================

上のcompleteOrder.jspのソース・コードの中で

--------------------------------------------------------
<% session.invalidate(); %>
--------------------------------------------------------

の行が無いとどういうことになるか、考えてみてください。

ヒント:セッション・オブジェクトがそのまま生き続けますから、
注文が完了したあとに商品の検索ページに戻って別の注文をしよう
としたときにも、セッション・オブジェクトが新規のものではなく
以前のままになってしまいますね。そうすると新しい注文をしよ
うとしても・・・・。

実際にテストして、どういうことになるか、確認してみてください。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2008 Future Lifestyle Inc. 不許無断複製