そのへんのちらしのうら

調べたこと、学んだこと、おもしろかったこと。

JSP+ServletでシンプルなWebアプリをつくる(7)ー明細部に入力した値でDB更新する

3.開発

3.7.明細部に入力した値でDB更新する


検索して表示した在庫情報に対し、明細部に入力した値でテーブルを更新します。
明細部の情報を1行ずつではなく複数行分まとめてリクエストして処理するには、ちょっとした工夫が必要です。(このあたり、自分が探してみた限りでは一般的な参考書等にあまり掲載されていないことが多い気がします)

①Daoクラスに更新処理を実装する
Daoクラスに更新メソッドを追加しました。(ついでにコネクション取得をメソッドとして切り出しました)
ZaikoChoseiDao.java

package dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import entity.Zaiko;


public class ZaikoChoseiDao {

    /**
    * DBコネクションを取得する
    * @return
    * @throws ClassNotFoundException
    * @throws SQLException
    */
    private Connection getDBConnection() throws ClassNotFoundException, SQLException {
        //JDBCドライバを読み込み
        Class.forName("oracle.jdbc.driver.OracleDriver");
        //データベースへ接続 jdbc:oracle:thin:@[ホスト名]:[ポート番号]:[データベース名]
        return DriverManager.getConnection("[ホスト名]:[ポート番号]:[データベース名]", "[ユーザー]", "[パスワード]");
    }

    /**
    * 在庫検索
    * @param location
    * @param itemcd
    * @return
    */
    public List<Zaiko> selectByWhere(String location, String itemcd) {

        Connection conn = null;
        List<Zaiko> resultList = new ArrayList<Zaiko>();

        try {

            conn = getDBConnection();

            String sql =
                      "SELECT"
                    + "\r\n T1.LOCATION"
                    + "\r\n,T1.ITEMCD"
                    + "\r\n,T1.LOT"
                    + "\r\n,T1.QUANTITY"
                    + "\r\nFROM ZAIKO T1"
                    + "\r\nWHERE 1=1";
            if (location != null && !location.isEmpty()) {
                sql = sql.concat("\r\nAND T1.LOCATION = '" + location + "'");
            }
            if (itemcd != null && !itemcd.isEmpty()) {
                sql = sql.concat("\r\nAND T1.ITEMCD = '" + itemcd + "'");
            }

            PreparedStatement ps = conn.prepareStatement(sql);

            ResultSet rs = ps.executeQuery();

            while (rs.next()) {
                Zaiko zaiko = new Zaiko(
                     rs.getString("LOCATION")
                    ,rs.getString("ITEMCD")
                    ,rs.getString("LOT")
                    ,rs.getInt("QUANTITY")
                    );
                resultList.add(zaiko);
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
            return null;
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
            return null;
        } finally {
            //データベース接続切断
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                    return null;
                }
            }
        }
        return resultList;
    }

    /**
    * 在庫更新(PK)
    * @param location
    * @param itemcd
    * @param lot
    * @param quantity
    * @return
    */
    public int updateByPrimaryKey(String location, String itemcd, String lot, int quantity) {

        Connection conn = null;

        try {

            conn = getDBConnection();

            String sql =
                  "\r\nUPDATE ZAIKO"
                + "\r\nSET"
                + "\r\n QUANTITY = '" + quantity + "'"
                + "\r\nWHERE 1=1"
                + "\r\nAND LOCATION = '" + location + "'"
                + "\r\nAND ITEMCD = '" + itemcd + "'"
                + "\r\nAND LOT = '" + lot + "'"
                ;

            PreparedStatement ps = conn.prepareStatement(sql);

            return ps.executeUpdate(sql);

        } catch (SQLException ex) {
            ex.printStackTrace();
            return 0;
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
            return 0;
        } finally {
            //データベース接続切断
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

}


②Modelクラスに更新ロジックを追加
更新メソッドのみ下記に抜粋します。
ZaikoChoseiModel.java

   /**
    * フォームをもとに在庫更新する
    * @param form
    * @return
    */
    public ZaikoChoseiForm update(ZaikoChoseiForm form){

        List<ZaikoChoseiDetail> detail = form.getDetail();
        int resCnt = 0;

        for (int i = 0; i < detail.size(); i++)
        {
            ZaikoChoseiDetail row = detail.get(i);

            ZaikoChoseiDao dao = new ZaikoChoseiDao();
            resCnt += dao.updateByPrimaryKey(
                    row.getLocation(), row.getItemcd(), row.getLot(), Integer.parseInt(row.getQuantity()));
        }

        System.out.println("更新件数=" + resCnt);
        return form;
    }


③Controllerクラスに更新処理の呼び出し実装
画面で押したボタン(=モード)に応じ、Modelクラスのメソッドを呼び出すようにします。ここでは更新ボタン押下時の処理を追加しました。
なお、更新後には再検索しています。
ZaikoChoseiController.java(抜粋)

   /**
    * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
    */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        request.setCharacterEncoding("UTF-8");//リクエストボディのエンコーディング指定で文字化け回避
        response.setContentType("text/html; charset=UTF-8");

        try {

            ZaikoChoseiModel model = new ZaikoChoseiModel();

            //モード判定
            if (null != request.getParameter("SRH")) {
                //検索
                ZaikoChoseiForm form = model.search(setForm(request));
                //SetAttributeする
                getForm(form, request, response);
                RequestDispatcher dispatcher
                    = request.getRequestDispatcher("/WEB-INF/jsp/zaikochosei.jsp");
                dispatcher.forward(request, response);
            } else if (null != request.getParameter("UPD")) {
                //実行
                ZaikoChoseiForm form = model.update(setForm(request));
                //再検索
                form = model.search(form);
                //SetAttributeする
                getForm(form, request, response);
                RequestDispatcher dispatcher
                    = request.getRequestDispatcher("/WEB-INF/jsp/zaikochosei.jsp");
                dispatcher.forward(request, response);
            }

        } catch (Exception ex) {
            //TODO エラーページ
            response.getWriter().append("ERROR:"
                    + ex.getMessage()).append(request.getContextPath());
        }
    }


