SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Log4J コネクションプーリング対応JDBCAppenderでパフォーマンスを向上する

Jakarta Commons DBCPを使用したコネクションプーリング対応JDBCAppender


  • X ポスト
  • このエントリーをはてなブックマークに追加

Log4Jに付属するJDBCAppenderはDBロギングのためのクラスですが、DBのコネクションプーリングに対応していません。本稿ではJDBCAppenderを拡張し、DBのコネクションプーリングに対応したカスタムのアペンダーを作成する方法を紹介します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

はじめに

 Apache Logging Services ProjectのLog4Jは、DBにログを出力するためのクラスとしてJDBCAppenderクラスを用意しています。このクラスはコネクションプーリングに対応していないため、ログを出力するたびに「DBへの接続」「SQL実行」「DB接続のクローズ」を繰り返します。そのためログの出力回数が増えるとシステムの負荷が高くなってしまいます。本稿では、JDBCAppenderクラスを拡張してコネクションプーリングに対応させる例を紹介します。DBのコネクションプーリング機能は「Jakarta commons DBCP」を使用します。

対象読者

 Javaでプログラミングしたことがある方を対象とします。

必要な環境

 で動作確認をしています。

 DBはHSQL DB 1.7.3を使用します。HSQLDBは、Javaで記述されたDBです。使用方法が簡単でサンプルまたはデモ用のDBとしての使用に適しています。また、JBossのDBとしても組み込まれています。

実装と設定(「log4j.properties」)

Log4j基本設定

 Log4Jでは「ロガー(Logger)」と「アペンダー(Appender)」を設定ファイル(本稿では「log4j.properties」)に定義します。ロガーとはログ出力を実行するオブジェクトのことで、アプリケーションはこのロガーに対してログ出力を命令します。ロガーはログ出力命令を受けると、自らに関連付けられたアペンダーにログを出力します。アペンダーとはログ出力先のことです。Log4Jでは出力先ごとに、コンソール(ConsoleAppender)、ファイル(FileAppenderDailyxRollingFileAppender)、DB(JDBCAppender)、メール(SMTPAppender)、syslog(SyslogAppender)、NTイベントログ(NTEventLogAppender)、Telnet(TelnetAppender)、ソケット(SocketAppender)など、さまざまなアペンダーを用意しています。

 以下が本稿で使用する「log4j.properties」のロガーとアペンダーの定義です。「dataSourceLogger」という名前のロガーと、「appenderDB」という名前のアペンダーを使用します。

「log4j.properties」(抜粋)
# Logger定義  出力ログレベルと、Appender(=ログ追加先)を設定
log4j.logger.dataSourceLogger=DEBUG, appenderDB

# Appender定義 
log4j.appender.appenderDB=to.msn.wings.codezine.
logging.JDBCConnectionPoolAppender

 以下の表にLog4Jのログレベルを示します。どのログレベルを使用するかは、基本的に開発者に委ねられていますが、ここではそれぞれの一般的な使い方を簡単に説明します。ログレベルの高さは、数字が大きいほど高い事を示します。

Log4Jのログレベル
ログレベルの高さログレベル説明
1DEBUG開発者のデバッグ用。システムの本番環境では出力しない場合が多い。
2INFO処理の開始・終了など正常に処理が行われていることを示すログ。
3WARN実際の処理に影響を及ぼさないが、通常の状況とは違う状態である事を通知するログ。プロパティファイルの設定値がなくデフォルト値を適用する場合など。
4ERRORエラーが発生したが、リカバリ可能で処理を停止するほどでもない場合に出力されるログ。
5FATAL最高のログレベル。致命的で処理続行不能な場合に出力されるログ。

アプリケーション全体構成

 以下の図に本稿のサンプルアプリケーションの全体図を示します。

 図中、オレンジ色のクラスが本稿で新規に作成するクラスです。JDBCConnectionPoolAppenderクラスはLog4Jで用意されているJDBCAppenderクラスをextentdsし作成します。また、アプリケーションはSampleAppクラスのmainメソッドから起動します。

JDBCAppenderクラス

