DAO クラスと Model クラスを使ってデータベースを操作してみる【MVC モデル】 Part 2

Java

この記事は以下の記事の続きです。

スポンサーリンク

システムを拡張していく

前回の記事で作ったシステムを拡張します。

主に Model クラスと DAO クラスを拡張し、データベースの操作の幅を広げていきます。

とりあえず JSP に入力フィールドを用意

前回作ったままでは、すべてのデータを取得するというたった一つのデータベース操作しかすることができません。

最低限データの追加、変更は実装したいものです。

ということで、以前作った itemList.jsp にアイテム追加とデータ書き換えができる入力フィールドを追加します。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="java.util.ArrayList"%>
<%@ page import="database.ItemBean"%>
<%
	ArrayList<ItemBean> beanList = (ArrayList<ItemBean>) request.getAttribute("beanList");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
	integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
	crossorigin="anonymous">
<title>アイテム一覧</title>
</head>
<body>

	<div class="container">
		<div class="row">
			<h1>アイテム一覧</h1>

			<table class="table">
				<tbody>
					<tr class="bg-dark text-white text-center">
						<th scope="col">アイテムID</th>
						<th scope="col">アイテム名</th>
						<th scope="col">価格</th>
						<th scope="col">在庫数</th>
					</tr>
					<%
						for (ItemBean bean : beanList) {
					%>
					<tr class="text-center">
						<td><%=bean.getId()%></td>
						<td><%=bean.getName()%></td>
						<td><%=bean.getPrice()%> 円</td>
						<td><%=bean.getQuantity()%> コ</td>
					</tr>
					<%
						} // endfor
					%>
				</tbody>
			</table>

		</div>
		<div class="row">

			<form class="col-md-6" action="ManagementServlet" method="post">
				<div class="form-group">
					<label>アイテム名</label>
					<input type="text" name="name" class="form-control">
				</div>
				<div class="form-group">
					<label>価格</label>
					<input type="number" name="price" class="form-control">
				</div>
				<div class="form-group">
					<label>在庫数</label>
					<input type="number" name="quantity" class="form-control">
				</div>
				<button class="btn btn-primary" type="submit" name="addItem">アイテム追加</button>
			</form>

			<form class="col-md-6" action="ManagementServlet" method="post">
				<div class="form-group">
					<label>アイテムID</label>
					<input type="number" name="id" class="form-control">
				</div>
				<div class="form-group">
					<label>アイテム名</label>
					<input type="text" name="name" class="form-control">
				</div>
				<div class="form-group">
					<label>価格</label>
					<input type="number" name="price" class="form-control">
				</div>
				<div class="form-group">
					<label>在庫数</label>
					<input type="number" name="quantity" class="form-control">
				</div>
				<button class="btn btn-primary" type="submit" name="updateItem">アイテムデータ変更</button>
			</form>

		</div>
	</div>

</body>
</html>

見栄えを整えるための CSS クラスが多くなってきて、結構入れ子が複雑です。

今回は Servlet の学習なので、気にせずコピペしてください。とくに div タグは見栄えを整えるために存在する HTML としての意味を持たないタグです。

ここで重要なのはそれぞれの入力フィールドにどのような name 属性が振られているかです。

<div class="form-group">
	<label>価格</label>
	<input type="number" name="price" class="form-control">
</div>

たとえばこの入力フィールドには price という name 属性が振られています。つまり、この入力フィールドに入力された文字列は Servlet に送信されたとき、以下のような記述で取得することができます。

// フォームから送信されるデータは文字列扱いなので、
// たとえ数字であっても一旦 String 型で受け取ります
String s_price = request.getParameter("price");

また、今回はボタンに振られた name も重要になってきます。

アイテムを追加するボタン
<button class="btn btn-primary" type="submit" name="addItem">アイテム追加</button>

アイテムデータを変更するボタン
<button class="btn btn-primary" type="submit" name="updateItem">アイテムデータ変更</button>

Servlet は通常、JSP でどのボタンが押されたのかを認識することはできません。これを判別させるため、JSP側であらかじめ name をつけておいてあげます。

DAO クラスで発行できる SQL を増やす

