金銭出納帳を作る - サーブレットで実装してみる

bewise.jpやまうちです。

前回作成したデータベースを使用して、サーブレットで金銭出納帳の表示と入力、編集、削除をやっていこうと思います。

今回は少し長くなったので、目次を差し込んでおきます。


サーブレットの仕組み

サーブレット (servlet) はJavaでWebアプリケーションを作成する際の基本になる仕組みになります。

クライアントのブラウザからWebアプリケーションにアクセスすると、ブラウザからのGETリクエストをアプリケーション・サーバー (サーブレット・コンテナ) が受信して、リクエストに含まれるパスから対応するサーブレットを特定します。

次にアプリケーション・サーバーはクライアントからのリクエストをHttpServletRequestオブジェクトにして、GETリクエストであればサーブレットオブジェクトのdoGetメソッド、POSTリクエストであればdoPostメソッドに渡します。

サーブレットオブジェクトはHttpServletRequestオブジェクトとして渡されたリクエスト内容に従って処理を行い、アプリケーション・サーバーから受け取ったHttpServletResponseオブジェクトに処理結果を出力して、アプリケーション・サーバーに処理を戻します。

アプリケーション・サーバーはHttpServletResponseオブジェクトの内容に従ってクライアントにレスポンスを返すという形で処理が行われます。

サーブレットクラスはHttpServletクラスのサブクラスとして実装します。

HTTPリクエストメソッドにはPOST、GETのほかにHEAD、PUT、DELETEなどがありますが、HttpServletクラスがこれらのリクエストに対する最小限の実装を行っているので、特に必要がなければサーブレットクラスで実装する必要はありません。

リクエストに含まれるパスとサーブレットオブジェクトの紐づけの方法は2種類あります。

ひとつはサーブレットクラスにつけた「@WebServlet」アノテーションでパスを指定する方法です。この方法はサーブレットクラスを実装するだけでパスの指定もできるという利点があります。

もう一つは「web.xml」ファイルにサーブレットクラスとパスの紐づけを記述する方法です。こちらはパスとサーブレットの紐づけを集中管理できるという利点があります。

今回は少し思うところもあってweb.xmlファイルを使用することにしました。

一覧表示の作成

まずデータベースに記録されている項目の一覧表示を行うサーブレットを作っていきます。

前回のおさらい - フォルダ構成など

サーブレットの実装は前回、Java EEを使ってみる - 環境構築編で作成したプロジェクト、GlassFishTestに実装を追加する形で進めていきたいと思います。

フォルダ構成は、C:\javaeeフォルダの下にGlassFisf5.1をglassfish5というフォルダ名で展開し、GlassFishはC:\javaee\glassfish5\glassfishに、DerbyはC:\javaee\glassfish5\javadbに展開されています。

また、Pleiades All in One EclipseもC\javaeeフォルダにpleiadesというフォルダ名で展開し、Pleiades All in One Eclipseに同梱されているJava 8、C:\javaee\pleiades\java\8フォルダにあるものを使用しています。

上記フォルダ構成に合わせたJAVA_HOMEおよびDERBY_HOME環境変数とGlassFish、Derby、Java 8のbinフォルダにパスを通したコマンドプロンプトのショートカットを「ターミナル」ショートカットとこのドキュメントでは呼びます。

JDBCドライバの準備

サーブレットからデータベースにアクセスするにはサーブレットからアクセスできるフォルダにJDBCドライバを配置する必要があります。

GlassFishの場合は、アプリケーションを配置するドメインのlibフォルダにJDBCドライバを配置しなければなりません。

Derbyインストール先のlibフォルダ、C:\javaee\glassfish5\javadb\libから、C:\javaee\glassfish5\glassfish\domains\domain1\libにderbyclient.jarをコピーします。

「ターミナル」ショートカットから「asadmin start-database --dbhome C:\javaee\glassfish5\glassfish\databases」を実行してDerbyデータベースサーバーを起動してください。

サーブレットの作成

サーブレットはJavaのクラスとして実装するので、コツコツと手書きしてもかまわないのですが、Eclipseにはサーブレットのひな型を作成する機能があるので、今回はそちらを使います。

Javaソースフォルダ (src/main/java) を選択して、右クリックメニューから「新規(W)」の「その他(O)...」を選択し、「新規」ダイアログを表示します。

新規ダイアログ