④リクエストパラメータから明細部情報を取り出す

setFormメソッドを下記のように書き換えます。

前回、jspファイルで明細部のnameにインデックスをつけました。
リクエストパラメータには、たとえばロケーションの項目であれば、「location[0]=01010101」「location[1]=01010102」…とインデックス付の文字列としてセットされるため、アプリケーション側ではこれを分解して利用する必要があります。それを下記の実装で実現しています。

この実装により、前回までは1回検索したあとに再度検索ボタンを押すとエラーとなっていましたが(インデックス付の文字列と一致するプロパティがないため)、今回からは何度ボタンを押してもエラーにならないようになっています。
ZaikoChoseiController.java(抜粋)

   /**
    * リクエストパラメータを画面フォームに設定するメソッド
    * @param request
    * @return
    * @throws IntrospectionException
    * @throws IllegalAccessException
    * @throws IllegalArgumentException
    * @throws InvocationTargetException
    */
    private ZaikoChoseiForm setForm(HttpServletRequest request)
            throws IntrospectionException, IllegalAccessException
                , IllegalArgumentException, InvocationTargetException {

        //画面Form生成
        ZaikoChoseiForm form = new ZaikoChoseiForm();

        //リクエストパラメータのnameとvalueを保持するマップ
        Map<String, String[]> paramMap = request.getParameterMap();
        //インデックスをキーに明細行の情報を保持するマップ
        Map<Integer, ZaikoChoseiDetail> detailMap
            = new TreeMap<Integer, ZaikoChoseiDetail>();

        //リクエストパラメータを順に取り出す
        for (String key : paramMap.keySet()) {

            if ("SRH".equals(key) || "UPD".equals(key)) {
                continue;
            }

            String[] val = paramMap.get(key);
            String _key;
            if (0 > key.indexOf("[")) {
                //明細部でない場合

                //リクエストパラメータのnameの設定
                _key = key;
                //プロパティディスクリプタの取得
                PropertyDescriptor prop
                    = new PropertyDescriptor(_key, ZaikoChoseiForm.class);
                Method setter = prop.getWriteMethod();
                setter.invoke(form, val[0]);
            } else {
                //明細部の場合

                //リクエストパラメータのnameの設定(index除く)
                _key = key.substring(0, key.indexOf("["));
                //プロパティディスクリプタの取得
                PropertyDescriptor prop
                    = new PropertyDescriptor(_key, ZaikoChoseiDetail.class);
                //インデックスの取得
                int index = Integer.parseInt(key.substring(
                    (key.indexOf("[") + 1), (key.indexOf("]"))));

                if (detailMap.containsKey(index)) {
                    //すでに処理している明細行の場合

                    //同じインデックスの明細行を取得
                    ZaikoChoseiDetail row = detailMap.get(index);
                    //対象プロパティのセッター取得・実行
                    Method setter = prop.getWriteMethod();
                    setter.invoke(row, val[0]);
                } else {
                    //明細行の生成
                    ZaikoChoseiDetail row = new ZaikoChoseiDetail();
                    //対象プロパティのセッター取得・実行
                    Method setter = prop.getWriteMethod();
                    setter.invoke(row, val[0]);
                    detailMap.put(index, row);
                }
            }

            if (!detailMap.isEmpty()) {
                //formの明細部をセット
                form.setDetail(new ArrayList<ZaikoChoseiDetail>(detailMap.values()));
            }
        }

        return form;
    }


⑤確認
それでは確認してみましょう。
まずは検索して……
f:id:bubox2:20190313230346p:plain f:id:bubox2:20190313230450p:plain 明細部の内容を一部変えます。
f:id:bubox2:20190313230611p:plain 更新ボタン押下すると…...
f:id:bubox2:20190313230821p:plain DBも更新されています。
f:id:bubox2:20190313230832p:plain
これで検索~更新までの一通りの実装はいったん完了です。