Comments
Description
Transcript
サーブレット・JSP 基礎講座②実習
サーブレット・JSP 基礎講座②実習 サーブレット・JSP 基礎講座②実習 日本エコシステム株式会社 1 サーブレット・JSP 基礎講座②実習 目次 1. 目的...................................................................................................................................................... 3 2. リファクタリング................................................................................................................................ 3 3. MySQL によるデータベースの構築.................................................................................................... 6 4. エンティティクラスの作成.................................................................................................................. 7 5. DAO クラスの作成.............................................................................................................................. 8 6. 可変要素を含む SQL の発行..............................................................................................................11 7. データの追加(INSERT)................................................................................................................12 8. 共通処理の基本クラス切り出し........................................................................................................ 14 9. エンティティのマッピング................................................................................................................18 10. MVC モデルとドメイン駆動........................................................................................................... 22 11. ドメイン層の作成............................................................................................................................ 24 12. サービス層の作成............................................................................................................................ 26 13. DTO の作成..................................................................................................................................... 28 14. EL 式とタグライブラリ................................................................................................................... 29 15. フォームと画面入力データ..............................................................................................................31 16. まとめ.............................................................................................................................................. 32 2 サーブレット・JSP 基礎講座②実習 1. 目的 サーブレット・JSP を使った Web アプリケーションに MVC モデルを適用し、メンテナンス性等を 考慮したプログラム構造の作り方を解説することで、サーブレット・JSP の理解を深めることを目的 として、最小限のアプリケーションを改修していく実習を行います。 本実習の内容・方針は、次の通りです。 (1) 環境構築編で作成した、最小限のアプリケーションを少しずつ改修して、MVC モデルを適用し たアプリケーションを作成します。 (2) MVC モデルを適用していくための改修について概略を解説し、実際にアプリケーションを改修 する実習で理解を深めることを重視します。 2. リファクタリング eclipse のプロジェクトやソースファイル上の名前を右クリックすると、[リファクタリング]メニュ ーが出てきます。以降の改修では、このリファクタリングの機能を使うことが多くなりますので、リフ ァクタリングに慣れていきましょう。 リファクタリングを使って、最小アプリケーションを次のように修正してください。 (1) プロジェクト名のリファクタリング 一般にプロジェクト名は小文字の単語をハイフンで接続する命名規則となっています。 WebAppSample プロジェクトの名前を web-app-sample にリファクタリングしてみましょう。 その際、TOMCAT_HOME ディレクトリ配下にあるコンテキストファイルの名前は手書きで修正 する必要があるので注意しましょう。 "http://localhost:8080/web-app-sample/Sample"でアクセスできるように変更されましたか? (2) サーブレットクラスの名前、パッケージの変更 サーブレットクラスは"~Servlet"という命名規則にしましょう。WebAppSample クラスの名前を IndexServlet に変更してください。 また、サーブレットクラスは servlet パッケージに配置しましょう。新しく servlet(必ず全て小文 字で記述します)パッケージを作成し、IndexServlet クラスを移動します。 (3) web.xml の変更 上記(2)のリファクタリングを行っただけではエラーとなって画面が表示されなくなります。 web.xml を次のように変更してみましょう。 3 サーブレット・JSP 基礎講座②実習 <web-app> <servlet> <servlet-name>Index</servlet-name> <servlet-class>servlet.IndexServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Index</servlet-name> <url-pattern>/index.html</url-pattern> </servlet-mapping> </web-app> "http://localhost:8080/web-app-sample/index.html"でアクセスできるようになりましたか? このようにブラウザからアクセスする側にはあたかも HTML であるかのように見せるためには、 サーブレットの設定を操作します。 (4) インデックスページの JSP を追加。 web-app-sample プロジェクト直下に jsp フォルダを作成し、index.jsp を追加します。フォルダ追 加の際、同じく web-app-sample プロジェクト直下にある何も入っていない src と bin フォルダは削 除してしまいましょう。index.jsp の内容は、次の通りにしてください。 <%@page pageEncoding="UTF-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>web-app-sample</title> </head> <body> <h1>Hello world!</h1> </body> </html> また、サーブレットでは JSP へのフォワードを行うようにします。IndexServlet のコードを、次の 通りに変更してください。 package servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; (次のページに続く) 4 サーブレット・JSP 基礎講座②実習 (前のページの続き) /** * インデックス画面サーブレット */ public class IndexServlet extends HttpServlet { /** * デフォルトシリアルバージョン */ private static final long serialVersionUID = 1L; // GET リクエストに対応する処理 public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { } } // 表示用 JSP にフォワードする RequestDispatcher rd = req.getRequestDispatcher("/jsp/index.jsp"); rd.forward(req, res); メンテナンス性を考慮し、コメントはしっかりと入れるようにしましょう。 index.jsp に記述した HTML が表示されましたか? (5) トップページのサーブレットと JSP の追加 IndexServlet と index.jsp と同じように、TopServlet と top.jsp を追加してみましょう。jsp 内の HTML の内容は、ひとまず index.jsp と同じで構いません。index.jsp と見分けがつくように、"Hello world!"の箇所を"トップページです。"という文字列に変更しておきます。 また index.jsp は、次のように変更します。 <%@page pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!-- トップページにリダイレクトする --> <c:redirect url="/top.html"/> ここで使っている<c:redirect>タグは JSP のタグライブラリなので、使用するためには jar ファイ ルを必要とします。タグライブラリの jar ファイルは資材として用意していますので、資材から "jakarta-taglibs-standard-1.1.2.jar" と "jstl-1.1.2.jar" の 2 ファイルを WEB-INF/lib 配下にコピー して、ビルドパスに追加しておいてください。 TopServlet からフォワードする JSP ファイルは top.jsp にしてください。IndexServlet と同様に 、 TopServlet の設定を web.xml に忘れずに追加します。(コピーペーストして名前変更します) "http://localhost:8080/web-app-sample/index.html"にアクセスして、top.jsp の内容が表示されま したか?<c:redirect>タグにより、index.jsp から TopServlet が起動され、top.jsp にフォワードされ るので、top.jsp の画面が表示されます。 5 サーブレット・JSP 基礎講座②実習 ここまでのリファクタリングで index.jsp と top.jsp の 2 つの画面を作りました。 index.jsp は top.jsp にリダイレクトするだけの役割になったため、以降は top.jsp 及び TopServlet を改修していきます。 3. MySQL によるデータベースの構築 本実習では MySQL をローカルマシンにインストールし、サーブレットからアクセスします。 データベースを使った開発を行う場合、データベースやデータベースユーザ、テーブルの作成はコマ ンドラインから行うことはほとんどありません。再構築や再テストが簡単にできるように、バッチファ イル化します。データベース作成から初期データを投入するまでの手順と、データベースを削除して元 の何もない状態に戻す手順をバッチファイル化しています。 実習用資材として用意されている下記のバッチファイルを使ってください。 項番 1 2 バッチファイル名 101_createAll.bat 102_deleteAll.bat 役割 データベース、ユーザ、テーブルの作成、初期データの投入ま で行う。 テーブル、ユーザ、データベースの削除を行い、元の何もなか った状態に戻す。 それ以外のバッチファイルは、この 2 つのバッチファイルから呼び出される子となります。また 、 MySQL のインストール位置など、可変要素となっているものは dir.ini という設定ファイルになって いますので、環境に合わせて dir.ini の内容を変更してください。 また、初期データ投入のための SQL ファイル "04_loadData.sql" 内の CSV ファイルのパスも、環境 に合わせて変更してください。それ以外は特に変更の必要はありません。 また以降の実習では、メールアドレスをユーザ ID としてポータルサイトのユーザを登録するプロ グラムを作っていきます。下記 SQL により生成される Web サービスユーザテーブルが解説の中心に なります。 CREATE TABLE WEB_SERVICE_USER( WEB_SERVICE_USER_ID INT AUTO_INCREMENT, MAIL CHAR(128), PASSWORD CHAR(32), NICKNAME VARCHAR(256), PRIMARY KEY(WEB_SERVICE_USER_ID) ); 6 サーブレット・JSP 基礎講座②実習 4. エンティティクラスの作成 Java による開発では、データベースのテーブルに対応するエンティティのクラスを作成します。 エンティティクラスは、データベーステーブルの各属性の setter/getter を持つクラスです。データ ベーステーブルのレコード 1 件分を格納するために用います。 前述の Web サービスユーザテーブルのレコード 1 件分を格納するためのエンティティクラスは、次 のようになります。WEB-INF/src 配下に entity パッケージを作成して、エンティティクラスを配置し ましょう。(クラス名は WebServiceUser となります、下記) package entity; /** * WEB サービスユーザエンティティ */ public class WebServiceUser implements Entity { /** * WEB サービスユーザ ID */ private Integer webServiceUserId; public Integer getWebServiceUserId() { return webServiceUserId; } public void setWebServiceUserId(Integer webServiceUserId) { this.webServiceUserId = webServiceUserId; } /** * メールアドレス */ private String mail; public String getMail() { return mail; } public void setMail(String mail) { this.mail = mail; } /** * パスワード */ private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } (次のページに続く) 7 サーブレット・JSP 基礎講座②実習 (前のページの続き) } /** * ニックネーム */ private String nickname; public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } implements している Entity はインターフェースです。当該クラスがエンティティであることを示 すマーカーインターフェースとなります。(下記) package entity; /** * エンティティ(マーカインターフェース) */ public interface Entity { } webServiceUserId は SQL 上整数型で定義されていますので Integer 型となり、パスワードとニッ クネームは String 型となります。get/set のメソッドはキャメル記法(最初の単語は全て小文字、2 つ 目以降の単語は先頭 1 文字のみ大文字で 2 文字目からは小文字とする記法)で厳密に書かなければな りません。(後に解説する EL 式に正しく認識させるため) エンティティクラスはデータベースからテーブルのデータを取得した際、取得したデータを格納す るために用います。すぐには使いませんが、コンパイルが通る状態で準備しておきましょう。 5. DAO クラスの作成 Java プログラムからデータベースにアクセスする場合は、通常 DAO パターンを用います。データ ベースと直接やりとりする DAO クラスをテーブルごとに作成し、データベースアクセスのコードを モデルクラス(業務ロジック)の中に記述しないようにします。 DAO はインターフェースとして用意します。こうすることにより、データベースがない状態でもモ ックを使ってテストができるようになります。(実装の差し替えを行えるようにします) 本実習では JDBC を使って MySQL にアクセスします。コードを見ていきましょう。 8 サーブレット・JSP 基礎講座②実習 インターフェースを定義します。DB アクセスの方法が分かり、メソッドが固まるまではまだインタ ーフェースそのものの変更は行いません。作るだけで一旦置いておきます。 package dao; /** * WEB サービスユーザ DAO */ public interface WebServiceUserDao { } 実装クラスにデータベースアクセスサンプルコードを記述します。下記の通り作成してください。 package dao.impl; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.List; import dao.WebServiceUserDao; import entity.WebServiceUser; /** * WEB サービスユーザ DAO(実装) */ public class WebServiceUserDaoImpl implements WebServiceUserDao { /** * WEB サービスユーザ全取得の SQL */ private static String SQL_GET_ALL = "SELECT WEB_SERVICE_USER_ID, MAIL, NICKNAME FROM WEB_SERVICE_USER"; /** * WEB サービスユーザ全取得 * * @return 全 WEB サービスユーザ * @throws ClassNotFoundException * @throws SQLException */ public List<WebServiceUser> getAll() throws ClassNotFoundException, SQLException { Connection conn = null; Statement s = null; ResultSet rs = null; try { // JDBC ドライバをクラス名で探し、初期化を行う Class.forName("com.mysql.jdbc.Driver"); (次のページに続く) 9 サーブレット・JSP 基礎講座②実習 (前のページの続き) // コネクションを取得する conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/WEB_APP_SAMPLE", "webappsampleuser", "12345"); // ステートメントを作成する s = conn.createStatement(); // SQL を実行し、リザルトセットを得る rs = s.executeQuery(SQL_GET_ALL); // リザルトセット内の全てのアイテムを処理するまでループ while (rs.next()) { // リザルトセットからデータを取得する Integer id = rs.getInt("WEB_SERVICE_USER_ID"); String mail = rs.getString("MAIL"); String nickname = rs.getString("NICKNAME"); } // 取得したデータをコンソールに表示する System.out.println("id : " + id + " mail : " + mail + " nickname : " + nickname); } finally { } } // インスタンスが存在する場合はクローズする if (rs != null) { rs.close(); } if (s != null) { s.close(); } if (conn != null) { conn.close(); } // テスト段階では null に設定する return null; } TopServlet からメソッドを呼び出してテストします。doGet メソッドの先頭に、次のコードを埋め 込んで作成したメソッドを呼び出してみましょう。 //+ テストのため、作ったメソッドを呼び出す WebServiceUserDaoImpl webServiceUserDaoImpl = new WebServiceUserDaoImpl(); try { webServiceUserDaoImpl.getAll(); } catch (Exception e) { e.printStackTrace(); } //- 10 サーブレット・JSP 基礎講座②実習 実行時には MySQL の JDBC コネクタが必要になります。資材から WEB-INF/lib 配下にコピーし、 ビルドパスに追加しておいてください。 eclipse のコンソールに System.out.println したデータベースのデータが表示されましたか? 6. 可変要素を含む SQL の発行 上記 5.で作成したプログラムは、SQL 内に可変の要素がありませんでしたが、可変要素を含む SQL を実行することもできます。 例えば SQL が次の通りだったとします。 /** * WEB サービスユーザ取得の SQL */ private static String SQL_GET = "SELECT WEB_SERVICE_USER_ID, MAIL, NICKNAME FROM WEB_SERVICE_USER WHERE WEB_SERVICE_USER_ID = ?"; SQL の最後、WHERE の条件でユーザ ID が "?" となっています。これが可変要素です。Java プログ ラム内の変数を "?" の部分に当てはめて SQL を実行することができます。PreparedStatement とい うクラスを使います。特定の ID に合致するユーザだけを取得する get というメソッドを追加しましょ う。次のように記述します。 /** * WEB サービスユーザ取得 * * @param webServiceUserId * 検索する WEB サービスユーザの ID * @return 検索結果 * @throws ClassNotFoundException * @throws SQLException */ public WebServiceUser get(Integer webServiceUserId) throws ClassNotFoundException, SQLException { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // JDBC ドライバをクラス名で探し、初期化を行う Class.forName("com.mysql.jdbc.Driver"); // コネクションを取得する conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/WEB_APP_SAMPLE", "webappsampleuser", "12345"); // PreparedStatement により SQL を形成する ps = conn.prepareStatement(SQL_GET); ps.setInt(1, webServiceUserId); (次のページへ続く) 11 サーブレット・JSP 基礎講座②実習 (前のページの続き) // SQL を実行し、リザルトセットを得る rs = ps.executeQuery(); // リザルトセット内の全てのアイテムを処理するまでループ while (rs.next()) { // リザルトセットからデータを取得する Integer id = rs.getInt("WEB_SERVICE_USER_ID"); String mail = rs.getString("MAIL"); String nickname = rs.getString("NICKNAME"); // 取得したデータをコンソールに表示する System.out.println("id : " + id + " mail : " + mail + " nickname : " + nickname); } } finally { } } // インスタンスが存在する場合はクローズする if (rs != null) { rs.close(); } if (ps != null) { ps.close(); } if (conn != null) { conn.close(); } // テスト段階では null に設定する return null; TopServlet に追加した get メソッドを呼び出すコードを書いてテストしてみましょう。指定した ID のデータだけを取得できましたか? 7. データの追加(INSERT) 前述の 5.~6.のサンプルでは SELECT を発行していましたが、INSERT や UPDATE もできます。 可変要素がある場合は PreparedStatement を使うのは同じです。SELECT の場合は executeQuery を呼び出していましたが、これが executeUpdate の呼び出しに代わります。 例えば INSERT を行う次のような SQL があったとします。 /** * 1 データ追加の SQL */ private static String SQL_INSERT = "INSERT INTO WEB_SERVICE_USER(MAIL, PASSWORD, NICKNAME) VALUES(?, ?, ?)"; 12 サーブレット・JSP 基礎講座②実習 コードは次のようになります。 /** * WEB サービスユーザ取得 * * @param webServiceUserId * 検索する WEB サービスユーザの ID * @return 検索結果 * @throws ClassNotFoundException * @throws SQLException */ public WebServiceUser insert(WebServiceUser webServiceUser) throws ClassNotFoundException, SQLException { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // JDBC ドライバをクラス名で探し、初期化を行う Class.forName("com.mysql.jdbc.Driver"); // コネクションを取得する conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/WEB_APP_SAMPLE", "webappsampleuser", "12345"); // PreparedStatement により SQL を形成する ps = conn.prepareStatement(SQL_INSERT); ps.setString(1, webServiceUser.getMail()); ps.setString(2, webServiceUser.getPassword()); ps.setString(3, webServiceUser.getNickname()); // SQL を発行する ps.executeUpdate(); } finally { } // インスタンスが存在する場合はクローズする if (rs != null) { rs.close(); } if (ps != null) { ps.close(); } if (conn != null) { conn.close(); } // テスト段階では null に設定する return null; } この追加メソッドも TopServlet から呼び出して、動作を確認しましょう。getAll 呼び出しの前で insert を呼び出せば、INSERT 後の全データを表示できます。動作が確認できましたか? 13 サーブレット・JSP 基礎講座②実習 8. 共通処理の基本クラス切り出し 前述 5.~7.の処理では、データベースのコネクションを得る箇所や、finally 内のクローズ処理が共 通しています。こうした共通処理は、他のテーブルの DAO クラスを作成した場合でも同じように必要 になります。 このようなケースでは、共通処理を基本クラスに切り出します。JdbcDaoBase というクラスを作成 してみましょう。このクラスには、前述 5.~7.で共通している次の処理を配置します。 package dao.impl; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import dao.EntityMapper; /** * JDBC マネージャ */ public class JdbcDaoBase { // 以下、データベースに接続するためのパラメータ private static String DB_NAME = "jdbc:mysql://localhost:3306/WEB_APP_SAMPLE"; private static String DB_USER = "webappsampleuser"; private static String DB_PASSWORD = "12345"; /** * コネクション */ private Connection conn; public Connection getConn() { return conn; } public void setConn(Connection conn) { this.conn = conn; } /** * ステートメント */ private Statement s; public Statement getS() { return s; } public void setS(Statement s) { this.s = s; } (次のページに続く) 14 サーブレット・JSP 基礎講座②実習 (前のページの続き) /** * プリペアードステートメント */ private PreparedStatement ps; public PreparedStatement getPs() { return ps; } public void setPs(PreparedStatement ps) { this.ps = ps; } /** * リザルトセット */ private ResultSet rs; public ResultSet getRs() { return rs; } public void setRs(ResultSet rs) { this.rs = rs; } /** * コネクション取得 * * @return SELECT 実行により取得したエンティティのリスト * @throws ClassNotFoundException * @throws SQLException */ public Connection getDbConn() throws ClassNotFoundException, SQLException { // まだコネクションが存在しない場合 if (getConn() == null) { // JDBC ドライバをクラス名で探し、初期化を行う Class.forName("com.mysql.jdbc.Driver"); // コネクションを取得する setConn(DriverManager.getConnection( DB_NAME, DB_USER, DB_PASSWORD)); } } // コネクションを呼び出し側に戻す return getConn(); /** * クローズ * * @throws SQLException */ public void close() throws SQLException { (次のページに続く) 15 サーブレット・JSP 基礎講座②実習 (前のページの続き) } } // インスタンスが存在する場合はクローズする if (getRs() != null) { getRs().close(); setRs(null); } if (getS() != null) { getS().close(); setS(null); } if (getPs() != null) { getPs().close(); setPs(null); } if (getConn() != null) { getConn().close(); setConn(null); } 毎回ローカル変数で宣言していた Connection、Statement、PreparedStatement、ResultSet を基本 クラスに移動し、接続の取得とクローズをメソッド化しています。このような基本クラスを作った場合、 派生クラス側の処理は次のように変わります。(下記例は insert メソッド) public WebServiceUser insert(WebServiceUser webServiceUser) throws ClassNotFoundException, SQLException { try { // DB と接続する getDbConn(); // PreparedStatement により SQL を形成する setPs(getConn().prepareStatement(SQL_INSERT)); getPs().setString(1, webServiceUser.getMail()); getPs().setString(2, webServiceUser.getPassword()); getPs().setString(3, webServiceUser.getNickname()); // SQL を発行する getPs().executeUpdate(); } finally { // クローズ処理を実行する close(); } } // テスト段階では null に設定する return null; 長かったコードが随分すっきりしました。同じように他の 2 メソッドも修正してみましょう。 getAll と get の改修後コードも記載します。 16 サーブレット・JSP 基礎講座②実習 public List<WebServiceUser> getAll() throws ClassNotFoundException, SQLException { try { // DB と接続する getDbConn(); // ステートメントを作成する setS(getConn().createStatement()); // SQL を実行し、リザルトセットを得る setRs(getS().executeQuery(SQL_GET_ALL)); // リザルトセット内の全てのアイテムを処理するまでループ while (getRs().next()) { // リザルトセットからデータを取得する Integer id = getRs().getInt("WEB_SERVICE_USER_ID"); String mail = getRs().getString("MAIL"); String nickname = getRs().getString("NICKNAME"); } // 取得したデータをコンソールに表示する System.out.println("id : " + id + " mail : " + mail + " nickname : " + nickname); } finally { } } // クローズ処理を実行する close(); // テスト段階では null に設定する return null; public WebServiceUser get(Integer webServiceUserId) throws ClassNotFoundException, SQLException { try { // DB と接続する getDbConn(); // PreparedStatement により SQL を形成する setPs(getConn().prepareStatement(SQL_GET)); getPs().setInt(1, webServiceUserId); // SQL を実行し、リザルトセットを得る setRs(getPs().executeQuery()); // リザルトセット内の全てのアイテムを処理するまでループ while (getRs().next()) { // リザルトセットからデータを取得する Integer id = getRs().getInt("WEB_SERVICE_USER_ID"); String mail = getRs().getString("MAIL"); String nickname = getRs().getString("NICKNAME"); (次のページに続く) 17 サーブレット・JSP 基礎講座②実習 (前のページの続き) } // 取得したデータをコンソールに表示する System.out.println("id : " + id + " mail : " + mail + " nickname : " + nickname); } finally { // クローズ処理を実行する close(); } } // テスト段階では null に設定する return null; これまでと同じように動きましたか? 9. エンティティのマッピング これまでのサンプルでは、データベースから取得したデータを System.out.println を使って表示す るだけで、エンティティ等の戻り値が null になっていました。データベースから取得したデータをエ ンティティに格納して呼び出し側に戻せばいいのですが、DAO クラス内にベタでコードを書くと、コ ードが長くなってしまいます。 リザルトセットに入っているデータをエンティティに詰める役割を持ったクラスを作ってみましょ う。まず最初にエンティティをマッピングする EntityMapper インターフェースを作成します。 package dao; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import entity.Entity; /** * エンティティマッパー */ public interface EntityMapper { /** * エンティティリスト取得 * * @return エンティティリスト */ public List<? extends Entity> getEntityList(); (次のページに続く) 18 サーブレット・JSP 基礎講座②実習 (前のページの続き) /** * エンティティのマッピング * * @param rs * リザルトセット * @return エンティティのリスト * @throws SQLException */ public void mapEntity(ResultSet rs) throws SQLException; } このインターフェースを実装するクラス WebServiceUserEntityMapper は、次のようになります。 package dao.impl; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import dao.EntityMapper; import entity.WebServiceUser; /** * WEB サービスユーザの RowMapper */ public class WebServiceUserEntityMapper implements EntityMapper { /** * エンティティリスト */ private List<WebServiceUser> entityList; /** * エンティティリスト取得 */ public List<WebServiceUser> getEntityList() { return entityList; } /** * エンティティのマッピング * * @param rs * リザルトセット * @return エンティティのリスト * @throws SQLException */ public void mapEntity(ResultSet rs) throws SQLException { // 戻り値となるエンティティのリストを作成する entityList = new ArrayList<WebServiceUser>(); (次のページに続く) 19 サーブレット・JSP 基礎講座②実習 (前のページの続き) // リザルトセット内の全てのアイテムを処理するまでループ while (rs.next()) { // リザルトセットからデータを取得し、エンティティに設定する WebServiceUser entity = new WebServiceUser(); entity.setWebServiceUserId(rs.getInt("WEB_SERVICE_USER_ID")); entity.setMail(rs.getString("MAIL")); entity.setNickname(rs.getString("NICKNAME")); // 戻り値のリストに作成したエンティティを追加する entityList.add(entity); } } } リザルトセットの内容をエンティティにマッピングするクラスを作ったので、get と getAll のコー ドを正式なものに変更してみましょう。(以下、get のコードです) public WebServiceUser get(Integer webServiceUserId) throws ClassNotFoundException, SQLException { try { // DB と接続する getDbConn(); // PreparedStatement により SQL を形成する setPs(getConn().prepareStatement(SQL_GET)); getPs().setInt(1, webServiceUserId); // SQL を実行し、リザルトセットを得る setRs(getPs().executeQuery()); // リザルトセットをエンティティにマッピングする WebServiceUserEntityMapper entityMapper = new WebServiceUserEntityMapper(); entityMapper.mapEntity(getRs()); // SELECT の結果を呼び出し側に戻す return entityMapper.getEntityList().get(0); } finally { } // クローズ処理を実行する close(); } EntityMapper インターフェースの List<? extends Entity>という書き方が特殊です。 これは「Entity インターフェースを実装するエンティティクラスなら何でも」という意味になりま す。Java のコンパイラ 1.5 以降、ジェネリックコードで警告を出さないためには、このようなインター フェース定義が必須ですので、覚えておきましょう。 20 サーブレット・JSP 基礎講座②実習 System.out.println で取得した内容を表示するのは、TopServlet に書いている仮のテストコードで 行います。 WebServiceUser entity = webServiceUserDaoImpl.get(2); System.out.println("id : " + entity.getWebServiceUserId() + " mail : " + entity.getMail() + " nickname : " + entity.getNickname()); データベースから取得したデータが、コンソールに正しく表示されましたか?getAll も同じように 改修してみましょう。それぞれのクラス・メソッドのコードも長すぎず適度になってきました。 処理の形もほぼ固まってきましたので、空のままだった DAO インターフェースの定義も、正式な形 にしておきます。(ただし最初に共通処理を決めて、インターフェースから作るのが本来の形です) public interface WebServiceUserDao { /** * 1 データ取得 * * @param webServiceUserId * WEB サービスユーザ ID * @throws SQLException * @throws ClassNotFoundException */ public WebServiceUser get(Integer webServiceUserId) throws SQLException, ClassNotFoundException; /** * 1 データ追加(insert 発行) * * @param webServiceUser * WEB サービスユーザ * @return 追加した WEB サービスユーザ * @throws SQLException * @throws ClassNotFoundException */ public WebServiceUser insert(WebServiceUser webServiceUser) throws SQLException, ClassNotFoundException; } /** * WEB サービスユーザ全取得 * * @return 全 WEB サービスユーザ * @throws ClassNotFoundException * @throws SQLException */ public List<WebServiceUser> getAll() throws ClassNotFoundException, SQLException; 21 サーブレット・JSP 基礎講座②実習 10. MVC モデルとドメイン駆動 ここまでの実習では DAO パターンを使い、DAO クラスを作ってきました。作成した DAO クラスの 動作確認を行うために TopServlet に DAO 呼び出しコードを記述しましたが、通常はサーブレットか ら DAO を直接呼び出すことはありません。 Java を使った Web アプリケーション開発では一般に、MVC モデルが採用されます。MVC モデルの MVC とは、モデル(Model)・ビュー(View)・コントローラ(Controller)のことを示します。 View は画面のことで、本実習の場合は JSP が View に相当します。画面で何か入力して実行ボタン などを押すと、Controller が起動します。本実習の場合、Controller はサーブレットとなります 。 Controller は業務ロジックである Model を呼び出し、結果を View に戻します。 この View→Controller→Model という呼び出しは決して崩してはならない、というのが MVC モデ ルのルールになっています。業務ロジックに当たる Model の移植性を確保するため、というのがその 主な理由です。 View Controller Model DAO は単にデータベースとやり取りをするだけですから、業務ロジックが入っていません。 Controller であるサーブレットから DAO を直接呼び出すのではなく、Model クラスを呼び出し、モ デルクラス内から DAO を呼び出す必要があります。 Model クラスを作ってみましょう。ここではドメイン駆動を考え方を使って Model クラスを作成し ていきます。ドメイン駆動は、次の 2 種類のクラスに業務ロジックを記述します。 クラスの種類 ドメイン リポジトリ 役割 データベーステーブルの 1 行に対する操作を実装するクラス。(更新や削除などを 行う) データベーステーブルの複数行に対する操作を実装するクラス。(複数の行を抽出 する検索処理などを行う、ドメインの生成・取得の責務も負う) 22 サーブレット・JSP 基礎講座②実習 これまでデータベースの WEB_SERVICE_USER というテーブルに対し、WebServiceUser という エンティティクラス、WebServiceUserDao という DAO クラスを作成しました。ドメイン駆動も同様 に、WebServiceUserDomain と WebServiceUserRepository クラスを作成します。クラス内に記述す るメソッドは、それぞれのクラスの役割に応じます。また、ドメインクラスはデータベースの 1 行のデ ータに対応するクラスであることから、必ずエンティティを属性として持っています。 更にドメイン駆動では、サービスを定義する必要があります。 サービス層 XxxService ドメイン層 XxxDomain XxxRepository データアクセス層 XxxDao 上図のようにドメイン駆動では、クラスが 3 つのレイヤに分かれて存在します。ドメイン層がドメイ ン及びリポジトリ、データアクセス層が Dao、ドメイン層の上位にあるサービス層にサービスを配置し なければなりません。(サービスは Dao と同じでインターフェースとします) サービスは「Web サービスユーザ登録サービス」、 「Web サービスユーザ更新サービス」、 「Web サービ スユーザ削除サービス」などのように、機能単位で作成されます。 なお、サービス層にはインターフェースとする、ドメインを呼び出すだけで業務ロジックは記述しな い、というルールがあります。 MVC モデルの Model として、サービス層・ドメイン層のソースコードを書いてみましょう。 23 サーブレット・JSP 基礎講座②実習 11. ドメイン層の作成 作成した DAO のメソッドはデータの取得と追加のみで、チェック処理等の業務ロジックと呼べる 処理が入っていません。これから作成するドメイン層のコードも業務ロジック部分がまだないスケル トン的なものになります。まず、エンティティを持つドメインクラスを作成します。 package domain; import entity.WebServiceUser; /** * WEB サービスユーザドメイン */ public class WebServiceUserDomain { } /** * エンティティ */ private WebServiceUser entity; public WebServiceUser getEntity() { return entity; } public void setEntity(WebServiceUser entity) { this.entity = entity; } ドメインが持っているエンティティクラスの設定と取得のみを持つクラスです。実際に業務ロジッ クを追加すると、メソッドが増えていきます。(例えば行データの更新や削除、外部キーで紐付くデー タの検索など) 次にリポジトリのクラスを作成します。リポジトリはドメイン生成(createDomain)とドメイン取 得(getDomain)が責務となっています。これは DAO クラスの insert と get に対応します。これに getAll に対応するメソッドを加え、次のようにクラスを作成します。 package domain; import java.sql.SQLException; import java.util.List; import dao.impl.WebServiceUserDaoImpl; import entity.WebServiceUser; /** * WEB サービスユーザリポジトリ */ public class WebServiceUserRepository { (次のページに続く) 24 サーブレット・JSP 基礎講座②実習 (前のページの続き) /** * ドメイン取得 * * @param webServiceUserId * WEB サービスユーザ ID * @return 取得した WEB サービスユーザ * @throws SQLException * @throws ClassNotFoundException */ public WebServiceUserDomain getDomain(Integer webServiceUserId) throws SQLException, ClassNotFoundException { // ID をキーとしてデータを取得する WebServiceUserDaoImpl webServiceUserDao = new WebServiceUserDaoImpl(); WebServiceUser webServiceUser; webServiceUser = webServiceUserDao.get(webServiceUserId); // ドメインにエンティティを設定し、呼び出し側に戻す WebServiceUserDomain webServiceUserDomain = new WebServiceUserDomain(); webServiceUserDomain.setEntity(webServiceUser); return webServiceUserDomain; } /** * ドメイン作成 * * @param webServiceUserId * WEB サービスユーザ ID * @return 取得した WEB サービスユーザ * @throws ClassNotFoundException * @throws SQLException */ public WebServiceUserDomain createDomain(WebServiceUser webServiceUser) throws SQLException, ClassNotFoundException { // ID をキーとしてデータを取得する WebServiceUserDaoImpl webServiceUserDao = new WebServiceUserDaoImpl(); webServiceUserDao.insert(webServiceUser); } // ドメインにエンティティを設定し、呼び出し側に戻す WebServiceUserDomain webServiceUserDomain = new WebServiceUserDomain(); webServiceUserDomain.setEntity(webServiceUser); return webServiceUserDomain; /** * 全データ取得 * * @return 取得した全 WEB サービスユーザのリスト * @throws ClassNotFoundException * @throws SQLException */ public List<WebServiceUser> getAll() throws SQLException, ClassNotFoundException { } // 全 WEB サービスユーザのリストを呼び出し側に戻す WebServiceUserDaoImpl webServiceUserDao = new WebServiceUserDaoImpl(); return webServiceUserDao.getAll(); } 25 サーブレット・JSP 基礎講座②実習 TopServlet に記述しているテストコードを次のように変更すると、ドメインのテストが可能です。 // + テストのため、作ったメソッドを呼び出す WebServiceUserRepository webServiceUserRepository = new WebServiceUserRepository(); try { WebServiceUser webServiceUser = new WebServiceUser(); webServiceUser.setMail("[email protected]"); webServiceUser.setPassword("password"); webServiceUser.setNickname("INSERT テスト"); webServiceUserRepository.createDomain(webServiceUser); List<WebServiceUser> webServiceUserList = webServiceUserRepository .getAll(); for (WebServiceUser entity : webServiceUserList) { System.out.println("id : " + entity.getWebServiceUserId() + " mail : " + entity.getMail() + " nickname : " + entity.getNickname()); } System.out.println(""); WebServiceUserDomain domain = webServiceUserRepository.getDomain(2); WebServiceUser entity = domain.getEntity(); System.out.println("id : " + entity.getWebServiceUserId() + " mail : " + entity.getMail() + " nickname : " + entity.getNickname()); } catch (Exception e) { e.printStackTrace(); } // - これまでと同じ結果になりましたか? 12. サービス層の作成 ドメイン層のソースコードが完成したら、次はサービス層のソースファイルを作成します。サービス 層は機能単位で作ります。この後、JSP 画面で新規ユーザを登録し、新規登録したユーザを含む全ユー ザを表示させる実装を行います。その場合、サービスとしては「Web サービスユーザ登録サービス」と 「全ユーザ取得サービス」が必要になります。 それぞれ WebServiceUserRegisterService、WebServiceUserGetAllService という名前のインター フェースとします。インターフェースを作ったら、実装クラスの作成を行います。実装クラスはインタ ーフェースに Impl を付けたクラスです。(Web サービスユーザ登録サービスならば、実装クラス名は WebServiceUserRegisterServiceImpl となります) まずはインターフェースから作成してみましょう。次のコードは全ユーザ取得サービスのインター フェースです。 26 サーブレット・JSP 基礎講座②実習 package service; import java.sql.SQLException; import java.util.List; import entity.WebServiceUser; /** * WEB サービスユーザ全ユーザリスト取得サービス */ public interface WebServiceUserGetAllService { } /** * サービス実行 * * @return 取得した全 WEB サービスユーザのリスト */ public List<WebServiceUser> run() throws SQLException, ClassNotFoundException; 実装クラスのコードは、次のようになります。 package service.impl; import java.sql.SQLException; import java.util.List; import service.WebServiceUserGetAllService; import domain.WebServiceUserRepository; import entity.WebServiceUser; /** * WEB サービスユーザ全ユーザリスト取得サービス(実装) */ public class WebServiceUserGetAllServiceImpl implements WebServiceUserGetAllService { /** * サービス実行 * * @return 取得した全 WEB サービスユーザのリスト * @throws ClassNotFoundException * @throws SQLException */ public List<WebServiceUser> run() throws SQLException, ClassNotFoundException { } } // 全 WEB サービスユーザリストを取得して、呼び出し側に戻す WebServiceUserRepository webServiceUserRepository = new WebServiceUserRepository(); return webServiceUserRepository.getAll(); ここまでのコードを見ていれば理解できない箇所はないと思います。 「サービス層からはドメイン層 を呼び出す」という規則に従って、リポジトリの該当メソッドを呼び出しています。TopServlet から呼 び出してテストし、同様に Web サービスユーザ登録サービスも作成してみましょう。 27 サーブレット・JSP 基礎講座②実習 13. DTO の作成 データアクセス層(DAO)、ドメイン層(ドメイン、リポジトリ)、サービス層(サービス)のソー スコードが完成したので、MVC モデルのうち Model の部分は完成しました。サーブレットは既に作成 しているので Controller の部分も完成しています。それでは実際に画面にデータベースから取得した データを表示してみましょう。 View の部分を担う JSP ですが、JSP は HTML 形式で表示のみを行うため、画面表示用のデータは サーブレットで用意しなければなりません。そのような場合に画面表示用の可変データを格納するの が DTO(Data Tarnsfer Object)です。DTO クラスには、画面表示に必要十分なデータを格納します。 package dto; import java.util.List; import entity.WebServiceUser; /** * トップ画面の DTO(画面表示データ) */ public class TopDto { } // エンティティリスト private List<WebServiceUser> entityList; public List<WebServiceUser> getEntityList() { return entityList; } public void setEntityList(List<WebServiceUser> entityList) { this.entityList = entityList; } 例えば「画面で新規登録するユーザのデータを入力させ、登録されたユーザも含めた全ユーザを表示 する」という仕様であった場合、画面表示データは全ユーザの情報があれば足ります。(説明簡略化の ため、ユーザ数が多い場合にページングを行うということを考えないこととします) よって DTO のコードは、上記のようにエンティティのリストの set と get のみで足ります。まずは TopServlet の doGet で、全ユーザ取得サービスを実行し、取得できたユーザのデータを DTO に設定し てみましょう。 public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { // 全データ取得を実行する WebServiceUserGetAllService webServiceUserGetAllService = new WebServiceUserGetAllServiceImpl(); List<WebServiceUser> entityList; entityList = webServiceUserGetAllService.run(); (次のページに続く) 28 サーブレット・JSP 基礎講座②実習 (前のページの続き) // 画面出力内容を設定する TopDto topDto = new TopDto(); topDto.setEntityList(entityList); req.setAttribute("topDto", topDto); // 表示用 JSP にフォワードする RequestDispatcher rd = req.getRequestDispatcher("/jsp/top.jsp"); rd.forward(req, res); } } catch (Exception e) { e.printStackTrace(); } DTO を使った実際の画面表示は次の章で行いますので、ここでは DTO に設定したデータを System の println メソッドを使って出力し、データが設定されていることを確認しましょう。これま でと同じ結果になりましたか? 14. EL 式とタグライブラリ JSP の重要な機能として EL 式があります。EL 式はリクエストデータやセッションデータに設定し た変数等にアクセスできます。例えば前章では、TopServlet 内で top.jsp に表示させるためのデータと して TopDto を定義しました。JSP 内では EL 式を使ってこの TopDto にアクセスできます。このとき の EL 式は、次の通りです。 ${topDto} "topDto"は TopServlet で次のように設定した変数でした。 req.setAttribute("topDto", topDto); EL 式は request や session など、サーブレットの変数を暗黙的に持っています。HttpRequest(サ ーブレット内の req 変数)配下にある"topDto"は"${topDto}"と書くだけでアクセスできます。 また一般的に画面表示として EL 式を使う場合は、次のようにタグライブラリと組み合わせて使い ます。 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <c:out value="${entity.webServiceUserId}" /> taglib で prefix="c"のタグライブラリを指定しています。(これによって<c:out>タグなど、先頭 に"c:"が付くタグを使えるようになります) 29 サーブレット・JSP 基礎講座②実習 <c:out>タグは、value="[値]"で指定した文字列を表示できます。[値]の部分に EL 式による変数を入 れると、DTO 等に設定されている変数の内容を表示できます。 EL 式を使うためには、web.xml の<webapp>タグを JSP2.4 以降の次の記述に変える必要がありま す。 <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> また、タグライブラリは JSP で<%@taglib %>による宣言の他に、タグライブラリの jar ファイルも 必要になります。資材から jakarta-taglibs-standard と jstl の 2 つの jar ファイルを WEB-INF/lib 配 下にコピーし、ビルドパスに追加しましょう。 top.jsp の<body>タグ内に次の記述を追加し、TopDto の内容を表示させてみましょう。 <p> <c:if test="${topDto.entityList != null}"> <table border="1"> <tr> <th> WEB サービスユーザ ID </th> <th> メールアドレス </th> <th> ニックネーム </th> </tr> <c:forEach var="entity" items="${topDto.entityList}"> <tr> <td> <c:out value="${entity.webServiceUserId}" /> </td> <td> <c:out value="${entity.mail}" /> </td> <td> <c:out value="${entity.nickname}" /> </td> </tr> </c:forEach> </table> </c:if> </p> ここで出てきている<c:if>タグと<c:forEach>タグについても説明します。 <c:if>タグは if 文による条件判定相当のことができます。<c:if test="${topDto.entityList != null}">と書い た場合、topDto 内の entityList が null でなければ、<c:if>~</c:if>の範囲が有効になります。 30 サーブレット・JSP 基礎講座②実習 <c:forEach>タグは、繰り返し処理を行うことができます。 <c:forEach var="entity" items="${topDto.entityList}">と書いた場合、「topDto.entityList 内にあ る各アイテムを entity という名前でアクセスしてループする」という意味になります。<c:forEach>~ </c:forEach>の間では繰り返し要素である entiy 内の変数にアクセスできます。entity 内に mail とい う変数があれば${entity.mail}でアクセスすることが可能です。 タグライブラリは大文字小文字を厳密に区別するので注意してください。例えば英文字は全部小文 字として<c:foreach>と書いても動かず、<c:forEach>として初めて動きます。また、set/get メソッドは きちんとキャメル記法で記述してください。 entity クラスに getMail というメソッドがないと、$ {entity.mail}と記述してもアクセスできません。(例外が発生します) トップページに、データベース内のデータを一覧した表が出てきましたか? 15. フォームと画面入力データ サーブレットでデータベースから取得したデータを JSP に表示させることができました。それでは 画面からの入力を受けて、サーブレットの処理を走らせてみましょう。 画面入力にはフォームを使います。<form>タグを使って、次のように画面入力のテキストボックス とサブミットボタンを作ってみましょう。 <form method="post"> <p>メールアドレス<input type="text" name="mail"></p> <p>パスワード<input type="password" name="password"></p> <p>ニックネーム<input type="text" name="nickname"></p> <p><input type="submit" value="追加"></p> </form> form のメソッドは post としています。TopServlet 側に doPost のメソッドを追加し、画面入力され たデータをデータベースに追加してみましょう。次のようにコードを記述します。 // POST リクエストに対応する処理 public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { // 画面の入力値を取得する WebServiceUser webServiceUser = new WebServiceUser(); webServiceUser.setMail(new String(req.getParameter("mail") .getBytes("iso-8859-1"), "UTF-8")); webServiceUser.setPassword(new String(req.getParameter("password") .getBytes("iso-8859-1"), "UTF-8")); webServiceUser.setNickname(new String(req.getParameter("nickname") .getBytes("iso-8859-1"), "UTF-8")); (次のページに続く) 31 サーブレット・JSP 基礎講座②実習 (前のページの続き) // WEB サービスユーザを追加する WebServiceUserRegisterService webServiceUserRegisterService = new WebServiceUserRegisterServiceImpl(); webServiceUserRegisterService.run(webServiceUser); // エンティティリストを取得する WebServiceUserGetAllService webServiceUserGetAllService = new WebServiceUserGetAllServiceImpl(); List<WebServiceUser> entityList = webServiceUserGetAllService.run(); // 画面出力内容を設定する TopDto topDto = new TopDto(); topDto.setEntityList(entityList); req.setAttribute("topDto", topDto); // 表示用 JSP にフォワードする req.getRequestDispatcher("/jsp/top.jsp").forward(req, res); // // 以下、エラー発生時にはエラーページに遷移する // } catch (Exception e) { e.printStackTrace(); req.getRequestDispatcher("/jsp/error.jsp").forward(req, res); } 画面入力は request の getParameter で取得しています。HTTP でやり取りする文字列のコードは iso-8859-1 ですので、これを UTF-8(開発環境によっては Shift-JIS になることもあります)に変換 しています。これは文字化け回避のためのコードです。 画面入力を元にエンティティを作成し、WEB サービスユーザ登録サービスを呼び出しています。そ の後は doGet メソッドと同じように、全ユーザリストを画面に表示するためにサービスを呼び出しま す。 また、catch で error.jsp にフォワードしています。何らかの例外が発生した場合に、エラーページに 飛ばすという意味があります。これまでのフォワードと使い方は同じなので、特に難しいことはないで しょう。 画面で入力したデータがデータベースに登録され、画面にも折り返しで表示されましたか? 16. まとめ 本実習では、最小限のサーブレット・JSP のプログラムを拡張して、データベースとの連携を行え るところまで解説しました。DAO、モデルクラス、コントローラ、DTO、EL 式、タグライブラリなど、そ れぞれに作法やノウハウがあります。実習した内容をしっかりと押さえておきましょう。お疲れ様でし た。 32