プロパティ

 Log4Jに用意されているorg.apache.log4j.JDBCAppenderクラスには、以下のプロパティが定義されています。

JDBCAppenderクラスの属性
プロパティ設定例説明
driverorg.hsqldb.jdbcDriverJDBCドライバクラス名
urljdbc:hsqldb:hsql://localhostDB接続先URL
UsersaDB接続用ユーザー名
passwordDB接続用パスワード
sqlINSERT INTO processlog(LOGTIME, LOGLEVEL, MESSAGE) VALUES ('%d', '%p', '%m')実行するSQL文
layoutorg.apache.log4j.PatternLayoutパターン変換に使用するLayoutクラス名
ThresholdINFOログ出力レベルの閾値。設定値より高いログレベルの場合のみ出力する。INFOの場合、「INFO」「WARNING」「ERROR」「FATAL」の場合のみ出力する
BufferSize2内部で持つログ出力のバッファ数。「2」で設定した場合、2個の出力ログが蓄積された段階でまとめて出力する

「log4j.propeties」設定

 上記のプロパティを「log4j.properties」ファイルに以下のように設定します。この設定値はアペンダークラスのインスタンス変数に適用されます。

「log4j.properties」(抜粋)
### Appenderのプロパティ ###
#  JDBCAppenderクラスに存在する属性
log4j.appender.appenderDB.driver=org.hsqldb.jdbcDriver
log4j.appender.appenderDB.URL=jdbc:hsqldb:hsql://localhost
log4j.appender.appenderDB.user=sa
log4j.appender.appenderDB.password=
log4j.appender.appenderDB.sql=INSERT INTO 
    processlog(LOGTIME, LOGLEVEL, MESSAGE) VALUES ('%d', '%p', '%m')
log4j.appender.appenderDB.layout=org.apache.log4j.PatternLayout
log4j.appender.appenderDB.Threshold=DEBUG
log4j.appender.appenderDB.bufferSize=1

 「log4j.appender.appenderDB.sql」のSQL文に指定されている「%d」、「%p」、「%m」はLog4Jではパターン文字といい、org.apache.log4j.PatternLayoutクラスによって、ログ出力時に、パターンごとに文字列変換されます。

 以下の表にPatternLayoutクラスによって変換できるパターン文字の主なものを示します。

PatternLayoutクラスによって変換できるパターン文字
パターン文字変換内容
%d現在日付
%pログレベル
%mログメッセージ
%cカテゴリ名
%Cパッケージ名を含めたクラス名
%lクラス名.メソッド名(ソースコードファイル名:ソースコード上の行番号)
%Lソースコード上の行番号
%rアプリケーションが起動してからのミリ秒
%tスレッド名

JDBCConnectionPoolAppenderクラス

コネクションプーリング

 本稿では、コネクションプールを使用するためのDataSourceクラスとして「Jakarta Commons DBCP」のBasicDataSourceクラスを使用します。BasicDataSourceクラスはjavax.sql.DataSourceインターフェースをimplementsしています。

BasicDataSourceクラスのプロパティ

 BasicDataSourceクラスで、コネクションプーリングの設定に必要なプロパティは、以下の表の通りです。

BasicDataSourceクラスのプロパティ
プロパティ設定例説明
maxActive10同時にプールから割り当てられる事ができるコネクションの最大数。無制限の場合0(ゼロ)を設定。
maxIdle2プールに残すコネクションの最大数。無制限の場合0(ゼロ)を設定。
minIdle1プールに残す最小コネクション数。プールに残さない場合は、0(ゼロ)を設定。
initialSize5プール内初期コネクション数。
maxWait60000使用可能なコネクションがプールから取得できない場合の、例外が投げられるまでの待機時間(ミリ秒)。

 これらのプロパティをDBCConnectionPoolAppenderクラスのインスタンス変数として実装します。

コネクションプーリング用 「log4j.properties」設定

 以下が「log4j.properties」の設定です。以下の設定値がJDBCConnectionPoolAppenderクラスのインスタンス変数に適用されます。