JSP にデータの追加と変更をするためのフォームを追加したので、DAO にも INSERT,UPDATE 文を扱うメソッドを追加します。

	/**
	 * アイテム名と価格、数量を指定してレコードを追加する
	 *
	 * @param ItemBean bean
	 * @throws SQLException
	 */
	public void insert(ItemBean bean) throws SQLException {
		try {

			// JDBCドライバのロード
			Class.forName("com.mysql.jdbc.Driver");

			// DB 接続
			con = DriverManager.getConnection(url, user, password);

			// SQL文を生成
			ps = con.prepareStatement(
					"INSERT INTO "
					+ "items(name, price, quantity) "
					+ "VALUES(?, ?, ?)");

			ps.setString(1, bean.getName());
			ps.setInt(2, bean.getPrice());
			ps.setInt(3, bean.getQuantity());

			// SQLを実行
			ps.executeUpdate();

		} catch (ClassNotFoundException ce) {

			// JDBCドライバが見つからなかったときの処理
			ce.printStackTrace();
		}
	}

	/**
	 * アイテム名、価格と数量を指定して、特定のIDのレコードを更新する
	 *
	 * @param bean
	 * @throws SQLException
	 */
	public void update(ItemBean bean) throws SQLException {
		try {

			// JDBCドライバのロード
			Class.forName("com.mysql.jdbc.Driver");

			// DB 接続
			con = DriverManager.getConnection(url, user, password);

			// SQL文を生成
			ps = con.prepareStatement(
					"UPDATE "
					+ "items "
					+ "SET name = ?, price = ?, quantity = ? "
					+ "WHERE id = ?");

			ps.setString(1, bean.getName());
			ps.setInt(2, bean.getPrice());
			ps.setInt(3, bean.getQuantity());
			ps.setInt(4, bean.getId());

			// SQLを実行
			ps.executeUpdate();

		} catch (ClassNotFoundException ce) {

			// JDBCドライバが見つからなかったときの処理
			ce.printStackTrace();
		}
	}

今回追加したメソッド以外は省略しています。

以前作った DAO クラスに上記2つのメソッドを追加しました。

ちなみにSQL 文そのものの書き方は以下の記事でまとめています。

今回のソースコードの追記で重要なのは以下のような SQL 実行部分です。

// SQL文を生成
ps = con.prepareStatement(
		"INSERT INTO"
		+ "items(name, price, quantity)"
		+ "VALUES(?, ?, ?)");

ps.setString(1, bean.getName());
ps.setInt(2, bean.getPrice());
ps.setInt(3, bean.getQuantity());

例として INSERT 文をとりあげます。

SQL の文字列の中にはてなマークがあり、その下で bean の中身を set しています。

これは「何個目のはてなに bean の中身をセットするか」を示しています。

SQL の中に直接入力されたデータを入れたりするのはセキュリティ上危険であり「SQL インジェクション」という攻撃を受ける可能性があります。

なので View からの入力によって変化する可能性のあるデータ(変数)はそのまま SQL の文字列に入れず、必ず Connection.prepareStatement の setString / setInt を噛ませてください。

また、SELECT 文のような返り値がある SQL 文を実行する際は executeQuery メソッドを使って SQL を実行しますが、INSERT や UPDATE などを返り値がない SQL を実行する場合は、executeUpdate メソッドを使用します。

ps.executeUpdate();

Model を拡張して実行できる処理を追加

次に Model クラスを拡張します。

主に上記で追加した SQL 実行メソッドを扱えるようにします。

	/**
	 * items テーブルにアイテムのデータを追加する
	 *
	 * @param name
	 * @param price
	 * @param quantity
	 */
	public void addItem(String name, int price, int quantity) {
		// データベース接続をするために DAO をインスタンス化
		ItemsDao dao = new ItemsDao();

		// bean を生成
		ItemBean bean = new ItemBean();

		bean.setName(name);
		bean.setPrice(price);
		bean.setQuantity(quantity);

		try {
			// bean のデータをテーブルに追加
			dao.insert(bean);
		} catch (SQLException e) {
			// 例外処理
			e.printStackTrace();
		} finally {
			// データベースから切断
			dao.close();
		}

	}


	public void updateItem(int id, String name, int price, int quantity) {
		// データベース接続をするために DAO をインスタンス化
		ItemsDao dao = new ItemsDao();

		// bean を生成
		ItemBean bean = new ItemBean();

		bean.setId(id);
		bean.setName(name);
		bean.setPrice(price);
		bean.setQuantity(quantity);

		try {
			// bean のデータをもとに情報を変更
			dao.update(bean);
		} catch (SQLException e) {
			// 例外処理
			e.printStackTrace();
		} finally {
			// データベースから切断
			dao.close();
		}

	}