「ウィザード(W)」の「Web」を展開して、「サーブレット」を選択し、「次へ(N)>」をクリックします。

サーブレット作成ダイアログ

「サーブレット作成ダイアログ」が表示されるので、「Javaパッケージ(K)」にパッケージ名、ここでは前回の流れで「jp.bewise.javaee.glassfishtest.servlet」としています、「クラス名(M)」には「TransactionListServlet」を指定しています。

「次へ(N)>」をクリックするとサーブレット名やURLマッピングを入力する画面が表示されます。

サーブレット配備記述子固有情報の入力画面

「URLマッピング(U)」にはクラス名と同じパスが指定されているので、「編集(T)...」をクリックして「/transactions」に変更し、「完了(F)」クリックしてサーブレットを作成しました。

作成されたサーブレットのひな型は以下のようになっています。

package jp.bewise.javaee.glassfishtest.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class TransactionListServlet
 */
@WebServlet("/transactions")
public class TransactionListServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public TransactionListServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

クラス宣言の直前にある「@WebServlet("/transactions")」はこのサーブレットに紐づけられるURLパス (の一部) を指定するもので、既定の設定ではプロジェクト名 (GlassFishTest) の後にここで指定したパスをつなげた「http://localhost:8080/GlassFishTest/transactions」にaccessすることでこのサーブレットが実行されます。

メソッド「doGet」と「doPost」がそれぞれHTTPのgetメソッドとpostメソッドを受信した際に呼ばれるメソッドとなります。ひな形ではdoPostメソッドはdoGetメソッドを呼び出すようになっており、doGetメソッドは所定の文字列を出力するものとなっています。

とりあえずサーブレットが用意できたので、実行してみたいと思います。

プロジェクト (GlassFishTest) を選択して、右クリックメニューから「実行(R)」の「1 サーバーで実行」を選択します。

するとブラウザが起動して、以下のようなエラー画面が表示されます。

表示されたエラー画面

サーブレットのパスが指定されていないのが原因なので、先ほどのパス (http://localhost:8080/GlassFishTest/transactions) をブラウザのアドレスバーに入力してエンターキーを押します。

ひな形の出力結果

すると上のようにひな形に記述されている文字列が表示されます。

HTML出力の実装

では、とりあえずHTML出力を実装していきたいと思います。

サーブレットでのHTML出力は、doGetメソッドのresponse引数で渡されたHttpServletResponseオブジェクトにコンテントタイプと文字エンコーディングを設定し、HttpServletResponseオブジェクトから取得したPrintWriterオブジェクトにHTMLデータを出力することで行うことができます。

コンテントタイプの出力にはsetContentTypeメソッドを、文字エンコーディングの指定にはsetCharacterEncodingメソッドを使用し、PrintWriterオブジェクトの取得にはgetWriterメソッドを使用します。

実際の実装例は以下のようになります。

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		PrintWriter writer = response.getWriter();
		writer.println("<!DOCTYPE html>");
		writer.println("<html lang='ja'>");
		writer.println("  <head>");
		writer.println("    <meta charset='UTF-8'>");
		writer.println("    <title>取引一覧</title>");
		writer.println("  </head>");
		writer.println("  <body>");
		writer.println("    <h1>取引一覧</h1>");
		writer.println("    <hr>");
		writer.println("    <table border='1'>");
		writer.println("      <tr>");
		writer.println("        <th>取引日</th>");
		writer.println("        <th>種別</th>");
		writer.println("        <th>項目</th>");
		writer.println("        <th>取引先</th>");
		writer.println("        <th>金額</th>");
		writer.println("      </tr>");
		writer.println("      <tr>");
		writer.println("        <td>2025/04/01</td>");
		writer.println("        <td>支払</td>");
		writer.println("        <td>食料品購入</td>");
		writer.println("        <td>スーパー○○</td>");
		writer.println("        <td>1247</td>");
		writer.println("      </tr>");
		writer.println("    </table>");
		writer.println("  </body>");
		writer.println("</html>");
		writer.close();
	}

doGetメソッドの実際のコードは上に示したようなものになります。

まず、「response.setContentType("text/html")」でコンテントタイプにHTMLを指定し、「response.setCharacterEncoding("UTF-8")」で文字エンコーディングにUTF-8を指定しています。

その後「response.getWriter()」でPrintWriterオブジェクトを取得し、printlnメソッドで地道にHTMLデータを出力したのち、PrintWriterオブジェクトをクローズしています。