「log4j.properties」(抜粋)
### JDBCConnectionPoolAppenderクラスで拡張された属性 ###
log4j.appender.appenderDB.maxActive=10
log4j.appender.appenderDB.maxIdle=2
log4j.appender.appenderDB.minIdle=1
log4j.appender.appenderDB.initialSize=5
log4j.appender.appenderDB.maxWait=1000

JDBCConnectionPoolAppenderクラスのインスタンス変数とセッターメソッド

 JDBCConnectionPoolAppenderクラスに、コネクションプーリング用のプロパティに対応したインスタンス変数とセッターメソッドを実装します。これにより、Log4JによってJDBCConnectionPoolAppenderのインスタンスにプロパティ値が適用されます。

「JDBCConnectionPoolAppender.java」(1/3)
public class JDBCConnectionPoolAppender extends JDBCAppender {
    
    // JDBCドライバークラス名
    // JDBCAppenderクラスにはsetDriverメソッドが定義されているのみで
    // インスタンス変数がないので、ここで定義
    protected String driver = null;
        
    // 同時にプールから割り当てられる事ができるコネクションの最大数
    protected int maxActive = 0;
    
    // プールに残すコネクションの最大数
    protected int maxIdle = 0;
    
    // プールに残す最小コネクション数。
    protected int minIdle = 0;
    
    // プール内初期コネクション数。
    protected int initialSize = 0;
    
    // 使用可能なコネクションがプールから取得できない場合の、
    // 例外が投げられるまでの待機時間(ミリ秒)
    protected long maxWait = -1;

    // データソース(コネクションプール)
    // 唯一のインスタンス
    protected static DataSource dataSource = null;

