torutkのブログ

ソフトウェア・エンジニアのブログ

Microsoft SQL Server JDBCドライバーにmodule-info定義を追加してモジュール対応

はじめに

Microsoft SQL Server JDBCドライバーのモジュール対応(module-info.classの組み込み)を試みました。経過をメモします。

経緯

SQL Server JDBCドライバーは、自動モジュール対応までしており、モジュールアプリケーションから利用できます。自動モジュールとは、JARファイルにはmodule-info.classが組み込まれておらず、マニフェストファイルのAutomatic-Module-Name属性にモジュール名が定義されたJARファイルです。

しかし、アプリケーションの配布イメージをJLinkで作成する際、自動モジュールを使用しているアプリケーションではエラーとなってしまいます。JLinkは、自動モジュールに対応していないためです。

そこで、後付けでmodule-info.classをSQL Server JDBCドライバーのJARファイルに突っ込み、モジュール対応したものとしてJLinkにかけてみます。

module-info.classの生成とJARファイルへの後付け(試行1回目)

まず、JDBCドライバーのJARファイル mssql-jdbc-7.4.1.jre11.jar の依存関係を抽出します。jdepsコマンドを使いますが、オプション指定にいろいろあり、何が正解か分かりませんので試した結果をメモします。

mssql-jdbc-7.4.1.jre11.jarの依存関係を調べる

--generate-module-info
D:\work> mkdir out
D:\work> jdeps --generate-module-info out mssql-jdbc-7.4.1.jre.jar
Missing dependence: D:\work\out\com.microsoft.sqlserver.jdbc\module-info.java not generated
エラー: 依存性が欠落しています
   com.microsoft.sqlserver.jdbc.KeyVaultCredential    -> com.microsoft.aad.adal4j.AuthenticationCallback    見つかりません
  :
--list-deps
D:\work> jdeps --list-deps mssql-jdbc-7.4.1.jre11.jar
   java.base
   java.logging
   java.naming
   java.security.jgss
   java.sql
   java.transaction.xa
   java.xml
--list-reduced-deps
D:\work> jdeps --list-reduced-deps mssql-jdbc-7.4.1.jre11.jar
   java.base
   java.naming
   java.security.jgss
   java.sql

--list-deps オプションと --list-reduced-deps オプションの差は、後者は推移的な依存関係のあるものはリストされないというもののようです。

module-info.javaを作成

module名は、mssql-jdbc-7.4.1.jre11.jar の自動モジュールで公開しているモジュール名 com.microsoft.sqlserver.jdbc とします。 APIとして公開するパッケージ com.microsoft.sqlserver.jdbc をexports宣言し、このAPIが実行時に最低限必要なモジュールをrequires宣言します。必要なモジュールは、先のjdepsコマンド(--list-reduced-depsオプション)で調べたものを記述します。なお、java.baseは暗黙でrequiresされるので省略します。

module com.microsoft.sqlserver.jdbc {
    exports com.microsoft.sqlserver.jdbc;
    requires java.naming;
    requires java.security.jgss;
    requires java.sql;
}

module-info.java から module-info.classを生成し、jarファイルに追加

D:\work> javac --patch-module com.microsoft.sqlserver.jdbc=mssql-jdbc-7.4.1.jre11.jar module-info.java

module-info.class が生成されます。 このmodule-info.class をJARファイルに追加します。

D:\work> jar uvf mssql-jdbc-7.4.1.jre11.jar module-info.class
module-infoが更新されました: module-info.class

JLink で使用

JDBCを使用するアプリケーションの配布イメージをjlinkで作成します。

エラー: 署名済モジュラJAR D:\work\mssql-jdbc-7.4.1.jre11.jarは現在サポートされていないため、エラーを抑止するには--ignore-signing-informationを使用します

とエラーになったので、jlinkのコマンドにオプション --ignore-signing-information を追加します。

警告: 署名済モジュラJAR D:\toru\Documents\work\job\mcc\Glowlink\spectrum_viewer\.\mssql-jdbc-7.4.1.jre11.jarは現在サポートされていません

とjlinkコマンドがエラーから警告に変わり、配布イメージが生成されました。

配布イメージのモジュールにJDBCが含まれるかを確認

D:\work\myapp\out\runtime> bin\java --list-modules
myapp
com.microsoft.sqlserver.jdbc
java.base@11.0.4
java.datatransfer@11.0.4
  :

一応生成できています。

実行

D:\work\myapp\out\runtime> bin\myapp.bat
java.sql.SQLException: No suitable driver found for jdbc:sqlserver://localhost;databaseName=spectrum;user=foo;password=bar
        at java.sql/java.sql.DriverManager.getConnection(Unknown Source)

JDBCドライバが見つからないとのエラーになってしまいました。

回避策(場当たり的な)

本来は不要ですが、プログラム中でJDBCドライバークラスを明示的にnewして登録すると実行可能となりました。

static {
    try {
        DriverManager.registerDriver(new com.microsoft.sqlserver.jdbc.SQLServerDriver());
    } catch (SQLException ex) {
        logger.log(Level.WARNING, "DriverManager cannot register", ex);
    }
}

実行時エラーの調査

JDBCドライバーが見つからないという実行時エラーの原因を探ってみます。

オリジナルのJDBCドライバーのモジュール情報

D:\libs> jar --describe-module --file mssql-jdbc-7.4.1.jre11.jar
モジュール・ディスクリプタが見つかりません。自動モジュールが導出されました。

com.microsoft.sqlserver.jdbc@7.4.1.jre11 automatic
requires java.base mandated
provides java.sql.Driver with com.microsoft.sqlserver.jdbc.SQLServerDriver
contains com.microsoft.sqlserver.jdbc
contains com.microsoft.sqlserver.jdbc.dataclassification
contains com.microsoft.sqlserver.jdbc.dns
contains com.microsoft.sqlserver.jdbc.osgi
contains microsoft.sql
contains mssql.googlecode.cityhash
contains mssql.googlecode.concurrentlinkedhashmap
contains mssql.security.provider

module-info.classを後付けしたJDBCドライバーのモジュール情報

D:\work> jar --describe-module --file mssql-jdbc-7.4.1.jre11.jar
com.microsoft.sqlserver.jdbc jar:file:///D:/work/mssql-jdbc-7.4.1.jre11.jar/!module-info.class
exports com.microsoft.sqlserver.jdbc
requires java.base mandated
requires java.naming
requires java.security.jgss
requires java.sql
contains com.microsoft.sqlserver.jdbc.dataclassification
contains com.microsoft.sqlserver.jdbc.dns
contains com.microsoft.sqlserver.jdbc.osgi
contains microsoft.sql
contains mssql.googlecode.cityhash
contains mssql.googlecode.concurrentlinkedhashmap
contains mssql.security.provider

モジュール情報の違いを分析

元のmssql-jdbc-7.4.1.jre11.jarと、module-info.classを後付け挿入したmssql-jdbc-7.4.1.jre11.jarとの違いを見ると、今回の実行時エラー(ドライバーが見つからない)に関係しそうな相違点は、provies項です。

provides java.sql.Driver with com.microsoft.sqlserver.jdbc.SQLServerDriver

各種JDBCドライバーは、SPI(Service Provider Interface)で提供されており、モジュール仕様以前のクラスパスではJARファイルの中にMETA-INF/services/java.sql.Driver ファイルを置き、このファイルの中にSPIの実装クラスのFQCNを記載します。 mssql-jdbc-7.4.1.jre11.jar の場合は、このファイルの中に次の記述があります。

com.microsoft.sqlserver.jdbc.SQLServerDriver

このJARファイルがクラスパスにあると、サービスローダーがSPIの実装クラスを認識してロードすることができます。

モジュール仕様では、SPIの実装はモジュール定義(module-info.java)にprovides宣言で記述します。

module-info.classの生成とJARファイルへの後付け(試行2回目)

module-info.javaに、provides宣言を追加し、コンパイルしたmodule-info.classをJARファイルに追加します。

module-info.javaの再記述

module com.microsoft.sqlserver.jdbc {
    exports com.microsoft.sqlserver.jdbc;
    provides java.sql.Driver with com.microsoft.sqlserver.jdbc.SQLServerDriver;
    requires java.naming;
    requires java.security.jgss;
    requires java.sql;
}

module-info.classの再生成

D:\work> javac --patch-module com.microsoft.sqlserver.jdbc=mssql-jdbc-7.4.1.jre11.jar module-info.java

module-info.class を mssql-jdbc-7.4.1.jre11.jar に挿入

D:\work>jar uvf mssql-jdbc-7.4.1.jre11.jar module-info.class
module-infoが更新されました: module-info.class

モジュール情報の確認

D:\work> jar --describe-module --file mssql-jdbc-7.4.1.jre11.jar
com.microsoft.sqlserver.jdbc jar:file:///D:/work/mssql-jdbc-7.4.1.jre11.jar/!module-info.class
exports com.microsoft.sqlserver.jdbc
requires java.base mandated
requires java.naming
requires java.security.jgss
requires java.sql
provides java.sql.Driver with com.microsoft.sqlserver.jdbc.SQLServerDriver
contains com.microsoft.sqlserver.jdbc.dataclassification
contains com.microsoft.sqlserver.jdbc.dns
contains com.microsoft.sqlserver.jdbc.osgi
contains microsoft.sql
contains mssql.googlecode.cityhash
contains mssql.googlecode.concurrentlinkedhashmap
contains mssql.security.provider

provides項が見えています。

JLinkで配布イメージ生成後実行

SQL Serverに接続して無事データベースアプリケーションとして実行できるようになりました。

まとめ

  • Microsoft SQL Server用のJDBCドライバー(ライブラリ)は、自動モジュール(Automatic module)形式のJARファイルとして配布されている(Ver. 7.0以降)
  • 自動モジュールのJARは、モジュール対応アプリケーションからモジュール指定でコンパイル・実行ができる
  • 自動モジュールのJARを使用しているモジュール対応アプリケーションは、JLinkで配布イメージを生成することができない
  • 非モジュール(自動モジュールも含めて)のJARをモジュールJARにするには、module-info.javaを記述してコンパイルした後、JARファイルに追加する
  • module-info.javaを記述するには、jdepsコマンドで依存関係を解析する