今そこにあるJava EE(J2EE) -part1-
今そこにあるJava EE(J2EE) 。Java EEの基本です。
はじめに
- 2009年末にはJava EE6もリリースされそうな今日この頃
- 今さらJava? 今さらサーブレット? という人もいそうですが
- JavaとJava EEは大局的に見ればよくできていると思うので
- PHPの人も、Railsの人も、Seasideの人も、Kahuaの人も、教養として、あるいは対照として知っておいてよいと思います
- 技術評論社から2009年9月に出版予定の書籍「Perfect Java」の宣伝も兼ねています
全体の予定
- ベタなサーブレットAPIからMVCへの道筋(フレームワーク一歩手前)
- Spring2とHibernate3で書き直し(本当のMVC)
- Google App Engineに移植
方針
最近のJavaの開発は何かと準備が大変だったり、フレームワークで隠蔽して本質が見えにくいことが多いので、
- 最小限の依存ツール(RDBはJDK付属のJava DB(= Derby))
- もっともシンプルなコードから始めて徐々に積み上げていく方針
という方針で説明します。
必要な知識
- Javaの基本
- HTTP
- URL
- Webサーバの動作
Webサーバとは
- 通信プロトコルはHTTP
- Webブラウザ(UA)からHTTPのリクエストを受けてレスポンスを返します
- HTTPリクエストはリクエスト先URLを持ちます
- 単純なWebサーバはURLからファイルシステムのパスを取り出し、ファイルの中身を返します
- しかし、ファイルの中身を返すことはWebサーバの単なる一形態にすぎません
- ファイルを実行して、実行結果を返してもいいです(いわゆるCGI型)
Webアプリケーションサーバとは
- Webブラウザ(UA)からHTTPのリクエストを受けてレスポンスを返す、という動作はWebサーバそのものです
- リクエストURLから内部の処理にマップして、コードの実行結果を返すプログラムをWebアプリケーションサーバと呼びます
- URLからファイルパスという概念は喪失(してもいい)。URLがクラス名を示したり、メソッド名を示したり、あるいはそれらとまったく無関係でも問題ありません
- 狭義には、HTTP処理機能が無くてもWebアプリケーションサーバと呼んでもいいのですが、この辺は誤差の範囲なのでどうでもいいです
- Javaサーブレットエンジンと呼ばれるプログラムは、HTTPリクエストに対しJavaのコードを実行してHTTPレスポンスを返します
Java EE(Enterprise Edition)とは
- Webアプリ以外も含めた技術規格の集合
- 実質はWebアプリのための技術規格が中心
Java EEの混乱の歴史(1)
- 現在はJavaの3つの規格(SE、EE、ME)のひとつ
- SE(Standard Edition)は曲がりなりにもSunの実装(JREとJDK)がデファクトですが、EEの場合、そうでもありません
Java EEの混乱の歴史(3)
- Java EEは技術規格の集まりで、わかりにくいことにそれぞれの技術規格が個々にバージョン番号を持ちます
- たとえば、現在のJava EE5の場合
Servlet2.5 JSP2.1 EJB3.0 JSTL1.1 など
- Sun自身が(規格に対する)参照実装をリリースしているものがあり、それらはソフトウェアとして個々にバージョンを持ちます(e.g. GlassFish2.1など)
- http://java.sun.com/javaee/technologies/
Tomcat(1)
Apache Software Foundationによるサーブレットコンテナ
- HTTPサーバ
- サーブレットエンジン
- JSPコンパイラ
注意
- JSTL処理機能は持ちません(別配布。http://jakarta.apache.org/taglibs/index.html)
Tomcat(2)
TomcatのJava EEの規格のサポート状況 Tomcatバージョン サーブレット規格バージョン JSP規格バージョン 6.x 2.5 2.1 5.x 2.4 2.0 4.x 2.3 1.2 3.x 2.2 1.1
- Tomcat6を使います
fyi, その他のサーブレットエンジン
- Jetty
- GlassFish(Sun)
- JBoss(RedHat)
- WebSphere(IBM)
- WebLogic(Oracle)
- Resin
サーブレット開発の最小構成管理
- 最小の構成管理に必要な工程はビルドとデプロイ
簡単に言うと
- ビルド: javacで.javaファイルから.classファイルにコンパイルすること(warやearへのアーカイブまで含むこともあり)
- デプロイ: ビルドで生成した.classファイル(or warやear)と画像ファイルなどWebアプリに必要なファイルをWebアプリサーバがロードできるディレクトリにコピーすること
ant(1)
- http://ant.apache.org
- 最小構成管理には最も手軽
- 今回紹介するのはjavacとcp程度なのでシェルスクリプトでも可能ですが、antで
ant(2)
- antの設定ファイルはbuild.xml
- 雛型build.xmlを作って、使いまわすのがお薦め
- 開発ディレクトリの骨格構成も使いまわす
開発ディレクトリの骨格構成
build.xml ...antの設定ファイル src/ ...javaソースファイルを置くディレクトリ web/WEB-INF/web.xml ...Webアプリの設定ファイル(後述) /classes ...classファイルの出力先ディレクトリ /lib ...外部ライブラリファイルを置くディレクトリ /META-INF/context.xml ...Webアプリの設定ファイル(後述)
- サーブレットの規格で決まっている命名はWEB-INFとMETA-INFのディレクトリ名と設定ファイルweb.xmlです
雛型build.xml
<?xml version="1.0"?> <project basedir="." default="build"> <!-- need to modify --> <property name="name" value="myservlet"/> <!-- アプリ名を指定 --> <property name="appserver.dir" value="C:/Program Files/Apache Software Foundation/Tomcat 6.0"/> <!-- Windows --> <!-- <property name="appserver.dir" value="/usr/local/tomcat"/> --> <!-- GNU/Linux --> <!-- no need to touch --> <property name="deploy.dir" value="${appserver.dir}/webapps"/> <property name="src.dir" value="src"/> <property name="web.dir" value="web"/> <property name="build.dir" value="${web.dir}/WEB-INF/classes"/> <path id="master-classpath"> <fileset dir="${web.dir}/WEB-INF/lib"> <include name="*.jar"/> </fileset> <!-- fileset dir="${appserver.dir}/common/lib" --> <!-- for tomcat5 --> <fileset dir="${appserver.dir}/lib"> <!-- for tomcat6 --> <include name="*.jar"/> </fileset> <pathelement path="${build.dir}"/> </path> <target name="build" description="Compile main source tree java files"> <mkdir dir="${build.dir}"/> <javac destdir="${build.dir}" debug="true" failonerror="true"> <src path="${src.dir}"/> <classpath refid="master-classpath"/> </javac> </target> <target name="deploy" depends="build" description="Deploy application"> <copy todir="${deploy.dir}/${name}" preservelastmodified="true"> <fileset dir="${web.dir}"> <include name="**/*.*"/> </fileset> </copy> </target> <target name="clean"> <delete failonerror="false"> <fileset dir="${build.dir}"> <include name="**/*.class"/> <include name="**/*.properties"/> </fileset> </delete> </target> </project>
antの実行例
$ ant build $ ant deploy
最小サーブレットアプリ(1)
build.xml src/MyServlet.java web/WEB-INF/web.xml /META-INF/context.xml
最小サーブレットアプリ(2)
// MyServlet.java import java.io.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; public class MyServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.print("<html><head><title>hello servlet</title></head>"); out.print("<body><p>hello, servlet</p></body></html>"); } }
最小サーブレットアプリ(3)
// web.xml <?xml version="1.0" encoding="UTF-8"?> <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>myServlet</servlet-name> <servlet-class>MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>myServlet</servlet-name> <url-pattern>/MyServlet</url-pattern> </servlet-mapping> </web-app>
web.xmlによるマッピング(1)
- web.xmlの主な役割はマッピング
- マッピングとはリクエストURLから実行するサーブレットクラスを選択すること
- web.xmlの読み方(サーブレットクラスにサーブレット名をつけて、サーブレット名とURLと結びつける)
結局、
http://localhost:8080/myservlet/MyServlet
web.xmlによるマッピング(2)
- Tomcatでは、コンテキスト = Webアプリケーション
=> URLからコンテキストを選択(context.xml or server.xml) => コンテキスト内でサーブレットクラスを選択(web.xml)
web.xmlによるマッピング(3)
ルール記述 記述 説明 例 パス 完全一致パスのルール /my /*で終端するパス 前方一致パスのルール /my/* *.拡張子 拡張子によるマッピングルール *.jsp /の1文字 デフォルトルール /
web.xmlによるマッピング(4)
- $TOMCAT/conf/web.xmlにデフォルトのマッピングがあります
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> </servlet> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jspx</url-pattern> </servlet-mapping>
- DefaultServletはファイルの中身を返すサーブレットクラス(画像などスタティックファイルはこれに処理させる)
- JspServletはJSPファイルを実行して結果を返すサーブレットクラス(設定を上書きしなければ、拡張子.jspはこれが処理する)
- 特別ルール: WEB-INFとMETA-INFの下のファイルは、URLのパターンがマッチしてもファイルの中身を返しません
サーブレットクラス(1)
- サーブレットクラスはjavax.servlet.http.HttpServletを拡張継承した具象クラスです
- フレームワークが抽象基底クラスを用意し、フレームワークを利用する開発者が拡張継承した具象クラスを作成する技法はフレームワークで一般的です
- サーブレットコンテナも典型的なひとつのフレームワークです(今、このレイヤをフレームワークと呼ぶことは稀ですが)
- 拡張継承したクラスでメソッドをオーバーライドすると、サーブレットコンテナからコールバックされます
- doGetやdoPostのようなdoメソッドをオーバーライドします
サーブレットクラス(2)
- サーブレットコンテナはサーブレットクラスのオブジェクトをクラスごとにただひとつだけ生成します
- 同時に接続があると複数のスレッドが単一のサーブレットクラスのオブジェクトのメソッドを同時に呼びます
- もし、サーブレットクラスがインスタンスフィールドを持つ場合、排他制御が必要です
- しかし、サーブレットクラスがインスタンスフィールドを持つのは悪い設計です(サーブレットクラスは状態を持つべきではありません)
- サーブレット上での状態管理については後述します
リクエスト処理
リクエストの構成要素 対応メソッド リクエストURL getRequestURLなど クエリパラメータ getParameterやgetParameterValuesなど リクエストボディ(=ポストデータ) getInputStreamやgetReaderなど リクエストヘッダ getHeaderやgetHeadersなど
HTMLフォーム入力
- フォーム入力値はGETメソッドでもPOSTメソッドでも送れる
- HTTP的にはネットワーク上を流れる入力値の形式は異なります
- サーブレットAPIのレベルではメソッドによらずパラメータ取得APIで入力値を取得できます
- ファイルアップロードデータは別の扱いです(後述)
レスポンス処理
レスポンスの構成要素 対応メソッド レスポンスステータス setStatusなど レスポンスヘッダ setHeaderやaddHeaderなど レスポンスボディ getOutputStreamやgetWriterなど
- レスポンスボディの送信はストリームに対する出力で行います(サーブレットクラスから行う場合。JSPにするのが推奨です)
フォワード処理(1)
- フォワード処理とは別のサーブレットクラスやJSPに処理を丸投げすることです
- 別のサーブレットオブジェクトのdoメソッドを直接呼ぶのは禁止です
- 原則として、サーブレットオブジェクトから別のサーブレットオブジェクトを参照することは禁止と考えてください
- 処理をフォワードするにはRequestDispatcherオブジェクトを使います
フォワード処理(2)
RequestDispatcherオブジェクトの取得メソッド 取得メソッド 説明 ServletContextオブジェクトのgetRequestDispatcherメソッド パスからRequestDispatcherオブジェクトを取得。もっとも一般的な取得手段 ServletContextオブジェクトのgetNamedDispatcherメソッド 名前からRequestDispatcherオブジェクトを取得 HttpServletRequestオブジェクトのgetRequestDispatcherメソッド パスからRequestDispatcherオブジェクトを取得
フォワード処理(3)
// getRequestDispatcherを使うフォワード処理(ただしフォワード先はJSPであるべき) public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // リクエスト処理など getServletContext().getRequestDispatcher("/Another").forward(req, resp); // レスポンス処理を委譲 } }
- 動作上はサーブレットオブジェクトから別のサーブレットオブジェクトへフォワード処理をすることは可能です
- これは悪い習慣です。サーブレットクラス間に依存関係を持たせることは避けるべきです
- フォワード処理はサーブレットクラスからJSPに限定することを勧めます
リダイレクト処理
- リダイレクト処理は特別なステータスコードを指定したレスポンスです
- リダイレクト処理はフォワード処理と混同されることがありますが、まったく別の仕組みです
状態管理(1)
- サーブレットクラスからJSPへ処理をフォワードする時、サーブレットクラス内での処理結果をJSPに渡す必要が生じます
- メソッド呼び出しのように渡せるパラメータはないので、サーブレットクラスとJSPの間で共有可能なオブジェクトに属性を持たせます
- 属性は直感的には Map<String,Object> です
- setAttributeで属性名をキーに値をセットして、getAttributeで属性名から値をゲットします
- 属性値にはJavaBeansもしくはマップが便利です(JSTLのELでドット演算子でアクセスできるので)
状態管理(2)
属性用コンテナ コンテナ型 doメソッドでの取得方法 JSPのスコープ HttpServletRequest 引数で渡ってくる リクエスト HttpSession HttpServletRequestのgetSessionメソッドで取得 セッション ServletContext getServletContextメソッドで取得 アプリケーション PageContext - ページ
- 適切な設計で使う属性用コンテナは、HttpServletRequestとHttpSession
- 適切な設計で使うJSP側のスコープはリクエストスコープのみ(セッションスコープ禁止。アプリケーションスコープとページスコープは論外)
サーブレットプログラムでJava DBを使う準備(1)
- 組み込みモードでもクライアントサーバモードでも使えます
- 実体は $JAVA_HOME/db/ (JDK6以降)
- コマンドラインツールは ij
- 組み込みモードの場合、あるプロセスがデータベースファイルを使うとロックするので注意してください(ijコマンドで接続中は同じデータベースファイルを参照するサーブレットプログラムが動きません)
サーブレットプログラムでJava DBを使う準備(2)
- 組み込み版のライブラリ: derby.jar
- クライアント版のライブラリ: derbyclient.jar
- $TOMCAT/lib or $APP/WEB-INF/lib にjarファイルをコピー(デプロイ)します
Java DB
$ export DERBY_HOME=${JAVA_HOME}/db $ ${DERBY_HOME}/bin/ij ij> connect 'jdbc:derby:/tmp/dbname;create=true'; ij> create table articles (id integer primary key generated by default as identity, title varchar(256), body long varchar, updated_at timestamp); ij> insert into articles values (default, 'title1', 'body1', current_timestamp); ij> exit;
Java DBを使うcontext.xml
<Context reloadable="true"> <Resource name="jdbc/crud" auth="container" type="javax.sql.DataSource" driverClassName="org.apache.derby.jdbc.EmbeddedDriver" url="jdbc:derby:/tmp/dbname"/> </Context>
- jdbc/crudの部分は開発者が自由につけられる名前です
Java DBを使うweb.xml
<resource-ref> <res-ref-name>jdbc/crud</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
- jdbc/crudの部分はcontext.xmlに対応させます
- これでTomcatの持つデータソースにJDBCドライバを登録できます(データソースがコネクションプーリングもしてくれます)
サーブレットプログラムからRDBを使う(primitive)
- データソースオブジェクト(javax.sql.DataSource)を取得
- データソースオブジェクトからConnection(java.sql.Connection)オブジェクトを取得
- Connectionオブジェクトに対してSQLなどを発行
- 一般にデータソースオブジェクトは初期化時に取得し、Connectionオブジェクトは必要に応じて取得します
DataSourceオブジェクトの取得(1)
import javax.sql.DataSource; public class MyServlet extends HttpServlet { private DataSource ds; @Override public void init() throws ServletException { try { ds = (DataSource)(new InitialContext()).lookup("java:comp/env/jdbc/crud"); } catch (NamingException e) { throw new ServletException(e); } } 省略
DataSourceオブジェクトの取得(2)
- Servlet2.5以降はアノテーションを使えます
import javax.sql.DataSource; import javax.annotation.Resource; public class MyServlet extends HttpServlet { @Resource(name = "jdbc/crud") private DataSource ds; 省略
サーブレットクラスだけで処理(非MVCな例)(1)
// 開発ディレクトリ構成 build.xml web/META-INF/context.xml web/WEB-INF/web.xml src/ListServlet.java
サーブレットクラスだけで処理(非MVCな例)(2)
// web.xml <web-app> <servlet> <servlet-name>listServlet</servlet-name> <servlet-class>ListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>listServlet</servlet-name> <url-pattern>/list</url-pattern> </servlet-mapping> <resource-ref> <res-ref-name>jdbc/crud</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
サーブレットクラスだけで処理(非MVCな例)(3)
import java.io.*; import java.sql.*; import javax.sql.*; import javax.naming.*; import javax.annotation.Resource; import javax.servlet.http.*; import javax.servlet.ServletException; public class ListServlet extends HttpServlet { @Resource(name = "jdbc/crud") private DataSource ds; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { PrintWriter out = resp.getWriter(); out.print("<html><head><title>list</title></head>"); out.print("<body><table><tr><th>Title</th><th>Body</th></tr>"); Connection conn = ds.getConnection(); Statement stmt = conn.createStatement(); String sql = "SELECT title, body FROM articles ORDER BY updated_at DESC"; ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { String title = rs.getString("title"); String body = rs.getString("body"); out.print("<tr><td>" + title + "</td><td>" + body + "</td>"); } out.print("</table></body></html>"); } catch (Exception e) { throw new ServletException(e); } } }
JSP/JSTLだけで処理(非MVCな例)(1)
- http://jakarta.apache.org/taglibs/index.html からjstl.jarとstandard.jarを取得してlibの下にデプロイしてください
// 開発ディレクトリ構成 build.xml web/META-INF/context.xml web/WEB-INF/web.xml web/jsp/sql.jsp
JSP/JSTLだけで処理(非MVCな例)(2)
// web.xml <web-app> <resource-ref> <res-ref-name>jdbc/crud</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
JSP/JSTLだけで処理(非MVCな例)(3)
<%@ page contentType="text/html; charset=utf-8" %> <%@ page session="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %> <sql:query var="rs" dataSource="jdbc/crud"> SELECT title, body FROM articles ORDER BY updated_at DESC </sql:query> <html> <head><title>list</title></head> <body> <table> <tr> <th>Title</th> <th>Body</th> </tr> <c:forEach var="article" items="${rs.rows}"> <tr> <td>${article.title}</td> <td>${article.body}</td> </tr> </c:forEach> </table> </body> </html>
ビューを分離したCRUD(1)
- とりあえずページコントローラパターン(web.xmlのマッピングが醜いのは無視してください)
- とりあえずサーブレットクラス内でDBアクセス
- とりあえずエスケープ処理なし(SQLインジェクションもXSSも未対応)
次回、対処します。
ビューを分離したCRUD(2)
build.xml web/META-INF/context.xml /WEB-INF/jsp/show.jsp /edit.jsp /create.jsp /list.jsp /web.xml /index.jsp src/CreateServlet.java /ListServlet.java /ShowServlet.java /EditServlet.java /bean/Article.java
- フォワード先のJSPファイルはWEB-INFディレクトリに下に置いて、直接アクセスさせるJSPファイルと区別すべき
ビューを分離したCRUD(3)
// web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app> <servlet> <servlet-name>showServlet</servlet-name> <servlet-class>ShowServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>showServlet</servlet-name> <url-pattern>/show/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>listServlet</servlet-name> <servlet-class>ListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>listServlet</servlet-name> <url-pattern>/list/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>createServlet</servlet-name> <servlet-class>CreateServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>createServlet</servlet-name> <url-pattern>/create/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>editServlet</servlet-name> <servlet-class>EditServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>editServlet</servlet-name> <url-pattern>/edit/*</url-pattern> </servlet-mapping> <resource-ref> <res-ref-name>jdbc/crud</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
ビューを分離したCRUD(4)
// ListServlet.xml import java.util.*; import java.io.*; import java.sql.*; import javax.sql.*; import javax.naming.*; import javax.annotation.Resource; import javax.servlet.http.*; import javax.servlet.ServletException; import bean.Article; public class ListServlet extends HttpServlet { @Resource(name = "jdbc/crud") private DataSource ds; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { conn = ds.getConnection(); List<Article> list = new ArrayList<Article>(); Statement stmt = conn.createStatement(); String sql = "SELECT id, title, body FROM articles ORDER BY updated_at DESC"; ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { int id = rs.getInt("id"); String title = rs.getString("title"); String body = rs.getString("body"); list.add(new Article(id, title, body, null)); } req.setAttribute("articles", list); getServletContext().getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req, resp); rs.close(); stmt.close(); } catch (Exception e) { throw new ServletException(e); } finally { try { if (conn != null) { conn.close(); } } catch (SQLException e) { throw new ServletException(e); } } } }
ビューを分離したCRUD(5)
// bean/Article.java package bean; import java.util.*; public class Article { private int id; private String title; private String body; private Date updated_at; public int getId() { return id; } public String getTitle() { return title; } public String getBody() { return body; } public Date getUpdated_at() { return updated_at; } public Article(int id, String title, String body, Date updated_at) { this.id = id; this.title = title; this.body = body; this.updated_at = updated_at; } }
ビューを分離したCRUD(6)
// list.jsp <%@ page contentType="text/html; charset=utf-8" %> <%@ page session="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>list</title></head> <body> <table> <tr> <th>Title</th> <th>Body</th> </tr> <c:forEach var="article" items="${articles}"> <tr> <td>${article.title}</td> <td>${article.body}</td> <td><a href="show?id=${article.id}">Show</a></td> <td><a href="edit?id=${article.id}">Edit</a></td> </tr> </c:forEach> </table> <br /> <a href="create">Create</a> </body> </html>
- ELの${article}で狭いスコープから自動で属性値を探します
- ELのarticle.idなどは内部的にはメソッド呼び出し(JavaBeansとELのマジック)
ビューを分離したCRUD(7)
// ShowServlet.java import java.util.*; import java.io.*; import java.sql.*; import javax.sql.*; import javax.naming.*; import javax.annotation.Resource; import javax.servlet.http.*; import javax.servlet.ServletException; import bean.Article; public class ShowServlet extends HttpServlet { @Resource(name = "jdbc/crud") private DataSource ds; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { String id_param = req.getParameter("id"); int id = Integer.parseInt(id_param != null ? id_param : "0"); conn = ds.getConnection(); String sql = "SELECT id, title, body, updated_at FROM articles WHERE id=?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, id); ResultSet rs = stmt.executeQuery(); if (!rs.next()) { throw new ServletException("valid id parameter required"); } Article article = new Article(id, rs.getString("title"), rs.getString("body"), rs.getDate("updated_at")); req.setAttribute("article", article); getServletContext().getRequestDispatcher("/WEB-INF/jsp/show.jsp").forward(req, resp); rs.close(); stmt.close(); } catch (Exception e) { throw new ServletException(e); } finally { try { if (conn != null) { conn.close(); } } catch (SQLException e) { throw new ServletException(e); } } } }
ビューを分離したCRUD(8)
// show.jsp <%@ page contentType="text/html; charset=utf-8" %> <%@ page session="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>show</title></head> <body> <p> <b>Title:</b> ${article.title} </p> <p> <b>Body:</b> ${article.body} </p> <a href="edit?id=${article.id}">Edit</a> </body> </html>
ビューを分離したCRUD(9)
// CreateServlet.java import java.io.*; import java.sql.*; import javax.sql.*; import javax.naming.*; import javax.annotation.Resource; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; public class CreateServlet extends HttpServlet { @Resource(name = "jdbc/crud") private DataSource ds; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { getServletContext().getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); } @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { String title = req.getParameter("title"); String body = req.getParameter("body"); conn = ds.getConnection(); String sql = "INSERT INTO articles (id, title, body, updated_at) VALUES (default, ?, ?, current_timestamp)"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, title); stmt.setString(2, body); stmt.executeUpdate(); resp.sendRedirect(getServletContext().getContextPath() + "/list"); stmt.close(); } catch (Exception e) { throw new ServletException(e); } finally { try { if (conn != null) { conn.close(); } } catch (SQLException e) { throw new ServletException(e); } } } }
ビューを分離したCRUD(10)
// create.jsp <%@ page contentType="text/html; charset=utf-8" %> <%@ page session="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>new</title></head> <body> <form action="create" method="post"> <p> <b>Title</b><br/> <input id="title" name="title" size="30" type="text" /> </p> <p> <b>Body</b><br/> <textarea cols="40" id="body" name="body" rows="20"></textarea> </p> <p> <input id="submit" name="submit" type="submit" value="Create" /> </p> </form> </body> </html>
ビューを分離したCRUD(11)
// EditServlet.java import java.util.*; import java.io.*; import java.sql.*; import javax.sql.*; import javax.naming.*; import javax.annotation.Resource; import javax.servlet.http.*; import javax.servlet.ServletException; import bean.Article; public class EditServlet extends HttpServlet { @Resource(name = "jdbc/crud") private DataSource ds; @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { String id_param = req.getParameter("id"); if (id_param == null) { throw new ServletException("id parameter required"); } int id = Integer.parseInt(id_param); conn = ds.getConnection(); String sql = "SELECT id, title, body, updated_at FROM articles WHERE id=?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, id); ResultSet rs = stmt.executeQuery(); if (!rs.next()) { throw new ServletException("valid id parameter required"); } Article article = new Article(id, rs.getString("title"), rs.getString("body"), rs.getDate("updated_at")); req.setAttribute("article", article); getServletContext().getRequestDispatcher("/WEB-INF/jsp/edit.jsp").forward(req, resp); rs.close(); stmt.close(); } catch (Exception e) { throw new ServletException(e); } finally { try { if (conn != null) { conn.close(); } } catch (SQLException e) { throw new ServletException(e); } } } @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { String id_param = req.getParameter("id"); if (id_param == null) { throw new ServletException("id parameter required"); } int id = Integer.parseInt(id_param); String title = req.getParameter("title"); String body = req.getParameter("body"); conn = ds.getConnection(); String sql = "UPDATE articles SET title=?, body=?, updated_at=current_timestamp WHERE id=?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, title); stmt.setString(2, body); stmt.setInt(3, id); stmt.executeUpdate(); resp.sendRedirect(getServletContext().getContextPath() + "/list"); stmt.close(); } catch (Exception e) { throw new ServletException(e); } finally { try { if (conn != null) { conn.close(); } } catch (SQLException e) { throw new ServletException(e); } } } }
ビューを分離したCRUD(12)
// edit.jsp <%@ page contentType="text/html; charset=utf-8" %> <%@ page session="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>edit</title></head> <body> <form action="edit" method="post"> <p> <b>Title</b><br/> <input id="title" name="title" size="30" type="text" value="${article.title}" /> </p> <p> <b>Body</b><br/> <textarea cols="40" id="body" name="body" rows="20">${article.body}</textarea> </p> <p> <input id="submit" name="submit" type="submit" value="Update" /> </p> <input id="id" name="id" type="hidden" value="${article.id}" /> </form> </body> </html>
ビューを分離したCRUD(13)
// index.jsp <%@ page contentType="text/html; charset=utf-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:redirect url="/list" />
- なくてもいいですが
クライアントサーバモードのJava DBを使うには(1)
- derby.jarの代わりにderbyclient.jar
// サーバ開始 $ $DERBY_HOME/bin/startNetworkServer -h 0.0.0.0
クライアントサーバモードのJava DBを使うには(2)
$ ${DERBY_HOME}/bin/ij ij> connect 'jdbc:derby://localhost:1527/dbname;create=true'; ij> create table articles (id integer primary key generated by default as identity, title varchar(256), body long varchar, updated_at timestamp); ij> insert into articles values (default, 'title1', 'body1', current_timestamp); ij> exit;
クライアントサーバモードのJava DBを使うには(3)
// context.xml <?xml version="1.0" encoding="UTF-8"?> <Context reloadable="true"> <Resource name="jdbc/crud" auth="container" type="javax.sql.DataSource" driverClassName="org.apache.derby.jdbc.ClientDriver" url="jdbc:derby://localhost:1527/dbname"/> </Context>
- ソースコードの書き換え不要
MySQLを使うには(1)
- MySQL自体のセットアップ手順は省略します
// MySQLのJDBCドライバの配布サイト http://www.mysql.com/products/connector/
MySQLを使うには(2)
// context.xml <?xml version="1.0" encoding="UTF-8"?> <Context reloadable="true"> <Resource name="jdbc/crud" auth="container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost/myjdbc" username="someuser" password="somepass"/> </Context>
- ソースコードの書き換え不要
その他
- 日本語のフォーム入力(setCharacterEncoding)
- ファイルアップロード(http://commons.apache.org/fileupload/)
次回へ
- フロントコントローラパターンとページコントローラパターン
- ユーザ認証とセッション管理
- 各サーブレットクラスに似たコードがばらまかれる問題への対処
- エスケープ処理や検証コード
- URLがRESTではない