はじめに
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コマンドで依存関係を解析する