まずは、JDBCを使うServletを紹介しましょう。基本的には、これまで見てきたServletと同じスタイルです。
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class JDBCTestServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doIt(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doIt(request, response); } private void doIt(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("Shift_JIS"); response.setContentType("text/html; charset=Shift_JIS"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>JDBCTestServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>"); out.println("タイトルに"); out.println("Java"); out.println("という文字列を含む本は"); out.println("</p>"); out.println("<p>"); try { Class.forName("org.hsqldb.jdbcDriver"); String url = "jdbc:hsqldb:hsql://localhost"; Connection con = DriverManager.getConnection(url, "sa", ""); String selectStatement = "select title " + "from books where title like ?"; PreparedStatement prepStmt = con.prepareStatement(selectStatement); prepStmt.setString(1, "%" + "Java" + "%"); ResultSet rs = prepStmt.executeQuery(); while (rs.next()) { String title = rs.getString("title"); out.println(title); out.println("<br>"); } rs.close(); prepStmt.close(); con.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } out.println("</p>"); out.println("</body>"); out.println("</html>"); } }
このServletをコンパイルするには、ServletのAPIとJDBCドライバをclasspathに含める必要があります。この例では、Tomcatに含まれているServletのAPIと、HSQLDBのJDBCドライバが含まれているjarファイルをclasspathに追加しています。
javac -classpath "%CATALINA_HOME%\common\lib\servlet.jar;%HSQLDB_HOME%\lib\hsqldb.jar" JDBCTestServlet.java (実際には1行で入力する)
また、Tomcat上で実行するには、web.xmlにこのServletの情報を追加します。さらに、WEB-INF/libフォルダに、JDBCドライバが含まれているjarファイルを追加する必要があります。
WEB-INF/ --- web.xml |- classes/ --- JDBCTestServlet.class |- lib/ --- hsqldb.jar (JDBCドライバが含まれている)
これまで見てきたように、JDBCを使うには、まずConnectionを取得する必要があります。前章では、JDBCドライバを使ってConnectionを取得する例について解説しました。ここでは、JNDIとDataSourceを使う方法について解説します。
まず、サンプルプログラムを見てみましょう。このプログラムは、先に示したJDBCTestServlet.javaと同じ働きをします。
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; import javax.naming.*; import javax.sql.DataSource; public class JNDITestServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doIt(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doIt(request, response); } private void doIt(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("Shift_JIS"); response.setContentType("text/html; charset=Shift_JIS"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>JNDITestServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<p>"); out.println("タイトルに"); out.println("Java"); out.println("という文字列を含む本は"); out.println("</p>"); out.println("<p>"); try { Context context = new InitialContext(); DataSource ds = (DataSource)context.lookup( "java:comp/env/jdbc/library"); Connection con = ds.getConnection(); String selectStatement = "select title " + "from books where title like ?"; PreparedStatement prepStmt = con.prepareStatement(selectStatement); prepStmt.setString(1, "%" + "Java" + "%"); ResultSet rs = prepStmt.executeQuery(); while (rs.next()) { String title = rs.getString("title"); out.println(title); out.println("<br>"); } rs.close(); prepStmt.close(); con.close(); } catch (NamingException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } out.println("</p>"); out.println("</body>"); out.println("</html>"); } }
JNDI (Java Naming and Directory Interface)は、JavaでLDAPなどのディレクトリサービスを利用するためのAPIです。名前をキーにしてオブジェクトを取り出すことができます。J2EEには不可欠の技術で、JDBCのほかにもEnterprise JavaBeans (EJB)やJavaMailなどでも利用されています。
JNDITestServlet.javaでは、JDBCTestServlet.javaと比べて、Connectionの取得方法が違っています。
Context context = new InitialContext(); DataSource ds = (DataSource)context.lookup( "java:comp/env/jdbc/library"); Connection con = ds.getConnection();
ここでJNDIを利用しています。
1行目で、InitialContextを取得しています。InitialContextとは、JNDIで検索をするときの出発点です。ファイルシステムに例えると、ルートディレクトリに相当します。Contextは、ディレクトリに相当します。
2行目と3行目で、Contextのlookupというメソッドを使い、"java:comp/env/jdbc/library"という名前からDataSource型のインスタンスを取得しています。この名前を「リソース参照名」といいます。Javaのコンポーネントは、"java:comp/env/"という文字列からはじまるリソース参照名を持つように定められています。
4行目で、DataSource型のインスタンスからConnectionを取得しています。
JDBCドライバの例と違って、ドライバ名やデータベースのURL、ユーザ名やパスワードといった情報がプログラムから消えています。こうした情報は、server.xmlやweb.xmlといった設定ファイルに記述されており、そこからJNDIに登録されます。アプリケーションでは、抽象的な「名前」からDataSourceオブジェクトを取得するだけで良いのです。もし取り扱うデータベースが変更されたとしても、データベースへのアクセスに必要な情報はJNDIに登録されているので、設定ファイルを変更するだけで済み、プログラムの変更は必要ありません。プログラムの保守性が高まります。
まず、web.xmlでJNDIの利用を宣言します。
<web-app> ..... ..... <resource-ref> <res-ref-name>jdbc/library</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
resource-refという要素でJNDIの利用を宣言しています。
この子要素を見てみましょう。
res-ref-nameという要素で、リソース参照名を記述しています。JNDITestServlet.javaでは"java:comp/env/jdbc/library"という名前を使っていました。この名前から、Javaのコンポーネントの識別に使われる"java:comp/env/"を取り除き、残った"jdbc/library"が指定されています。
res-typeという要素では、JNDIに格納されているコンポーネントの型を記述します。ここでは、"javax.sql.DataSource"を指定しています。
また、res-authという要素では、データベースの認証をどこで行うか指定します。ここでは、Webコンテナ(Tomcat)で認証を行うので、"Container"を指定します。
server.xmlは、%CATALINA_HOME%\confフォルダにあります。Tomcat全体に関連する設定や、Webコンテナの実装に依存するような内容を記述するファイルです。
このserver.xmlに、データベースへの接続に必要な情報を記述します。
<!-- Tomcat Root Context --> <!-- <Context path="" docBase="ROOT" debug="0"/> --> <Context path="/test" docBase="test" reloadable="true"> <Resource name="jdbc/library" auth="Container" type="javax.sql.DataSource" /> <ResourceParams name="jdbc/library"> <parameter> <name>driverClassName</name> <value>org.hsqldb.jdbcDriver</value> </parameter> <parameter> <name>username</name> <value>sa</value> </parameter> <parameter> <name>password</name> <value></value> </parameter> <parameter> <name>url</name> <value>jdbc:hsqldb:hsql://localhost</value> </parameter> </ResourceParams> </Context>
server.xmlでは、次の部分の直後に、データベースの設定を記述します。
<!-- Tomcat Root Context --> <!-- <Context path="" docBase="ROOT" debug="0"/> -->
ここでの設定について、外側のタグから見ていきましょう。
<Context path="/test" docBase="test" reloadable="true"> ..... ..... </Context>
Contextというタグは、Webアプリケーションのことを指します。
ここでは、pathという属性に、このWebアプリケーションへのpathを記述します。
docBaseという属性には、%CATALINA_HOME%\webappsからの相対パスを記述します。
reloadableという属性は、このWebアプリケーション内にあるServletやJSPが変更されたときに、Webコンテナを再起動しなくても自動的に変更を反映させるかどうかを設定します。
<Resource name="jdbc/library" auth="Container" type="javax.sql.DataSource" />
要素Resourceでの指定は、先に見たweb.xmlでの指定と同じです。
ResourceParamsでは、データベースへの接続に必要な情報を指定します。この要素の子要素には複数のparameterがあり、ひとつのparameterはnameとvalueから構成されていることがわかります。
<ResourceParams name="jdbc/library"> <parameter> <name>driverClassName</name> <value>org.hsqldb.jdbcDriver</value> </parameter> <parameter> <name>username</name> <value>sa</value> </parameter> <parameter> <name>password</name> <value></value> </parameter> <parameter> <name>url</name> <value>jdbc:hsqldb:hsql://localhost</value> </parameter> </ResourceParams>
parameterを上から順に見ていくと、ドライバ名・ユーザ名・パスワード・URLとなっています。これらの情報は、JDBCドライバを使ったプログラムでは、ソースコードに書かれていたものです。JNDIを利用することによって、こうした情報を設定ファイルで記述できます。
JDBCTestServlet.javaでは、JDBCドライバが含まれているhsqldb.jarをWEB-INF/libにコピーしました。しかし、JNDITestServlet.javaでは、server.xmlにデータベースの設定情報を記述しました。そこで、単なる一つのWebアプリケーションだけでなく、Tomcat全体で共有できる場所にhsqldb.jarをコピーする必要があります。
そこで、hsqldb.jarを%CATALINA_HOME%\common\libにコピーします。
Connectionの取得という処理は、時間がかかる「重い」処理です。
そこで、「コネクション・プーリング」という手法が考案されました。まず、複数のConnectionをあらかじめ作成しておき、ためておきます。データベースを利用するときだけConnectionをアプリケーションに貸し出し、アプリケーションでConnectionがcloseされたら元に戻します。この手法によって、Coonectionは新たに作成されることはなく、アプリケーションで使いまわされます。結果として、データベースに早くアクセスできます。
コネクション・プーリングを行うために、Jakarta Projectから「DBCP」というライブラリが提供されています。直接DBCPのクラスを操作して、コネクション・プーリングの機能を使うことができます。しかし、TomcatにはDBCPが既に組み込まれており、server.xmlとweb.xmlを変更するだけでコネクション・プーリングを利用できます。
ここでは、Tomcatのserver.xmlとweb.xmlを変更する方法について解説します。
Servletのプログラムは、先に解説したJNDITestServlet.javaをそのまま利用できます。また、web.xmlも、JNDIの利用を宣言したサンプルをそのまま利用できます。
ここでは、server.xmlについて見てみましょう。
<!-- Tomcat Root Context --> <!-- <Context path="" docBase="ROOT" debug="0"/> --> <Context path="/test" docBase="test" reloadable="true"> <Resource name="jdbc/library" auth="Container" type="javax.sql.DataSource" /> <ResourceParams name="jdbc/library"> <parameter> <name>factory</name> <value> org.apache.commons.dbcp.BasicDataSourceFactory </value> </parameter> <parameter> <name>driverClassName</name> <value>org.hsqldb.jdbcDriver</value> </parameter> <parameter> <name>username</name> <value>sa</value> </parameter> <parameter> <name>password</name> <value></value> </parameter> <parameter> <name>url</name> <value>jdbc:hsqldb:hsql://localhost</value> </parameter> <parameter> <name>maxActive</name> <value>20</value> </parameter> <parameter> <name>maxIdle</name> <value>10</value> </parameter> <parameter> <name>maxWait</name> <value>10000</value> </parameter> </ResourceParams> </Context>
JNDIのときのserver.xmlと比べると、parameterの数が増えています。新しく追加されたparameterについて解説します。
factoryという要素では、DataSourceを生成するためのクラスを指定します。ここではDBCPを使うので、org.apache.commons.dbcp.BasicDataSourceFactoryを指定しています。
maxActiveという要素では、同時に利用できるConnectionの数を指定します。この数を超えてConnectionを利用しようとすると、別のアプリケーションがConnectionを使い終わるまで待つことになります。
maxWaitという要素では、Connectionの利用を待つ時間を指定します。単位はミリ秒です。ここでは10秒と指定されています。
maxIdleという要素では、Connectionの利用がない状態のときに、いくつのConnectionをためておくかどうか設定します。