Personal tools
You are here: Home 原稿・資料 ワークス、アリエル共同勉強会 今そこにあるJava EE(J2EE) -part1-
Document Actions

今そこにあるJava EE(J2EE) -part1-

今そこにあるJava EE(J2EE) 。Java EEの基本です。

はじめに

  • 2009年末にはJava EE6もリリースされそうな今日この頃
  • 今さらJava? 今さらサーブレット? という人もいそうですが
  • JavaとJava EEは大局的に見ればよくできていると思うので
  • PHPの人も、Railsの人も、Seasideの人も、Kahuaの人も、教養として、あるいは対照として知っておいてよいと思います
  • 技術評論社から2009年9月に出版予定の書籍「Perfect Java」の宣伝も兼ねています

全体の予定

  1. ベタなサーブレットAPIからMVCへの道筋(フレームワーク一歩手前)
  2. Spring2とHibernate3で書き直し(本当のMVC)
  3. Google App Engineに移植

方針

最近のJavaの開発は何かと準備が大変だったり、フレームワークで隠蔽して本質が見えにくいことが多いので、

  • 最小限の依存ツール(RDBはJDK付属のJava DB(= Derby))
  • もっともシンプルなコードから始めて徐々に積み上げていく方針

という方針で説明します。

必要なソフトウェア

  • JDK6.x
  • Tomcat6.x
  • ant
  • (emacs)

適当に最新版をインストールしてください。

必要な知識

  • 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の混乱の歴史(2)

  • (Sunおなじみ)バージョン番号の混乱
J2EE1.4
Java EE5(2005年)
Java EE6(2009年? 2010年?)

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アプリサーバがロードできるディレクトリにコピーすること

デプロイ先

$TOMCAT/webapps/アプリ名  ...以下、$APPで表記
  • $CATALINA_HOMEは紛らわしいので$TOMCATにします。

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

開発用context.xml

<?xml version="1.0" encoding="UTF-8"?>
<Context reloadable="true" />
  • デバッグ専用

最小サーブレットアプリ(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)

  1. データソースオブジェクト(javax.sql.DataSource)を取得
  2. データソースオブジェクトからConnection(java.sql.Connection)オブジェクトを取得
  3. 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)

// 開発ディレクトリ構成
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>
  • ソースコードの書き換え不要

その他

次回へ

  • フロントコントローラパターンとページコントローラパターン
  • ユーザ認証とセッション管理
  • 各サーブレットクラスに似たコードがばらまかれる問題への対処
  • エスケープ処理や検証コード
  • URLがRESTではない

Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.