    public void setDriver(String driver) {
        super.setDriver(driver);
        this.driver = driver;
    }

    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }
        
    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }
    
    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public void setMaxWait(long maxWait) {
        this.maxWait = maxWait;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

コネクション・データソースの取得

 ConnectionはDataSource(=コネクションプール)から取得します。DataSourceは、Jakarta Commons DBCPに用意されているBasicDataSourceクラスを使用しています。

「JDBCConnectionPoolAppender.java」(2/3)
// コネクション取得
protected Connection getConnection() throws SQLException {
    DataSource ds = createDataSource();
    return ds.getConnection();
}

// データソース取得
public DataSource createDataSource(){
    if ( dataSource != null ){
        return dataSource;
    }
    BasicDataSource ds = new BasicDataSource();
    ds.setDriverClassName( driver               );
    ds.setUsername       ( super.getUser()      );
    ds.setPassword       ( super.getPassword()  );
    ds.setUrl            ( super.getURL()       );
    ds.setMaxActive      ( maxActive            );
    ds.setMinIdle        ( minIdle              );
    ds.setMaxIdle        ( maxIdle              );
    ds.setMaxWait        ( maxWait              );
    ds.setInitialSize    ( initialSize          );
    dataSource = ds;     // シングルトンインスタンスに設定
    return ds;
}

コネクション・データソース クローズ処理

 以下がコネクションのクローズ処理と、データソース(コネクションプール)のクローズ処理になります。コネクションクローズ処理はログの出力ごと、データソースクローズ処理は、インスタンスが消滅する際(finalize)に親クラスのJDBCAppenderから呼び出されます。

「JDBCConnectionPoolAppender.java」(3/3)
    // コネクションクローズ処理
    protected void closeConnection(Connection con) {
        try {
            if ( con != null && !con.isClosed() ) con.close();
        } catch (Exception ex) {
            // do nothing
        }
    }

    // データソースクローズ処理
    public void close() {
        super.close();
        if ( dataSource != null ) {
        try {
            ((BasicDataSource)dataSource).close();

        } catch (Exception ex) {
            // do nothing
    }
}

サンプルアプリケーション

 今回作成したアペンダーを使用するサンプルアプリケーションです。メインメソッドでは、「dataSourceLogger」という名前のロガーを取得し、その「debug」「info」「warn」「error」「fatal」のメソッドを呼び出し、ログ出力を行っています。

「SampleApp.java」
public class SampleApp {

    static Logger dbLogger = Logger.getLogger( "dataSourceLogger" );
    
    public static void main(String[] args) throws Exception{
        dbLogger.debug( "DEBUG LEVEL" );
        dbLogger.info ( "INFO LEVEL"  );
        dbLogger.warn ( "WARN LEVEL"  );
        dbLogger.error( "ERROR LEVEL" );
        dbLogger.fatal( "FATAL LEVEL" );
    }
}

実行準備

HSQLDB起動(サーバーモード)

 添付ファイルを展開したディレクトリ直下の「dbServer.bat」を起動すると、HSQLDBがサーバーモードで起動します。

 HSQLDBには複数の起動モードが用意されています。以下の表に、HSQLDBの起動モードについて簡単にまとめます。

HSQLDB起動モード
起動モード説明
In-Memoryモードデータを一切保存せず、メモリ上だけで動作。
スタンドアロンモードサーバとクライアントが一体となって動く。
サーバモード通常のDBと同じように、DBサーバを立ち上げ、 クライアントからアクセスする。
WebサーバーモードHTTP経由でサーバにアクセスする。

 「dbServer.bat」の中身をみるとわかりますが、HSQLDBをサーバーモードで起動するには、HSQLDBのJarファイルにクラスパスを設定し、「org.hsqldb.Server」クラスを実行するだけです。

java -cp lib\hsqldb.jar org.hsqldb.Server -database data/mydb 
-port 9001

 以下の表に、HSQLDB起動時のオプションを簡単にまとめます。

HSQLDB起動時のオプション
オプション指定値説明
-databasedata/mydbHSQLDBのファイルを保存するディレクトリとDBファイルプレフィックス名。「data/mydb」の場合、「data」ディレクトリ内に「mydb.log」、「mydb.properties」、「mydb.script」というファイルが作成されます。
-port起動ポート番号

データベースマネージャーの起動

 HSQLDBには、DBにアクセスするためのGUIのツールが用意されています。

 添付ファイルを展開したディレクトリ直下の「dbManager.bat」を起動すると、データベースマネージャーが起動します。データベースマネージャーの起動方法は、HSQLDBのJarファイルにクラスパスを設定し「oorg.hsqldb.util.DatabaseManager」クラスを実行するだけです。

java -cp lib\hsqldb.jar org.hsqldb.util.DatabaseManager

 データベースマネージャーが起動したらダイアログが開きますので、以下の表に示した値を入力し「OK」ボタンを押下して下さい。DBに接続します。

データベースマネージャーダイアログの指定値
TypeHSQL Database Engine Server
Driverorg.hsqldb.jdbcDriver
URLjdbc:hsqldb:hsql://localhost/
Usersa
Password空白
データベースマネージャーダイアログ
データベースマネージャーダイアログ

ログ用テーブルの作成

 データベースマネージャーメニューの[File]→[Open Script]を選択するとスクリプト選択のダイアログが開きますので「create_table.sql」を選択して下さい。

 Create Table文が表示されますので「Execute」ボタンを押下して下さい。データベースマネージャーの左側のツリーに「PROCESSLOG」テーブルが表示されます。

実行

 添付ファイルを展開したディレクトリ直下の「exec.bat」を起動すると、サンプルが実行され、DBにログが出力されます。

実行確認

 データベースマネージャーメニューの[File]→[Open Script]を選択し、開いたダイアログで「select.sql」を選択して[Execute]ボタンを押下して下さい。DBに出力されたログが表示されます。

まとめ

 本稿ではLog4Jの「JDBCAppender」を拡張してコネクションプーリングに対応させる方法を紹介しました。本稿はコネクションプーリングへの対応という例を紹介しましたが、本稿の例以外にも、Log4Jで用意されている様々なアペンダーを拡張し、カスタムのアペンダーを作成してみてはいかがでしょうか。

参考資料

  1. Apache Logging Services Project Log4J
  2. @IT 『現場に活かすJakarta Project 第6回 DBのコネクションプールを簡単に実現する』 横田健彦 著、2003年5月

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 佐藤 治夫 (株式会社ビープラウド)(サトウ ハルオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/152 2006/10/11 19:34

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング