ActiveObjectsを使ってみた(4) - データベースの情報を取得する。

JavaのormであるActiveObjectsを使ってみるシリーズ4回目です。
これまでのシリーズで、使用するDBの切り替えを非常に簡単に行えることを見てきました。DBの切り替えは以下の3か所の変更だけで可能です。

  • EntityManagerの生成時に渡すURL/ユーザ名/パスワード
  • マイグレーション時のクラスパスに含めるJDBCドライバ
  • Webアプリケーションのクラスパスに含めるJDBCドライバ

しかし、あまりにも簡単に切り換えられるため、現在どのDBを使用しているのか不安になってしまいました。
そこで、今回は現在使用中のDBの情報を取得してみることにしました。

今回必要なAPIの一覧

データベースの情報を取得するためには、JDBCのデータベースのメタデータが必要になります。そこで、今回は以下のようなAPIを使います。

以下に、今回必要なAPIjdbcの内容(ActiveObjectsAPIについては日本語訳も)記載しました。なお、JDBCAPIの説明、および、ActiveObjcetcのAPIの説明の原文は、JavadocAPIからそのままコピーしています。

ActiveObjectsAPI

net.java.ao.EntityManager#getProvider()

Javadoc: https://activeobjects.dev.java.net/api/net/java/ao/EntityManager.html
EntityManagerがすべてのデータベース操作に使用するデータベース・プロバイダを取得します。データベース・プロバイダを確実に取得できるので、ActiveObjectsの外部でJDBC操作できるようになります。このようになります:

Retrieves the database provider used by this EntityManager for all database operations. This method can be used reliably to obtain a database provider and hence a Connection instance which can be used for JDBC operations outside of ActiveObjects. Thus:

    Connection conn = manager.getProvider().getConnection();
     try {
         // ...
     } finally {
         conn.close();
     }
net.java.ao.DatabaseProvider#getConnection()

Javadoc: https://activeobjects.dev.java.net/api/net/java/ao/DatabaseProvider.html
プロバイダのインスタンスが表すデータベースへの接続を取得します。データベースに対する任意のJDBC操作を実行するためにこの接続を使用できます。また、必要な時にActiveObjects自身のあらゆる箇所から使用されるメソッドです。

Retrieves a JDBC Connection instance which corresponds to the database represented by the provider instance. This Connection can be used to execute arbitrary JDBC operations against the database. Also, this is the method used by the whole of ActiveObjects itself to get database connections when required.

すべての接続インスタンスはスレッド内部にプールされます。したがって、スレッド毎に一つの接続だけしか持つことができません。トランザクションでは、整合性を壊すことなく任意のJDBC操作が可能でなければなりません。このメソッドを使用する開発者はこの事実を覚えておき、コネクションのインスタンスを変更しないようにすべきです。唯一の例外は、JDBCのコードがトランザクション内で実行されていないことが絶対に確実な場合です。

All Connection instances are pooled internally by thread. Thus, there is never more than one connection per thread. This is necessary to allow arbitrary JDBC operations within a transaction without breaking transaction integrity. Developers using this method should bear this fact in mind and consider the Connection instance immutable. The only exception is if one is absolutely certain that the JDBC code in question is not being executed within a transaction.

スレッド毎に単一の接続しかないにも関わらず、このメソッドが返すConnectionのインスタンスを、本物のJDBC接続として扱うべきです。接続を終えるときは、閉じるべきです。これは実際の接続プールを使用している場合には特に重要で、接続を破棄しないと接続プールが資源を使い果してクラッシュします。close()を呼び出すと必要に応じて接続のクローズへ割り込みが行われるため、開発者はスレッド毎単一接続の問題を気にする必要はありません。

Despite the fact that there is only a single connection per thread, the Connection instances returned from this method should still be treated as bona fide JDBC connections. They can and should be closed when their usage is complete. This is especially important when actual connection pooling is in use and non-disposal of connections can lead to a crash as the connection pool runs out of resources. The developer need not concern themselves with the single-connection-per-thread issue when closing the connection as the call to close() will be intercepted and ignored if necessary.

このメソッドはスレッド固有のいくつかの操作を実装する必要があるために、final宣言されサブクラスにオーバライドできないようになっています。(プール・プロバイダのように)接続の取得の仕組みをオーバライドする必要のあるデータベース・プロバイダは、代わりにgetConnectionImpl()メソッドをオーバーライドすべきです。

Due to the fact that this method must implement some thread-specific operations, it is declared final and thus is not overridable in subclasses. Database providers which need to override the connection fetching mechanism (such as pool providers) should instead override the getConnectionImpl() method.

JDBCAPI

java.sql.Connection#getMetaData()

Javadoc: http://java.sun.com/javase/ja/6/docs/ja/api/java/sql/Connection.html
この Connection オブジェクトが接続を表すデータベースに関するメタデータを格納する DatabaseMetaData オブジェクトを取得します。メタデータは、データベースのテーブル、サポートしている SQL 文法、ストアドプロシージャー、およびこの接続の能力などについての情報を含んでいます。

