純規の暇人趣味ブログ

首を突っ込んで足を洗う

[Java]HikariCPでMySQL/SQLiteに接続してみる

      2016/11/28    HimaJyun

JavaプログラムからSQLを呼び出す、なんてのは多々ある状況じゃないかと思われます。

この時によく使われているHikariCPと言うライブラリ、これの使い方がググってみてもイマイチ分かりづらかったため、今回はHikariCPの使い方を解説してみようと思います。

SQLとコネクションプーリング

SQLは基本的にそれ専用のサーバがあり、TCPやソケットなどで接続して……と言うのが一般的です。(SQLiteは例外的)

この時、毎回毎回接続し直すのはコストが高くつくので、アプリケーションの起動時に接続して接続をそのまま保持、使いまわしする、なんてのは常套手段です。

だた、SQLiteはともかくとして、MySQLなどでこれを行うと、デフォルトの8時間タイムアウトで接続がぶった切られてしまいます。

じゃあ定期的にクエリを送り続ければ良いかと言われればそう言う訳なく、例えば自動アップデートが走ったりしてMySQLがアップデートされたりすれば……再起動が掛かって問答無用でぶち切られます♡

でもってそれらをチェックする機能を自分でどうこうするのはめんどくさい。

コネクションプーリング

そこで登場するのがコネクションプーリングです。(まぁ、本当はもっと他の目的もあるのですが……)

これは、接続時に複数の接続を確保し、使い回す事で同時に要求があった場合でも捌けるようにしよう、みたいな手法です。

が、多くのコネクションプーリングのライブラリはついでに接続の切断云々周りをよしなに世話してくれるので、基本的に使わない理由がありません。

自力で接続周りのお世話をしてやっても良いのですが、それをするくらいなら素直にライブラリに頼った方が良いと思われます。(めんどくせぇですし、どっちみちライセンスのどうのこうのは避けられそうにない)

HikariCP

今回目的としているJavaであれば、この記事を書いた日(2016/09/09)の段階では「HikariCP」と言うライブラリを使うのが一般的な模様です。

CPがコネクションプーリングの略なのはお察しの通りとして、Hikariってのは文字通り「光」の事みたいですね。

とにかく軽量で高速を謳っているライブラリですね。

使い方

ライブラリにはライブラリの作法があるので、普通にJDBCを使って接続する時とはかなり勝手が違います。

でもって、HikariCP、光とか日本語言っておきながらドキュメントはガッツリ英語です。

プログラマは英語に流暢であるべき、と言うのは否定しませんが、理想と現実は常に現世と黄泉くらい程遠い物なのです。

Maven

とりあえず最近であれば普通にMavenが使われているでしょう。

HikariCPはMavenセントラルレポジトリに登録されているので気軽に利用する事が出来ます。

Java8であれば以下の様になります。

<dependency>
  <groupId>com.zaxxer</groupId>
  <artifactId>HikariCP</artifactId>
  <version>2.5.0</version>
</dependency>

Java7だったら次の通り

<dependency>
  <groupId>com.zaxxer</groupId>
  <artifactId>HikariCP-java7</artifactId>
  <version>2.4.8</version>
</dependency>

居ないと願いたいですが、try-with-resourcesも使えない産廃Java6を使っている場合は以下の通り

<dependency>
  <groupId>com.zaxxer</groupId>
  <artifactId>HikariCP-java6</artifactId>
  <version>2.3.13</version>
</dependency>

Bukkitプラグイン開発で使うならslf4jとこれをmaven-shade-pluginで内包してやらないとClassNoDefで怒られる

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>2.4.3</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>shade</goal>
          </goals>
          <configuration>
            <minimizeJar>true</minimizeJar>
            <artifactSet>
              <includes>
                <include>org.slf4j:*</include>
                <include>com.zaxxer:*</include>
              </includes>
            </artifactSet>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
<dependencies>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.16</version>
  </dependency>
</dependencies>

MySQL

とりあえずMySQLの方が需要がありそうなので、こちらを先に説明するとしましょう。(MariaDBでもそう変わらないかと思います。)

と言っても、だいたい以下の様になります。(一部パラメータは好みに合わせて調整してくださいね。)

public class Database {
  private HikariDataSource hikari;

  public Database() {
    // HikariCPの初期化
    HikariConfig config = new HikariConfig();

    // MySQL用ドライバを設定
    config.setDriverClassName("com.mysql.jdbc.Driver");

    // 「jdbc:mysql://ホスト:ポート/DB名」の様なURLで指定
    config.setJdbcUrl("jdbc:mysql://localhost:3306/DB名");

    // ユーザ名、パスワード指定
    config.addDataSourceProperty("user", "root");
    config.addDataSourceProperty("password", "123");

    // キャッシュ系の設定(任意)
    config.addDataSourceProperty("cachePrepStmts", "true");
    config.addDataSourceProperty("prepStmtCacheSize", "250");
    config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    // サーバサイドプリペアードステートメントを使用する(任意)
    config.addDataSourceProperty("useServerPrepStmts", "true");

    // 最小接続数まで接続を確保できない時に例外を投げる
    config.setInitializationFailFast(true);
    // 接続をテストするためのクエリ
    config.setConnectionInitSql("SELECT 1");

    // 接続
    hikari = new HikariDataSource(config);
  }
}

要はHikariConfigで様々な値を指定した後、それをHikariDataSourceのコンストラクタに渡す訳です。

SQLite

私はライセンスとか言う小難しい文字の羅列が理解出来ない頭の構造してるみたいなので、パブリックドメインのSQLiteはまるで神の様な存在です。

SQLiteでコネクションプーリングを使う必要性はあまりない気がするのですが、MySQL/SQLite両対応、みたいなプログラムを作る時には一括でまとめてしまった方が楽です。

HikariCPでSQLiteを使う場合には以下の通り。

public class Database {
  private HikariDataSource hikari;
  public Database() {
    // データのファイルパス
    String dbPath = "/data/sqlite.db";

    // SQLiteはサブディレクトリを作成してくれないみたいなので自分で作成する。
    (new File(dbPath)).getParentFile().mkdirs();

    HikariConfig config = new HikariConfig();

    // SQLite用ドライバを設定
    config.setDriverClassName("org.sqlite.JDBC");
    
    // 「jdbc:sqlite:/data/sqlite.db」の様に指定する。
    config.setJdbcUrl("jdbc:sqlite:" + dbPath);

    // 最小接続数まで接続を確保できない時に例外を投げる
    config.setInitializationFailFast(true);
    // 接続をテストするためのクエリ
    config.setConnectionInitSql("SELECT 1");

    // 接続
    hikari = new HikariDataSource(hikari);
  }
}

MySQLより簡単ですね。

SQLを呼び出す

実際にHikariCPから接続を取得してSQLを呼び出す場合には以下の様にします。

try(Connection con = hikari.getConnection();
    PreparedStatement prestat = con.prepareStatement("SELECT * FROM 秘蔵の写真集 WHERE type=?")) {

  prestat.setString(1, "彼女の寝顔写真");
  // ResultSetが要close()なせいでこんな風に2重tryにしないと行けないのスマートに解決できる方法求
  try(ResultSet rs = prestat.executeQuery() ) {
    // 結果をゴニョニョするコード
  }

} catch (SQLException e) {
  e.printStackTrace();
}

こんな感じでデータベースの「秘蔵の写真集」テーブルから「彼女の寝顔写真」を取得します。

重要なのは1行目ですね、先ほど作成したHikariDataSourceに対してgetConnection()を実行する事でjava.sqlの接続が取得出来ます。(ざっとコードを見た限り、それはHikariCPのProxyConnectionでラップされてる様に見えましたが、自信がないので断定はしない)

これはclose()する必要があります(と思います)、そうしないとHikariCPの接続プールに対して接続が返却されないんじゃないかと(勝手に予想)、どっち道HikariCPが良い具合にしてくれるでしょうからclose()しておけば良いのです。

使い終わったら

HikariDataSourceは確保したSQLの接続を解放するためにclose()する必要があるみたいです。

アプリケーションの終了時にclose()を呼び出してやりましょう。

public void close() {
  if (hikari != null) {
    hikari.close();
  }
}

終わりに

兎にも角にも、これで接続周りのどうこうが手抜き出来ます、サイコー!!

もしかすると私みたいなピヨピヨヒヨッコの解説記事なんぞ不要かも知れませんが、参考程度にどうぞ……

 - プログラミング