これを先ほどと同様に実行すると以下のような画面が表示されます。

実行画面

本来は何行か書きたかったのですが、面倒なので1行でやめました。

データベースからデータを取得する

固定の画面を表示していても仕方ないので、前回用意したデータベースからデータを取得して表示できるようにしたいと思います。

まず、JDBCドライバの準備を実施し、DerbyのJDBCドライバが所定のフォルダにコピーされていることと、Derbyデータベースが起動していることを確認してください。

データベースからのデータの取得手順

JDBCを使用したデータベースからのデータ取得手順は以下の通りとなります。

  1. JDBCドライバクラスをロードする
    Class.forNameスタティックメソッドを使用して、JDBCドライバクラスをロードします。JDBCドライバクラスはロードされると、次で使用するDriverManagerにJDBCドライバ自身を登録し、DriverManagerからデータベースに接続できるようにします。

  2. Connectionオブジェクトを取得する
    DriverManager.getConnectionスタティックメソッドを使用して、データベースとの接続を管理するConnectionオブジェクトを取得します。

  3. PreparedStatementオブジェクトを作成する
    ConnectionオブジェクトのprepareStatementメソッドを使用して、SQL文からJDBCが扱うPreparedStatementオブジェクトを生成します。

  4. ResultSetオブジェクトを取得する
    PreparedStatementオブジェクトのexecuteQueryメソッドを使用してデータベースに対する検索を行い、検索結果をResultSetオブジェクトとして取得します。

  5. ResultSetオブジェクトから検索結果を取得する
    データベースの検索結果を1件ずつResultSetオブジェクトから取得していきます。まず、ResultSetオブジェクトのnextメソッドで次の結果の有無を確認し、次の結果があればデータ型ごとに用意されているResultSetオブジェクトの取得メソッドを使用して検索結果を取得していきます。

  6. Connection、PreparedStatementの各オブジェクトをクローズする
    Connection、PreparedStatementの各オブジェクトはJavaが管理していないメモリなどの資源を利用している可能性があり、明示的にクローズ処理を行わないとメモリリークなどの不具合が発生する場合があります。なお、ResultSetオブジェクトはResultSetオブジェクトを生成したPreparedStatementオブジェクトをクローズすると自動的にクローズされるので、明示的にクローズする必要はありません。

なお、上の手順で示したメソッドはサーブレットクラスが対応していないClassNotFoundExceptionやSQLExceptionといった例外を発生する可能性があり、また上の第6項でしめしたオブジェクトのクローズを確実に実施する必要も考慮して、今回の例でのデータベース検索は以下のようなコードとして実装します。

Connection connection = null;
PreparedStatement statement = null;

try {
	Class.forName("org.apache.derby.jdbc.ClientDriver");
	connection = DriverManager.getConnection("jdbc:derby://localhost:1527/CashbookDB");
	statement = connection.prepareStatement("SELECT id, transaction_date, subject, description, paid_to, amount"
			+ " FROM cashbook_transaction ORDER BY transaction_date, id");
	ResultSet resultSet = statement.executeQuery();
	while (resultSet.next()) {
		Integer id = resultSet.getInt("id");
		Date transactionDate = resultSet.getDate("transaction_date");
		String subject= resultSet.getString("subject");
		String description = resultSet.getString("description");
		String paidTo = resultSet.getString("paid_to");
		Integer amount = resultSet.getInt("amount");
		// 取得した1件分の検索結果を処理する
	}
} catch (ClassNotFoundException | SQLException e) {
	// 例外が発生した場合の処理を記述する
} finally {
	if (statement != null) {
		try {
			statement.close();
		} catch (SQLException e) {
			// とりあえず無視しておく
		}
	}
	if (connection != null) {
		try {
			connection.close();
		} catch (SQLException e) {
			// とりあえず無視しておく
		}
	}
}

最初の3行でConnection、PreparedStatementの各オブジェクト変数を宣言し、nullで初期化しています。

try節内でJDBCドライバのロード、Connectionオブジェクトの取得、PreparedStatementオブジェクトの作成、ResultSetオブジェクトの取得を順次行っています。

最初のClass.forName("org.apache.derby.jdbc.ClientDriver")がJDBCドライバをロードする指示となります。引数にはJDBCドライバのクラス名を指示します。JDBCドライバのjarファイル名ではない点に注意してください。JDBCドライバはロードされるとJDBCに自分の情報を登録します。

指定されたJDBCドライバクラスが見つからない場合にはClassNotFoundExceptionが発生しますので、クラス名に誤りがないか、JDBCドライバのjarファイルは適切な位置に配置されているかを確認してください。

なお、現在のJDBCではJDBCドライバの検索とロードは自動的に行われるので必須ではありません。しかし、データベースに接続できないエラーの原因切り分けが容易になるという利点があるので今回は明示的にJDBCドライバをロードしています。

次のDriverManager.getConnection("jdbc:derby://localhost:1527/CashbookDB")がConnectionオブジェクトの取得になります。引数はデータベースを特定するURIになります。

DriverManagerは登録されたJDBCドライバから引数で与えられたURIを処理できるドライバを検索し、見つかったドライバを使用してConnectionオブジェクトの作成します。

ドライバが見つからない場合、あるいはデータベースが起動していないなどデータベースに接続できない場合SQLExceptionが発生します。URIに誤りがないか、データベースは起動しているかを確認してください。

次のprepareStatementメソッド呼び出しはSQL文を内部形式に変換する操作になります。同様の機能を持つシンプルなオブジェクトにStatementオブジェクトがあり、今回のような一度しか使わない簡単なSQL文ではPreparedStatementオブジェクトを使用する積極的な理由はないのですが、特に使い分ける理由もないのでPreparedStatementオブジェクトを使用しています。

最後のexecuteQueryメソッド呼び出しでデータベースに対する検索を行い、ResultSetオブジェクトを生成します。

SQL文に誤りがある場合、SQLExceptionがparepareStatementメソッドの呼び出しとexecuteQueryメソッドの呼び出しのいずれかでエラーが発生します。

データベースの検索結果が何十万件にもおよぶような場合も考えられます。このような大きなデータをResultSetオブジェクトで保持するのは無理があるので、ResultSetオブジェクトはデータベースから1件ずつ順次取り出す仕組みになっています。

上のコード例のwhileブロックが検索結果の取り出し方を示しています。まず、while節内でResulstSetオブジェクトのnextメソッドを呼び出しています。nextメソッドはデータベースから順次1項目ずつ検索結果を取り出し、検索結果が取り出せたらtrue、取り出せなければfalseが返されます。

取り出した検索結果はwhileブロック内にあるようにデータベースの各カラムに設定されたデータ型に対応したgetメソッドを使用して取り出します。

1件分の検索結果を取り出したら検索結果に対する処理を行います。これについては次に考察します。

検索終了後の処理ですが、ClassNotFoundExceptionとSQLExceptionはサーブレットのdoGetメソッドでは処理できないので、catch節で補足して必要な処理を行う必要があります。

その後、finallyブロックでConnectionオブジェクトとPreparedStatementオブジェクトのクローズ処理を行っています。何らかの例外が発生していた場合、クローズ処理でも例外が発生する可能性があるので、tryブロックで囲んで例外を無視する構成にしています。

データを保持するオブジェクトを用意する

データベースからデータを1件ずつ取得するごとにHTMLに埋め込むといった実装も考えられますが、HTML出力の途中にデータベースアクセスが入ってくるのはあまり麗しくありません。

そこで1件分のデータを保持するオブジェクトのリストとして検索結果を取得することでデータベース検索とHTML出力を分離することにします。

こういったデータベースの1件分のデータを保持するためのオブジェクトをエンティティ (実体) と呼んでいます。エンティティはプログラム内で再利用される可能性が大きいので、エンティティ用のパッケージ (jp.bewise.javaee.glassfishtest.entity) を用意してそこに定義するようにします。

データを保持するオブジェクトのクラス定義を以下に示します。

package jp.bewise.javaee.glassfishtest.entity;

import java.util.Date;