java.sql.DatabaseMetaData

Javadoc: http://java.sun.com/javase/ja/6/docs/ja/api/java/sql/DatabaseMetaData.html

メソッド名 概要
getDatabaseMajorVersion() 基本となるデータベースのメジャーバージョン番号を取得します。
getDatabaseMinorVersion() 基本となるデータベースのマイナーバージョン番号を取得します。
getDatabaseProductName() このデータベース製品の名前を取得します。
getDatabaseProductVersion() このデータベース製品のバージョン番号を取得します。
getDriverName() この JDBC ドライバの名前を取得します。
getDriverVersion() この JDBC ドライバのバージョン番号を String として取得します。
getDriverMajorVersion() この JDBC ドライバのメジャーバージョン番号を取得します。
getDriverMinorVersion() この JDBC ドライバのマイナーバージョン番号を取得します。
getJDBCMajorVersion() このドライバの JDBC メジャーバージョン番号を取得します。
getJDBCMinorVersion() このドライバの JDBC マイナーバージョン番号を取得します。

実際にDB情報を取得してみました

実際にDB情報を取得して画面に表示できるようにしました。MySQLPostgreSQLを使用して、それぞれDB情報画面を表示してみました。

MySQLのDB情報

PostgreSQLのDB情報


きちんとDB毎の情報を表示できています。この画面を見れば、現在どのDBを使用しているかを判断できます!

作成したプログラム

基本的に、この記事の最初に挙げたAPIを順番に呼び出していくだけの簡単なものです。以下に今回作成したプログラムをあげます。

DatabaseInfoServlet - データベース情報画面(サーブレット)
package sample.servlet;

import sample.SingletonEntityManager;
import net.java.ao.EntityManager;
import net.java.ao.DatabaseProvider;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

/**
 * データベース情報画面。
 */
public class DatabaseInfoServlet extends HttpServlet {
	
	/**
	 * GETリクエスト。
	 */
	public void doGet( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException {
		// エンティティ・マネージャを取得する。
		EntityManager manager = SingletonEntityManager.getInstance();
		// データベース・プロバイダを取得する。
		DatabaseProvider provider = manager.getProvider();
		
		Connection connection = null;
		try {
			// データベースへの接続を取得する。
			connection = provider.getConnection();
			// メタデータを取得する。
			DatabaseMetaData metaData = connection.getMetaData();
			
			// メタデータのうち、必要な情報をBeanに詰める。
			//
			// メタデータはコネクションを閉じると使用できなくなる(ようなので)、
			// 直接JSPには渡せませんでした。そのため、ここでBeanに詰め直しています。
			DatabaseMetaDataBean metaDataBean = new DatabaseMetaDataBean();
			metaDataBean.setDatabaseMajorVersion( metaData.getDatabaseMajorVersion() );
			metaDataBean.setDatabaseMinorVersion( metaData.getDatabaseMinorVersion() );
			metaDataBean.setDatabaseProductName( metaData.getDatabaseProductName() );
			metaDataBean.setDatabaseProductVersion( metaData.getDatabaseProductVersion() );
			metaDataBean.setDriverMajorVersion( metaData.getDriverMajorVersion() );
			metaDataBean.setDriverMinorVersion( metaData.getDriverMinorVersion() );
			metaDataBean.setDriverName( metaData.getDriverName() );
			metaDataBean.setDriverVersion( metaData.getDriverVersion() );
			metaDataBean.setJDBCMajorVersion( metaData.getJDBCMajorVersion() );
			metaDataBean.setJDBCMinorVersion( metaData.getJDBCMinorVersion() );
			
			// BeanをJSPに渡す。
			req.setAttribute( "DatabaseMetaData", metaDataBean );
		} catch ( SQLException e ) {
			// 取得に失敗する:
			
			// エラーメッセージをJSPに渡す。
			req.setAttribute( "Message", "取得に失敗しました。" );
		} finally {
			try {
				if ( connection != null ) connection.close();
			} catch ( SQLException e ) {
				// 無視
			}
		}

		// JSPへ転送する。
		req.getRequestDispatcher( "../jsp/DatabaseInfo.jsp" ).forward( req, resp );
	}
}
DatabaseMetaDataBean - DatabaseMetaDataの情報のうち、今回必要な情報だけを容れるためのBean
package sample.servlet;

public class DatabaseMetaDataBean {

	private int databaseMajorVersion;
	private int databaseMinorVersion;
	private String databaseProductName;
	private String databaseProductVersion;
	private int driverMajorVersion;
	private int driverMinorVersion;
	private String driverName;
	private String driverVersion;
	private int jDBCMajorVersion;
	private int jDBCMinorVersion;

	public void setDatabaseMajorVersion( int databaseMajorVersion ) {
		this.databaseMajorVersion = databaseMajorVersion;
	}
	public void setDatabaseMinorVersion( int databaseMinorVersion ) {
		this.databaseMinorVersion = databaseMinorVersion;
	}
	public void setDatabaseProductName( String databaseProductName ) {
		this.databaseProductName = databaseProductName;
	}
	public void setDatabaseProductVersion( String databaseProductVersion ) {
		this.databaseProductVersion = databaseProductVersion;
	}
	public void setDriverMajorVersion( int driverMajorVersion ) {
		this.driverMajorVersion = driverMajorVersion;
	}
	public void setDriverMinorVersion( int driverMinorVersion ) {
		this.driverMinorVersion = driverMinorVersion;
	}
	public void setDriverName( String driverName ) {
		this.driverName = driverName;
	}
	public void setDriverVersion( String driverVersion ) {
		this.driverVersion = driverVersion;
	}
	public void setJDBCMajorVersion( int jDBCMajorVersion ) {
		this.jDBCMajorVersion = jDBCMajorVersion;
	}
	public void setJDBCMinorVersion( int jDBCMinorVersion ) {
		this.jDBCMinorVersion = jDBCMinorVersion;
	}
	
	public int getDatabaseMajorVersion() {
		return databaseMajorVersion;
	}
	public int getDatabaseMinorVersion() {
		return databaseMinorVersion;
	}
	public String getDatabaseProductName() {
		return databaseProductName;
	}
	public String getDatabaseProductVersion() {
		return databaseProductVersion;
	}
	public int getDriverMajorVersion() {
		return driverMajorVersion;
	}
	public int getDriverMinorVersion() {
		return driverMinorVersion;
	}
	public String getDriverName() {
		return driverName;
	}
	public String getDriverVersion() {
		return driverVersion;
	}
	public int getJDBCMajorVersion() {
		return jDBCMajorVersion;
	}
	public int getJDBCMinorVersion() {
		return jDBCMinorVersion;
	}
}
DatabaseInfo.jsp - データベース情報画面(JSP)
<%@ page contentType="text/html; charset=Shift_JIS" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<html>
<head>
<title>データベース情報 - ActiveObjectsサンプル</title>
</head>
<body>
<h3>データベース情報</h3>

<%-- メッセージ欄 --%>
<logic:notEmpty name="Message">
<table border=1>
<tr><th>メッセージ</th></tr>
<tr><td><bean:write name="Message"/></td></tr>
</table>
<br>
</logic:notEmpty>

<%-- データベース情報欄 --%>
<logic:present name="DatabaseMetaData">
	<table border="1">
	<tr><th colspan="2">基本となるデータベース</th></tr>
	<tr><th>メジャーバージョン番号</th><td><bean:write name="DatabaseMetaData" property="databaseMajorVersion" format="0"/></td></tr>
	<tr><th>マイナーバージョン番号</th><td><bean:write name="DatabaseMetaData" property="databaseMinorVersion" format="0"/></td></tr>
	<tr><th colspan="2">データベース製品</th></tr>
	<tr><th>名前</th><td><bean:write name="DatabaseMetaData" property="databaseProductName"/></td></tr>
	<tr><th>バージョン番号</th><td><bean:write name="DatabaseMetaData" property="databaseProductVersion"/></td></tr>
	<tr><th colspan="2">JDBC ドライバ</th></tr>
	<tr><th>名前</th><td><bean:write name="DatabaseMetaData" property="driverName"/></td></tr>
	<tr><th>バージョン番号</th><td><bean:write name="DatabaseMetaData" property="driverVersion"/></td></tr>
	<tr><th>メジャーバージョン番号</th><td><bean:write name="DatabaseMetaData" property="driverMajorVersion" format="0"/></td></tr>
	<tr><th>マイナーバージョン番号</th><td><bean:write name="DatabaseMetaData" property="driverMinorVersion" format="0"/></td></tr>
	<tr><th colspan="2">JDBC バージョン</th></tr>
	<tr><th>JDBC メジャーバージョン番号</th><td><bean:write name="DatabaseMetaData" property="JDBCMajorVersion" format="0"/></td></tr>
	<tr><th>JDBC マイナーバージョン番号</th><td><bean:write name="DatabaseMetaData" property="JDBCMinorVersion" format="0"/></td></tr>
	</table>
</logic:present>
<logic:notPresent name="DatabaseMetaData">
データベース情報を表示できません。
</logic:notPresent>

</body>
</html>

おわりに

今回はormの使い方としては、ちょっと横道にそれた内容だったかもしれません。
しかし、ActiveObjectsにおいてもConnectionを取得して、ConnectionからDatabaseMetaDataを参照できることがわかりました。また、APIjavadocによれば、Connection自体もAcitveObjectsの外部で使用することができるようです。
このあたりの内容は、今後JDBCと一緒に学んでいきたいです。