#chiroito ’s blog

Java を中心とした趣味の技術について

GraalVM の native-imageで PostgreSQL の DataSource が使えない

要約

Java の native-image で PostgreSQL の JDBC ドライバに含まれる DataSource を使うには META-INF/native-image/serialization-config.json が必要です。

org.postgresql.ds.common.BaseDataSource を継承している全ての DataSource が対象になる可能性があります。

問題

以下のような PostgreSQL へ接続するアプリを作って native-image 化しました。

public class NativeImageTest {
    public static void main(String[] args) throws Exception {

        PGPoolingDataSource ds = new PGPoolingDataSource();

        ds.setUrl("jdbc:postgresql://localhost:5432/postgres");
        ds.setUser("postgres");
        ds.setPassword("postgres");

        try(Connection con = ds.getConnection();
            PreparedStatement stmt = con.prepareStatement("SELECT datname FROM pg_database");
            ResultSet rs = stmt.executeQuery()) {

            rs.next();
            System.out.println(rs.getString("datname"));
        }
    }
}

JVM上では問題なく動いたにもかかわらず、native-image では動きません。

Exception in thread "main" org.postgresql.util.PSQLException: Failed to setup DataSource.
        at org.postgresql.ds.PGPoolingDataSource.initialize(PGPoolingDataSource.java:275)
        at org.postgresql.ds.PGPoolingDataSource.getConnection(PGPoolingDataSource.java:331)
        at dev.chiroito.NativeImageTest.main(NativeImageTest.java:16)
Caused by: java.io.InvalidClassException: java.util.Properties; no valid constructor
        at java.base@11.0.19/java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:158)
        at java.base@11.0.19/java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:746)
        at java.base@11.0.19/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202)
        at java.base@11.0.19/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687)
        at java.base@11.0.19/java.io.ObjectInputStream.readObject(ObjectInputStream.java:489)
        at java.base@11.0.19/java.io.ObjectInputStream.readObject(ObjectInputStream.java:447)
        at org.postgresql.ds.common.BaseDataSource.readBaseObject(BaseDataSource.java:1582)
        at org.postgresql.ds.common.BaseDataSource.initializeFrom(BaseDataSource.java:1592)
        at org.postgresql.ds.PGPoolingDataSource.initialize(PGPoolingDataSource.java:273)
        ... 3 more

java.util.Properties クラスのデシリアライズができないことが原因です。

解決方法

シリアライズの設定を追加しましょう。シリアライズの設定は META-INF/native-image/serialization-config.json ファイルに記載します。

場所は以下の位置に置きましょう。

META-INF/native-image/serialization-config.json のファイルの中身は以下の通りです。java.util.Properties が継承している java.util.Hashtablejava.util.Dictionary も記載します。

[
  {
    "name":"java.util.Properties"
  },
  {
    "name":"java.util.Hashtable"
  },
  {
    "name":"java.util.Dictionary"
  }
]

クラスが足りないと以下のメッセージが出力されます。

Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: SerializationConstructorAccessor class not found for declaringClass: java.util.Hashtable (targetConstructorClass: java.util.Dictionary). Usually adding java.util.Hashtable to serialization-config.json fixes the problem.

記載が終わったら再び native-image 化すればOKです。