public class Transaction {
	private Integer id;
	private Date transactionDate;
	private String subject;
	private String description;
	private String paidTo;
	private Integer amount;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public Date getTransactionDate() {
		return transactionDate;
	}
	public void setTransactionDate(Date transactionDate) {
		this.transactionDate = transactionDate;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	public String getPaidTo() {
		return paidTo;
	}
	public void setPaidTo(String paidTo) {
		this.paidTo = paidTo;
	}
	public Integer getAmount() {
		return amount;
	}
	public void setAmount(Integer amount) {
		this.amount = amount;
	}
}

コード自体は簡単で、データベースの項目に対応するフィールドを用意し、それぞれのフィールドへのセッターとゲッターを並べるだけです。

単にデータを保持するオブジェクトなので、セッター・ゲッターを使用せずフィールドをパブリックで定義してもいいのですが、以下の二つの理由からセッター・ゲッターを使用しています。

ひとつめの理由は入力値の検査や想定外のアクセスの検出が可能になるという点です。フィールドへのアクセスを検出、制限することは簡単ではありませんが、セッター・ゲッターはメソッドなので入力値の検査を実装したり、アクセスを記録したりといったことが容易に可能です。これによりプログラムをより安全にすることができます。

ふたつ目の理由、こちらの方が重要なのですが、サーブレットとJSP (JavaServer Pages) を組み合わせたWebアプリケーションの実装ではサーブレットとJSPの間で値を受け渡すオブジェクトはセッター・ゲッターが必須となります。

データベース検索を実装する

データベースを検索して、検索結果をHTML出力に受け渡す方法が用意できましたので、データベース検索を実装していきます。

データベース検索もデータを保持するエンティティオブジェクト同様に再利用可能な形で独立させようと思います。独立したデータベース検索オブジェクトを使用する実装方式はDAO (データアクセスオブジェクト) パターンと呼ばれています。

ということでDAOのためのパッケージ (jp.bewise.javaee.glassfishtest.dao) を用意してデータベース検索のコードをその中に実装していきます。

package jp.bewise.javaee.glassfishtest.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.Date;
import java.util.List;

import jp.bewise.javaee.glassfishtest.entity.Transaction;

public class TransactionDAO {
	public List<Transaction> findAll() throws ClassNotFoundException, SQLException {
		Connection connection = null;
		PreparedStatement statement = null;
		List<Transaction> transactionList = new ArrayList<>();
		
		try {
			Class.forName("org.apache.derby.jdbc.ClientDriver");
			connection = DriverManager.getConnection("jdbc:derby://localhost:1527/CashbookDB");
			statement = connection.prepareStatement("SELECT id, transaction_date, subject, description, paid_to, amount"
					+ " FROM cashbook_transaction ORDER BY transaction_date, id");
			ResultSet resultSet = statement.executeQuery();
			while (resultSet.next()) {
				Integer id = resultSet.getInt("id");
				Date transactionDate = resultSet.getDate("transaction_date");
				String subject= resultSet.getString("subject");
				String description = resultSet.getString("description");
				String paidTo = resultSet.getString("paid_to");
				Integer amount = resultSet.getInt("amount");
				
				Transaction transaction = new Transaction();
				transaction.setId(id);
				transaction.setTransactionDate(transactionDate);
				transaction.setSubject(subject);
				transaction.setDescription(description);
				transaction.setPaidTo(paidTo);
				transaction.setAmount(amount);
				transactionList.add(transaction);
			}
		} finally {
			if (statement != null) {
				try {
					statement.close();
				} catch (SQLException e) {
					// とりあえず無視しておく
				}
			}
			if (connection != null) {
				try {
					connection.close();
				} catch (SQLException e) {
					// とりあえず無視しておく
				}
			}
		}
		return transactionList;
	}
}

データの取得手順の項で紹介したコードとほぼ同様ですが、例外処理は上位に任せるということでメソッド定義にthrows節を追加しています。

データベースの検索処理を独立させたので、呼び出し側は以下のようなシンプルな処理になります。

try {
    TransactionDAO transactionDAO = new TransactionDAO();
    List<Transaction> transactionList = transactionDAO.findAll();
    // transactionListを使用してHTML出力を行う
} catch (ClassNotFoundException | SQLException e) {
    // 例外処理の実施
}

一覧表示の実装

一覧表示を実装するために、最初に作ったHTML出力を修正してデータベースの検索結果をHTML出力に反映できるようにします。

HTML出力への検索結果の反映

検索結果のリストをHTMLテーブルに埋め込むのはfor文を使用することで以下のように簡単にできます。

for (Transaction transaction: transactionList) {
	writer.println("      <tr>");
	writer.println("        <td>" + transaction.getTransactionDate() + "</td>");
	writer.println("        <td>" + transaction.getSubject() + "</td>");
	writer.println("        <td>" + transaction.getDescription() + "</td>");
	writer.println("        <td>" + transaction.getPaidTo() + "</td>");
	writer.println("        <td>" + transaction.getAmount() + "</td>");
	writer.println("      </tr>");
}

上のコード例を組み込むに際して、HTML出力もエンティティやDAOと同様に別のパッケージ (jp.bewise.javaee.glassfishtest.view) に独立したクラスとして分離することにします。

package jp.bewise.javaee.glassfishtest.view;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import jp.bewise.javaee.glassfishtest.entity.Transaction;

public class TransactionView {
	public void showTransactionList(HttpServletResponse response, List<Transaction> transactionList) throws IOException {
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		PrintWriter writer = response.getWriter();
		writer.println("<!DOCTYPE html>");
		writer.println("<html lang='ja'>");
		writer.println("  <head>");
		writer.println("    <meta charset='UTF-8'>");
		writer.println("    <title>取引一覧</title>");
		writer.println("  </head>");
		writer.println("  <body>");
		writer.println("    <h1>取引一覧</h1>");
		writer.println("    <hr>");
		writer.println("    <table border='1'>");
		writer.println("      <tr>");
		writer.println("        <th>取引日</th>");
		writer.println("        <th>種別</th>");
		writer.println("        <th>項目</th>");
		writer.println("        <th>取引先</th>");
		writer.println("        <th>金額</th>");
		writer.println("      </tr>");
		for (Transaction transaction: transactionList) {
			writer.println("      <tr>");
			writer.println("        <td>" + transaction.getTransactionDate() + "</td>");
			writer.println("        <td>" + transaction.getSubject() + "</td>");
			writer.println("        <td>" + transaction.getDescription() + "</td>");
			writer.println("        <td>" + transaction.getPaidTo() + "</td>");
			writer.println("        <td>" + transaction.getAmount() + "</td>");
			writer.println("      </tr>");
		}
		writer.println("    </table>");
		writer.println("  </body>");
		writer.println("</html>");
		writer.close();
	}
}

こちらも先ほどのDAOと同様に例外の処理は行わず、throws節により呼び出し側に処理を依頼しています。

サーブレットへの統合

ここまででデータベースからのデータ取得と取得したデータを用いたHTML出力ができましたのでサーブレットに統合していきます。

ということで完成したサーブレットクラスは以下のようになります。

package jp.bewise.javaee.glassfishtest.servlet;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jp.bewise.javaee.glassfishtest.dao.TransactionDAO;
import jp.bewise.javaee.glassfishtest.entity.Transaction;
import jp.bewise.javaee.glassfishtest.view.TransactionView;

/**
 * Servlet implementation class TransactionListServlet
 */
@WebServlet("/transactions")
public class TransactionListServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public TransactionListServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			TransactionDAO transactionDAO = new TransactionDAO();
			TransactionView transactionView = new TransactionView();
			List<Transaction> transactionList = transactionDAO.findAll();
			transactionView.showTransactionList(response, transactionList);
		} catch (ClassNotFoundException | SQLException e) {
			throw new ServletException(e);
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

doGetメソッドは非常にシンプルになり、データベース検索を行うTransactionDAOオブジェクトとHTML出力を行うTransactionViewオブジェクトを生成したのちにそれぞれの処理メソッド (findAllとshowTransactionList) を呼び出すだけになっています。

なお、TransactionDAOオブジェクトのfindAllメソッドで処理しなかった例外 (ClassNotFoundExceptionとSQLException) はcatch節で捕捉したのち、ServletExceptionを発生させることでInternal Server Error 500を出力させています。

まとめ

サーブレットを用いてWebアプリケーションを作成してみました。

サーブレットでWebアプリケーションを作成する場合、HTMLをJavaプログラムから出力する必要があり冗長になってしまうことと、HTMLが直観的に編集できないという点にむずかしさがあります。

この問題を改善する方法としてサーブレットの出力をJavaServer Pages (JSP) に代行させるという手法があります。こちらは後日紹介させていただくつもりです。

ところで、サーブレットはHttpServletResponseオブジェクトのコンテントタイプを適切に指定し、必要なデータを出力することで、今回使用したようなHTML出力だけでなく、例えばCSVファイルを出力する、画像データを出力するといったことも可能となっています。

以上でサーブレットを用いた金銭出納帳の一覧表示が完成しました。次回は同じものをサーブレットとJSPを用いて実装してみたいと思います。



いいなと思ったら応援しよう!