今回追加したメソッド以外は省略しています。

どちらも bean を作成した情報を DAO に投げて、データベースアクセスに関する処理は DAO に任せています。

JSP に合うように Servlet を拡張する

JSP に入力フォームを追加したことで、 JSP から Servlet に対しておこなわれる要求が増えました。

これに対応するため、Servlet 側も整えていきます。

今回は データの送受信のため、doPost メソッドに追記していきます。

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {


		request.setCharacterEncoding("UTF-8");

		// 処理クラスをインスタンス化
		ManagementModel model = new ManagementModel();

		// フォームから送信されるデータは文字列扱いなので、
		// 一旦 String 型で受け取ります
		String s_id = request.getParameter("id");
		String name = request.getParameter("name");
		String s_price = request.getParameter("price");
		String s_quantity = request.getParameter("quantity");

		// 変数を初期化
		String msg = null;
		int id = 0;
		int price = 0;
		int quantity = 0;

		// int に変換
		try {
			price = Integer.parseInt(s_price);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}

		try {
			quantity = Integer.parseInt(s_quantity);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}

		// 押されたボタンを判定して分岐
		if (request.getParameter("addItem") != null) {
			// アイテム追加ボタンを押された場合
			model.addItem(name, price, quantity);
			msg = "アイテムを追加しました。";

		} else if(request.getParameter("updateItem") != null){
			// データ更新ボタンを押された場合
			try {
				id = Integer.parseInt(s_id);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			}

			model.updateItem(id, name, price, quantity);
			msg = "アイテム情報を更新しました。";
		}

		request.setAttribute("msg", msg);

		// アイテム一覧画面に移動
		RequestDispatcher rd = request
				.getRequestDispatcher("./database/result.jsp");
		rd.forward(request, response);
	}

doPost メソッド以外は省略しています。

どのボタンが押されたのかどうかを submit ボタンの name によって判定し、if 文で分岐しています。

そして分岐先でそれぞれ別のメソッドを呼び出し、別の処理をしたあと、同じ result.jsp というページにフォワードするようになっています。

フォワード後の結果画面を作る

では最後に結果画面を result.jsp という名前で作成します。

実はフォワードがおこなわれたタイミングでデータベースの書き込みは終えているのですが、だからといって何が起きたのか表示されないのはユーザビリティに欠けます。

とりあえず簡単なものを作っておくことにします。

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
String msg = (String)request.getAttribute("msg");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
	integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
	crossorigin="anonymous">
<title>結果画面</title>
</head>
<body>

	<div class="container">
		<div class="row">
			<h1>結果画面</h1><br>
			<p><%=msg %></p><br>
			<a href="ManagementServlet">アイテム一覧に戻る</a>
		</div>
	</div>

</body>
</html>

実行して動作を確認する

ManagementServlet を実行し、一覧かつフォームを担っている画面を開きます。

アイテムの追加

適当に打ち込んでアイテム追加ボタンを押しました。

するとアイテムを追加したという旨の結果画面が表示されました。一覧ページに戻ってみると、たしかに先ほど入力したデータが追加されていることが確認できました。

アイテムデータの変更

次は右側にあるアイテムデータ更新フォームを使って、指定したアイテム ID のデータを書き換えてみます。

今しがた追加したアイテム ID が 5番のデータを指定して、新しいアイテム名や価格などを入力後、ボタンを押します。

結果画面を挟んで、アイテム ID 5番のデータの情報が書き換わりました。

とりあえずこれで一段落

データ一覧を表示するだけという状態に比べたらだいぶまともな Web アプリケーションになりました。

しかしこれでは空のデータが入力されたときの対処をどこにも記述していないので、一部おかしな挙動をしてしまうことがあります。そのあたりご容赦ください。

コメント

タイトルとURLをコピーしました