メインコンテンツへスキップ
メインコンテンツへスキップ

JDBC ドライバー

注記

clickhouse-jdbcは最新のJavaクライアントを使用して標準的なJDBCインターフェースを実装しています。 パフォーマンスや直接アクセスが重要な場合は、最新のJavaクライアントを直接使用することを推奨します。

環境要件

セットアップ

<!-- https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc -->
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.9.7</version>
    <classifier>all</classifier>
</dependency>

クラスパスにjarを追加する必要があるアプリケーションでJDBCドライバーを使用している場合は、以下からjarをダウンロードしてクラスパスに追加してください:

設定

ドライバークラス: com.clickhouse.jdbc.ClickHouseDriver

注記

com.clickhouse.jdbc.ClickHouseDriverは、新旧両方のJDBC実装のためのファサードクラスです。デフォルトでは新しいJDBC実装が使用されます。 システムプロパティ clickhouse.jdbc.v1true に設定することで、古いJDBC実装を使用できます。このプロパティはDriverクラスを呼び出す前に設定する必要があります。

バージョンを切り替える別の方法として、各バージョンの Driver クラスを直接使用することもできます。

  • com.clickhouse.jdbc.Driver は新しい JDBC 実装 (V2) です。
  • com.clickhouse.jdbc.DriverV1 は旧バージョンの JDBC 実装 (V1) です。

URL構文: jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1&param2=value2][#tag1,tag2,...]。例えば:

  • jdbc:clickhouse:http://localhost:8123
  • jdbc:clickhouse:https://localhost:8443?ssl=true

URL の構文については、次の点に注意してください:

  • URL にはエンドポイントを 1 つだけ指定できます
  • プロトコルがデフォルトの「HTTP」以外の場合は、明示的に指定する必要があります
  • デフォルトの '8123' 以外のポートを使用する場合は、そのポート番号を明示的に指定する必要があります
  • ドライバーはポート番号からプロトコルを推測しないため、プロトコルを必ず明示的に指定する必要があります
  • プロトコルを明示的に指定している場合は、ssl パラメーターを指定する必要はありません。

接続プロパティ

主要な設定パラメータはJavaクライアントで定義されています。これらはそのままドライバに渡す必要があります。ドライバにはクライアント設定に含まれない独自のプロパティがあり、以下に記載されています。

ドライバープロパティ:

プロパティデフォルト値説明
disable_frameworks_detectiontrueUser-Agent を用いたフレームワーク検出を無効にする
jdbc_ignore_unsupported_valuesfalseドライバーの動作に影響しない箇所では SQLFeatureNotSupportedException を抑制します
clickhouse.jdbc.v1false新しい JDBC 実装ではなく旧 JDBC 実装を使用する
default_query_settingsnullクエリ実行時にデフォルトのクエリ設定を渡せるようにする
jdbc_resultset_auto_closetrueStatement をクローズするときに ResultSet を自動的にクローズします
beta.row_binary_for_simple_insertfalseRowBinary writer に基づく PreparedStatement 実装を使用します。INSERT INTO ... VALUES クエリでのみ動作します。
jdbc_resultset_auto_closetrueStatement をクローズすると ResultSet も自動的にクローズされます
jdbc_use_max_result_rowsfalseサーバープロパティ max_result_rows によって、クエリが返す行数を制限できるようにします。有効にすると、ユーザーが設定したオーバーフローモードを上書きします。詳細は JavaDoc を参照してください。
jdbc_sql_parserJAVACC使用する SQL パーサーを設定します。選択肢は ANTLR4, ANTLR4_PARAMS_PARSER, JAVACC です。
remember_last_set_rolestrue接続に対して最後に設定されたロールを保持する。
サーバー設定

すべてのサーバー設定には clickhouse_setting_ のプレフィックスを付ける必要があります (クライアント設定と同様) 。

Properties config = new Properties();
config.setProperty("user", "default");
config.setProperty("password", getPassword());

// set server setting
config.put(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1");

Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", config);

設定例

Properties properties = new Properties();
properties.setProperty("user", "default");
properties.setProperty("password", getPassword());
properties.setProperty("client_name", "my-app-01"); // when http protocol is used it will be `http_user_agent` in the query log but not `client_name`.

Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", properties);

これは、次の JDBC URL と同等です:

jdbc:ch:http://localhost:8123/?user=default&password=password&client_name=my-app-01 
// credentials shoud be passed in `Properties`. Here it is just for example.

注記: JDBC URL や接続プロパティを URL エンコードする必要はありません。自動的にエンコードされます。

クライアント識別

リクエストの発信元アプリケーションを識別する方法は2つあります。接続プロパティで com.clickhouse.client.api.ClientConfigProperties#CLIENT_NAME を設定するか、java.sql.Connection#setClientInfo(String name, String value) メソッドを使用します。

Properties properties = new Properties();
properties.setProperty(ClientConfigProperties.CLIENT_NAME.getKey(), "my-app-01");
Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", properties);
conn.setClientInfo(com.clickhouse.jdbc.ClientInfoProperties.APPLICATION_NAME.getKey(), "my-app-01");

どちらの方法でも、クエリログの http_user_agent の値は次のようになります。

my-app-01/1.0 clickhouse-java-v2/0.9.6-SNAPSHOT (Linux; jvm:17.0.17) Apache-HttpClient/5.4.4

操作の識別

JDBCドライバーは各操作ごとに query_id を生成します (現在はサーバー例外に含まれています) 。

操作に対して log_comment を設定するには、com.clickhouse.jdbc.StatementImpl#getLocalSettings メソッドを使用します。これには、 Statement または PreparedStatement を事前に com.clickhouse.jdbc.StatementImpl にキャストする必要があります。

StatementImpl stmt = (StatementImpl) conn.createStatement();
stmt.getLocalSettings().logComment("some-comment");

注意: localSettings はスレッド間で共有されるため、この方法が有効なのはステートメントを単一スレッドで使用する場合に限られます。

サポートされるデータ型

JDBCドライバは基盤となるJavaクライアントと同じデータ形式をサポートしています。

JDBC型マッピング

以下のマッピングが適用されます:

  • ResultSet#getObject(columnIndex) メソッドは、対応する Java クラスのオブジェクトを返します (Int8 -> java.lang.ByteInt16 -> java.lang.Short など) 。
  • ResultSetMetaData#getColumnType(columnIndex) メソッドは、対応する JDBC 型を返します (Int8 -> java.lang.ByteInt16 -> java.lang.Short など) 。

マッピングを変更する方法はいくつかあります:

  • ResultSet#getObject(columnIndex, class) メソッドは、値を指定された class 型に変換しようとします。変換にはいくつかの制限があります。詳細は各セクションを参照してください。

数値型

ClickHouse 型JDBC 型Java クラス
Int8TINYINTjava.lang.Byte
Int16SMALLINTjava.lang.Short
Int32INTEGERjava.lang.Integer
Int64BIGINTjava.lang.Long
Int128OTHERjava.math.BigInteger
Int256OTHERjava.math.BigInteger
UInt8OTHERjava.lang.Short
UInt16OTHERjava.lang.Integer
UInt32OTHERjava.lang.Long
UInt64OTHERjava.math.BigInteger
UInt128OTHERjava.math.BigInteger
UInt256OTHERjava.math.BigInteger
Float32REALjava.lang.Float
Float64DOUBLEjava.lang.Double
Decimal32DECIMALjava.math.BigDecimal
Decimal64DECIMALjava.math.BigDecimal
Decimal128DECIMALjava.math.BigDecimal
Decimal256DECIMALjava.math.BigDecimal
BoolBOOLEANjava.lang.Boolean
  • 数値型同士は相互に変換可能です。たとえば、Int8Float64 として取得したり、その逆に Float64Int8 として取得することも可能です。
    • rs.getObject(1, Float64.class) は、Int8 カラムの値を Float64 型として返します。
    • rs.getLong(1) は、Int8 カラムの値を Long 型として返します。
    • rs.getByte(1) は、Int16 カラムの値が Byte の範囲に収まる場合、その値を Byte 型として返します。
  • より広い型からより狭い型への変換は、データ破損のリスクがあるため推奨されません。
  • Bool 型は数値型としても扱われます。
  • すべての数値型は java.lang.String として取得できます。
  • Java の Float.MAX_VALUEFloat として保存すると問題が発生します (https://github.com/ClickHouse/clickhouse-java/issues/809)。同じ値を Double として保存すると、この問題は解決します。

文字列型

ClickHouse 型JDBC 型Java クラス
StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.String
  • Stringjava.lang.String または byte[] としてのみ読み取れます。
  • FixedString は値をそのまま読み取り、カラムの長さに達するまで末尾をゼロバイト (\0) で埋めます。 (たとえば、FixedString(10)'John' を指定した場合、'John\0\0\0\0\0\0\0\0\0' として読み取られます。)

Enum型

ClickHouse 型JDBC 型Java クラス
Enum8OTHERjava.lang.String
Enum16OTHERjava.lang.String
  • Enum8 および Enum16 は、デフォルトで java.lang.String にマッピングされます。
  • Enum の値は、専用の getter メソッドまたは getObject(columnIndex, Integer.class) メソッドを使用して数値として読み取ることができます。
  • Enum16 は内部的には short 型に、Enum8byte 型にマッピングされます。データ破損のリスクがあるため、Enum16byte 型として読み出すことは避けてください。
  • Enum の値は、PreparedStatement で文字列または数値として設定できます。

日付/時刻型

ClickHouse 型JDBC 型Java クラス
DateDATEjava.sql.Date
Date32DATEjava.sql.Date
DateTimeTIMESTAMPjava.sql.Timestamp
DateTime64TIMESTAMPjava.sql.Timestamp
TimeTIMEjava.sql.Time
Time64TIMEjava.sql.Time
  • Date / Time 型は、JDBC との互換性を高めるために java.sql 型にマッピングされます。ただし、ResultSet#getObject(columnIndex, Class<T>) の第 2 引数として対応するクラスを指定することで、java.time.LocalDatejava.time.LocalDateTimejava.time.LocalTime を取得することも可能です。
    • rs.getObject(1, java.time.LocalDate.class) は、Date カラムの値を java.time.LocalDate 型で返します。
    • rs.getObject(1, java.time.LocalDateTime.class) は、DateTime カラムの値を java.time.LocalDateTime 型で返します。
    • rs.getObject(1, java.time.LocalTime.class) は、Time カラムの値を java.time.LocalTime 型で返します。
  • DateDate32TimeTime64 はサーバーのタイムゾーンの影響を受けません。
  • DateTime, DateTime64 はサーバーまたはセッションのタイムゾーンに依存します。
  • DateTime および DateTime64 は、getObject(colIndex, ZonedDateTime.class) を使用すると ZonedDateTime として取得できます。

ネスト型

ClickHouse 型JDBC 型Java クラス
ArrayARRAYjava.sql.Array
TupleOTHERcom.clickhouse.data.Tuple
MapJAVA_OBJECTjava.util.Map
NestedARRAYjava.sql.Array
  • Array は、JDBC との互換性を保つため、デフォルトでは java.sql.Array にマッピングされます。これは、返される配列値についてより多くの情報を提供できるようにする目的もあり、型推論に有用です。
  • ArraygetResultSet() メソッドを実装しており、元の配列と同じ内容を持つ java.sql.ResultSet を返します。
  • コレクション型を java.lang.String として読み取るべきではありません。配列内の文字列値に引用符が付かないなど、データの表現方法として妥当ではないためです。
  • Map は、値を getObject(columnIndex, Class<T>) メソッドでのみ読み取れるため、JAVA_OBJECT にマッピングされます。
    • Map は名前付きカラムを持たないため、java.sql.Struct 型ではありません。
  • Tuple は異なる型を含み得るため Object[] にマッピングされており、List として扱うことは適切ではありません。
  • TuplegetObject(columnIndex, Array.class) メソッドを使用することで Array として取得できます。この場合、Array#baseTypeNameTuple カラム定義を返します。

配列の書き込み

java.sql.Connection#createArrayOf を使用して java.sql.Array オブジェクトをインスタンス化してください。このオブジェクトは、異なるデータベース間で配列の扱いを統一するために設計されています。 Array のファクトリメソッドに設定を渡すには、Connection オブジェクトが必要です。

このメソッドは 2 つの引数を取ります。

  • typeName - 配列要素の型名。たとえば、Array(Int32) -> "Int32"
  • elements - 配列に含まれる実際の要素。例えば、[[1, 2, 3], [4, 5, 6]] -> new Integer[][] {{1, 2, 3}, {4, 5, 6}}

Tuple は Object[] または java.sql.Struct として表現できます (タプルの書き込み方法については下記を参照してください) 。

try (Connection conn = ...) {
    Array array = conn.createArrayOf("Int32", new Integer[][] {{1, 2, 3}, {4, 5, 6}});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (arr) VALUES (?)")) {
        ps.setArray(1, array);
        ps.executeUpdate();
    }
}

配列の読み取り

Array オブジェクトを読み取るには ResultSet#getArray(columnIndex) を使用してください。このオブジェクトを使用して、任意のネスト深度の配列にアクセスできます。 Array#getResultSet() メソッドを使用すると、配列要素を java.sql.ResultSet としてより統一的に読み取ることができます。配列要素の正確な型が不明な場合に便利です。

try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Array(Int32)")) {
        ps.setArray(1, array);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Array array = rs.getArray(1);

                Object[] arr = (Object[]) array;
                Arrays.stream(arr).forEach(this::handleArrayElement);

                // or by using `ResultSet`
                ResultSet resultSet = array.getResultSet();
                while (resultSet.next()) {
                    // ...
                }
            }
        }
    } 
}

Tuple の書き込み

タプルは com.clickhouse.data.Tuple オブジェクトにマッピングされ、setObject(columnIndex, tuple) メソッドを呼び出してこのオブジェクトとして書き込む必要があります。 移植性を高めるために、タプルの書き込みには java.sql.Struct オブジェクトを使用することもできます。

try (Connection conn = ...) {
    Tuple tuple = new Tuple(1, "test", LocalDate.parse("2026-03-02"));
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) {
        ps.setObject(1, tuple);
        ps.executeUpdate();
    }
}

try (Connection conn = ...) {
    Struct struct = conn.createStruct("Tuple(Int32, String, Date)", new Object[] {1, "test", LocalDate.parse("2026-03-02")});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) {
        ps.setStruct(1, struct);
        ps.executeUpdate();
    }
}

Tuple の読み取り

メソッド getObject(columnIndex)Object[] を返します。getObject(columnIndex, Array.class) メソッドを使用すると、タプルを java.sql.Array として読み取ることができます。

try (Connection conn = ...) {
    try (PreparedStatement stmt = conn.prepareStatement("SELECT ?::Tuple(String, Int32, Date)")) {
        Array tuple = conn.createArrayOf("Tuple(String, Int32, Date)",  new Object[]{"test", 123, LocalDate.parse("2026-03-02")});
        stmt.setObject(1, tuple);
        try (ResultSet rs = stmt.executeQuery()) {
            rs.next();
            Array dbTuple = rs.getArray(1);
            Assert.assertEquals(dbTuple, tuple);
            Object arr = rs.getObject(1);
            Assert.assertEquals(arr, tuple.getArray());
        }
    }
}

Mapの書き込み

Map はキーと値のペアを必要とするため (java.sql.Struct はキーと値のペアをサポートしません) 、java.collections.Map オブジェクトとしてのみ書き込めます。

try (Connection conn = ...) {
    Map<String, Integer> map = new HashMap<>();
    map.put("key1", 1);
    map.put("key2", 2);
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (map) VALUES (?)")) {
        ps.setObject(1, map);
        ps.executeUpdate();
    }
}

地図の読み方

Map 型は、getObject(columnIndex, Map.class) メソッドを使用することで java.collections.Map オブジェクトとして読み取れます。

try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Map(String, Int32)")) {
        ps.setStruct(1, struct);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Map<String, Integer> map = rs.getObject(1, Map.class);
                // ...
            }
        }
    }
}

Nested 型への書き込み

java.sql.Connection#createStruct を使用して java.sql.Struct オブジェクトをインスタンス化します。このオブジェクトは、複数のデータベース間でネスト構造の扱いを統一するために設計されています。 Struct のファクトリメソッドに設定を渡すには、Connection オブジェクトが必要です。

このメソッドは2つの引数を受け取ります:

  • typeName - ネストされた要素の型名。例えば Nested(Tuple(Int32, String)) -> "Nested(Tuple(Int32, String))"
  • elements - ネストされた要素そのものです。たとえば [1, 'test'] -> new Object[] {1, 'test'} となります。

try (Connection conn = ...) {
    Struct struct = conn.createStruct("Nested(Tuple(Int32, String))", new Object[] {1, 'test'});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (nested) VALUES (?)")) {
        ps.setStruct(1, struct);
        ps.executeUpdate();
    }
}

Nested 型の読み取り

Nested オブジェクトを読み取るには、ResultSet#getStruct(columnIndex, StructDescriptor) を使用します。このオブジェクトを使うと、任意の深さにネストされた要素へアクセスできます。 Struct#getResultSet() メソッドを使用すると、ネストされた要素を java.sql.ResultSet と同様の、より一貫した方法で読み取ることができます。これは、ネストされた要素の正確な型が不明な場合に有用です。

try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Nested(Tuple(Int32, String))")) {
        ps.setStruct(1, struct);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Struct struct = rs.getStruct(1);
                Object[] tuple = (Object[]) struct;
                Arrays.stream(tuple).forEach(this::handleTupleElement);

                // or by using `ResultSet`
                ResultSet resultSet = struct.getResultSet();
                while (resultSet.next()) {
                    // ...
                }
            }
        }
    }
}

地理型

ClickHouse 型JDBC 型Java クラス
PointOTHERdouble[]
RingOTHERdouble[][]
PolygonOTHERdouble[][][]
MultiPolygonOTHERdouble[][][][]

Nullable型とLowCardinality型

  • NullableLowCardinality は他の型をラップする特殊な型です。
  • Nullable は、ResultSetMetaData が返す型名の形式に影響します。

特殊型

ClickHouse 型JDBC 型Java クラス
UUIDOTHERjava.util.UUID
IPv4OTHERjava.net.Inet4Address
IPv6OTHERjava.net.Inet6Address
JSONOTHERjava.lang.String
AggregateFunctionOTHER(バイナリ表現)
SimpleAggregateFunction(ラップされた型)(ラップされたクラス)
  • UUID は JDBC 標準の型ではありませんが、JDK に含まれています。デフォルトでは、getObject() メソッドから java.util.UUID が返されます。
  • getObject(columnIndex, String.class) メソッドを使用すると、UUIDString として読み書きできます。
  • IPv4IPv6 は JDBC の標準型ではありませんが、JDK の一部です。デフォルトでは、getObject() メソッドで取得すると java.net.Inet4Addressjava.net.Inet6Address が返されます。
  • IPv4 および IPv6 は、getObject(columnIndex, String.class) メソッドを使用することで String として読み書きできます。

日付、時刻、タイムゾーンの処理

ドライバーが日付/時刻およびタイムスタンプを処理する際の一般的な落とし穴や動作ロジックについては、日付/時刻ガイドを参照してください。

接続の作成

String url = "jdbc:ch://my-server:8123/system";

Properties properties = new Properties();
DataSource dataSource = new DataSource(url, properties);//DataSource or DriverManager are the main entry points
try (Connection conn = dataSource.getConnection()) {
... // do something with the connection

認証情報と設定の指定

String url = "jdbc:ch://localhost:8123?jdbc_ignore_unsupported_values=true&socket_timeout=10";

Properties info = new Properties();
info.put("user", "default");
info.put("password", "password");
info.put("database", "some_db");

//Creating a connection with DataSource
DataSource dataSource = new DataSource(url, info);
try (Connection conn = dataSource.getConnection()) {
... // do something with the connection
}

//Alternate approach using the DriverManager
try (Connection conn = DriverManager.getConnection(url, info)) {
... // do something with the connection
}

単純なステートメント


try (Connection conn = dataSource.getConnection(...);
    Statement stmt = conn.createStatement()) {
    ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
    while(rs.next()) {
        // ...
    }
}

Insert

try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable VALUES (?, ?)")) {
    ps.setString(1, "test"); // id
    ps.setObject(2, LocalDateTime.now()); // timestamp
    ps.addBatch();
    ...
    ps.executeBatch(); // stream everything on-hand into ClickHouse
}

HikariCP

// connection pooling won't help much in terms of performance,
// because the underlying implementation has its own pool.
// for example: HttpURLConnection has a pool for sockets
HikariConfig poolConfig = new HikariConfig();
poolConfig.setConnectionTimeout(5000L);
poolConfig.setMaximumPoolSize(20);
poolConfig.setMaxLifetime(300_000L);
poolConfig.setDataSource(new ClickHouseDataSource(url, properties));

try (HikariDataSource ds = new HikariDataSource(poolConfig);
     Connection conn = ds.getConnection();
     Statement s = conn.createStatement();
     ResultSet rs = s.executeQuery("SELECT * FROM system.numbers LIMIT 3")) {
    while (rs.next()) {
        // handle row
        log.info("Integer: {}, String: {}", rs.getInt(1), rs.getString(1));//Same column but different types
    }
}

詳細情報

詳細については、GitHubリポジトリおよびJavaクライアントのドキュメントを参照してください。

トラブルシューティング

ログ

このドライバはログに slf4j を使用し、classpath 上で最初に利用可能な実装を使用します。

大量挿入時のJDBCタイムアウトの解決

ClickHouseで実行時間が長くなる大規模な挿入 (INSERT) を行う際、次のようなJDBCタイムアウトエラーが発生することがあります:

Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]

これらのエラーはデータ挿入プロセスを妨げ、システムの安定性に影響を与える可能性があります。この問題に対処するには、クライアントのOS上のいくつかのタイムアウト設定を調整する必要がある場合があります。

Mac OS

macOS では、以下の設定値を調整することでこの問題を解消できます。

  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1

Linux

Linuxでは、同等の設定を行っただけでは問題が解決しない場合があります。Linuxにおけるソケットのキープアライブ設定の扱いが異なるため、追加の手順が必要です。以下の手順に従ってください:

  1. /etc/sysctl.conf または関連する設定ファイルで、以下の Linux カーネルパラメータを調整します:
  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1
  • net.ipv4.tcp_keepalive_intvl: 75
  • net.ipv4.tcp_keepalive_probes: 9
  • net.ipv4.tcp_keepalive_time: 60 (デフォルトの 300 秒から短くすることも検討できます)
  1. カーネルパラメータを変更したら、次のコマンドを実行して変更内容を反映させます:
sudo sysctl -p

これらの設定を行った後は、クライアント側でソケットの Keep Alive オプションが有効になっていることを確認する必要があります:

properties.setProperty("socket_keepalive", "true");

移行ガイド

主な変更点

機能V1 (旧)V2 (新)
トランザクション対応一部サポートサポートなし
レスポンスカラム名の変更一部サポートサポートなし
マルチステートメントSQLサポートなし許可されていない
名前付きパラメーターサポートありサポートなし (JDBC仕様に含まれない)
PreparedStatement を使ったストリーミングデータサポートありサポートなし
  • JDBC V2 はより軽量な実装として設計されており、その過程で一部の機能が削除されています。
    • ストリーミングデータは JDBC 仕様や Java 自体の機能には含まれていないため、JDBC V2 ではサポートされません。
  • JDBC V2 では明示的な設定が必要です。フェイルオーバー用のデフォルト設定は用意されていません。
    • プロトコルは URL 内で明示的に指定する必要があります。ポート番号に基づいてプロトコルを暗黙的に判別することはありません。

設定変更

列挙型は次の2種類のみです:

  • com.clickhouse.jdbc.DriverProperties - ドライバー独自の設定プロパティです。
  • com.clickhouse.client.api.ClientConfigProperties - クライアント構成プロパティです。クライアント構成の変更点については、Java クライアントのドキュメントを参照してください。

接続プロパティは次のように解析されます:

  • まず URL からプロパティを解析し、そこで指定された値が他のすべてのプロパティより優先されます。
  • ドライバーのプロパティはクライアント側には渡されません。
  • エンドポイント (ホスト、ポート、プロトコル) は URL から解析して取得されます。

例:

String url = "jdbc:ch://my-server:8443/default?" +
            "jdbc_ignore_unsupported_values=true&" +
            "socket_rcvbuf=800000";

Properties properties = new Properties();
properties.setProperty("socket_rcvbuf", "900000");
try (Connection conn = DriverManager.getConnection(url, properties)) {
    // Connection will use socket_rcvbuf=800000 and jdbc_ignore_unsupported_values=true
    // Endpoints: my-server:8443 protocol: http (not secure)
    // Database: default
}

データ型の変更点

数値型

ClickHouse 型V1 との互換性JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
Int8TINYINTjava.lang.ByteTINYINTjava.lang.Byte
Int16SMALLINTjava.lang.ShortSMALLINTjava.lang.Short
Int32INTEGERjava.lang.IntegerINTEGERjava.lang.Integer
Int64BIGINTjava.lang.LongBIGINTjava.lang.Long
Int128OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
Int256OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
UInt8OTHERjava.lang.ShortOTHERcom.clickhouse.data.value.UnsignedByte
UInt16OTHERjava.lang.IntegerOTHERcom.clickhouse.data.value.UnsignedShort
UInt32OTHERjava.lang.LongOTHERcom.clickhouse.data.value.UnsignedInteger
UInt64OTHERjava.math.BigIntegerOTHERcom.clickhouse.data.value.UnsignedLong
UInt128OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
UInt256OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
Float32REALjava.lang.FloatREALjava.lang.Float
Float64DOUBLEjava.lang.DoubleDOUBLEjava.lang.Double
Decimal32DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
Decimal64DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
Decimal128DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
Decimal256DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
BoolBOOLEANjava.lang.BooleanBOOLEANjava.lang.Boolean
  • 最大の違いは、移植性を高めるために符号なし型が Java の型にマッピングされるようになった点です。

文字列型

ClickHouse 型V1 との互換性JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
StringVARCHARjava.lang.StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.StringVARCHARjava.lang.String
  • FixedString は両方のバージョンで、そのままの値として読み出されます。たとえば、'John' を格納した FixedString(10)'John\0\0\0\0\0\0\0\0\0' として読み出されます。
  • PreparedStatement#setBytes が使用されると、値は unhex('<hex_string>') に変換され、その結果が String として読み取られます。
  • 文字列は UTF-8 でエンコードされて保存されます。

日付/時刻型

ClickHouse 型V1 との互換性JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
DateDATEjava.sql.DateDATEjava.time.LocalDate
Date32DATEjava.sql.DateDATEjava.time.LocalDate
DateTimeTIMESTAMPjava.sql.TimestampTIMESTAMPjava.time.OffsetDateTime
DateTime64TIMESTAMPjava.sql.TimestampTIMESTAMPjava.time.OffsetDateTime
TimeTIMEjava.sql.Time新しい型/未サポート新しい型/未サポート
Time64TIMEjava.sql.Time新しい型/サポート対象外新しい型/サポート対象外
  • TimeTime64 は、V2 でのみサポートされる新しい型です。
  • DateTimeDateTime64 は、JDBC との互換性を高めるために java.sql.Timestamp にマッピングされます。

Enum型

ClickHouse 型V1 との互換性JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
EnumVARCHARjava.lang.StringOTHERjava.lang.String
Enum8VARCHARjava.lang.StringOTHERjava.lang.String
Enum16VARCHARjava.lang.StringOTHERjava.lang.String

ネスト型

ClickHouse 型V1 との互換性JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
ArrayARRAYjava.sql.ArrayARRAYObject[] またはプリミティブ型配列
TupleOTHERObject[]STRUCTjava.sql.Struct
MapJAVA_OBJECTjava.util.MapSTRUCTjava.util.Map
NestedARRAYjava.sql.ArraySTRUCTjava.sql.Struct
  • In V2 では、Array は JDBC との互換性を維持するため、デフォルトで java.sql.Array にマッピングされます。これは返される配列値に関する情報量を増やし、型推論に役立てるためでもあります。
  • In V2 では、ArraygetResultSet() メソッドを実装し、元の配列と同一の内容を持つ java.sql.ResultSet を返します。
  • V1 では Map に対して STRUCT を使用していますが、常に java.util.Map オブジェクトを返します。V2 では、MapJAVA_OBJECT にマッピングすることでこの問題を解消しています。
  • V1 は Tuple に対して STRUCT を使用しますが、常に List<Object> オブジェクトを返します。V2 では TupleOTHER にマッピングし、デフォルトで Object[] を返します。
  • V2ではタプルの書き込みに使用する com.clickhouse.data.Tuple#Tuple が導入されました。これにより、値がタプルか配列かを簡単に判別できるようになりました。
  • PreparedStatement#setBytesResultSet#getBytes はコレクション型に対しては使用できません。これらのメソッドはバイト列を扱うためのものです。
  • 通常、Array 型の読み書きには java.sql.Array を使用します。JDBC ドライバーはこれに完全対応しています。
  • V2 では NestedArray にマッピングされ、タプルの配列として扱われます。
  • V2 では java.sql.Struct に対するサポートは部分的なものにとどまります。これは、Struct が Array 型と非常によく似た型であり、キーと値のペアをサポートしないためです。StructTuple 値の書き込みに使用できます。

地理型

ClickHouse 型V1 との互換性JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
PointOTHERdouble[]OTHERdouble[]
RingOTHERdouble[][]OTHERdouble[][]
PolygonOTHERdouble[][][]OTHERdouble[][][]
MultiPolygonOTHERdouble[][][][]OTHERdouble[][][][]

Nullable型とLowCardinality型

  • NullableLowCardinality は他の型をラップする特殊な型です。
  • V2 ではこれらの型に変更はありません。

特殊型

ClickHouse 型V1 互換JDBC 型 (V2)Java クラス (V2)JDBC 型 (V1)Java クラス (V1)
JSONOTHERjava.lang.Stringサポートされていませんサポートされていません
AggregateFunctionOTHER(バイナリ表現)OTHER(バイナリ表現)
SimpleAggregateFunction(ラップされた型)(ラップされたクラス)(ラップされた型)(ラップされたクラス)
UUIDOTHERjava.util.UUIDVARCHARjava.util.UUID
IPv4OTHERjava.net.Inet4AddressVARCHARjava.net.Inet4Address
IPv6OTHERjava.net.Inet6AddressVARCHARjava.net.Inet6Address
DynamicOTHERjava.lang.Objectサポートなしサポートなし
VariantOTHERjava.lang.Objectサポートなしサポートなし
  • V1 では UUID に対して VARCHAR を使用しますが、常に java.util.UUID オブジェクトを返します。V2 では、UUIDOTHER にマッピングすることでこの問題を解消し、java.util.UUID オブジェクトを返します。
  • V1 では IPv4IPv6 に対して VARCHAR を使用しますが、常に java.net.Inet4Address および java.net.Inet6Address オブジェクトを返します。V2 では、IPv4IPv6OTHER にマッピングすることでこの問題を解消し、java.net.Inet4Address および java.net.Inet6Address オブジェクトを返します。
  • DynamicVariant は V2 で導入された新しい型です。V1 ではサポートされていません。
  • JSONDynamic 型を基盤としているため、V2 でのみサポートされています。
  • IPv4およびIPv6の値は、getBytes(columnIndex) メソッドを使用して byte[] として読み取ることもできます。ただし、これらの型については専用クラスを使用することが推奨されます。
  • V2 では、IP アドレスを数値として読み取ることはサポートしていません。IP アドレスの数値への変換は、InetAddress クラス群で行う方がより適切な実装と考えられているためです。

データベースメタデータの変更

  • V2 ではデータベースを指す名称として Schema のみを使用します。Catalog という名称は将来のために予約されています。
  • V2 は DatabaseMetaData.supportsTransactions() および DatabaseMetaData.supportsSavepoints() に対して false を返します。これは今後の開発で変更される予定です。

clickhouse-jdbcは標準のJDBCインターフェースを実装しています。clickhouse-clientの上に構築されており、カスタム型マッピング、トランザクションサポート、標準的な同期のUPDATEおよびDELETE文などの追加機能を提供するため、レガシーなアプリケーションやツールでも簡単に使用できます。

注記

最新のJDBC (0.7.2) バージョンはClient-V1を使用しています

clickhouse-jdbc APIは同期的に動作し、一般に SQL の解析や型のマッピング/変換などのオーバーヘッドが大きくなります。パフォーマンスが重要な場合、または ClickHouse へより直接的にアクセスしたい場合は、clickhouse-clientの使用を検討してください。

環境要件

セットアップ

<!-- https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc -->
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.7.2</version>
    <!-- すべての依存関係を含む Uber JAR を使用します。JAR を小さくするには classifier を http に変更してください -->
    <classifier>shaded-all</classifier>
</dependency>

バージョン 0.5.0 以降は、クライアントに同梱されている Apache HTTP Client を使用しています。このパッケージには共有ライブラリ版がないため、ロガーを依存関係として追加する必要があります。

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.16</version>
</dependency>

設定

ドライバークラス: com.clickhouse.jdbc.ClickHouseDriver

URL構文: jdbc:(ch|clickhouse)[:<protocol>]://endpoint1[,endpoint2,...][/<database>][?param1=value1&param2=value2][#tag1,tag2,...]。例えば:

  • jdbc:ch://localhostjdbc:clickhouse:http://localhost:8123 と等価です
  • jdbc:ch:https://localhostjdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICT と同じです。
  • jdbc:ch:grpc://localhostjdbc:clickhouse:grpc://localhost:9100 と同じです

接続プロパティ:

プロパティデフォルト値説明
continueBatchOnErrorfalseエラー発生時にバッチ処理を継続するかどうか
createDatabaseIfNotExistfalseデータベースが存在しない場合に作成するかどうか
custom_http_headersカンマ区切りで指定するカスタム HTTP ヘッダー。例:User-Agent=client1,X-Gateway-Id=123
custom_http_paramsカンマ区切りのカスタム HTTP クエリパラメータ。例:extremes=0,max_result_rows=100
nullAsDefault00 - null 値はそのまま扱い、null を許容しないカラムに null を挿入しようとした場合は例外をスローする; 1 - null 値はそのまま扱い、挿入時の null チェックを無効にする; 2 - クエリおよび挿入の両方で、null を対応するデータ型のデフォルト値に置き換える
jdbcCompliancetrue標準的な同期型の UPDATE/DELETE 文および疑似トランザクションをサポートするかどうか
typeMappingsClickHouse データ型と Java クラスの対応付けをカスタマイズします。これにより、getColumnType() および getObject(Class<>?>) の両方の戻り値に影響を与えます。例えば、UInt128=java.lang.String,UInt256=java.lang.String
wrapperObjectfalsegetObject() が Array / Tuple 型に対して java.sql.Array / java.sql.Struct を返すかどうかを指定します。

注記: 詳細についてはJDBC固有の設定を参照してください。

サポートされるデータ型

JDBCドライバは、クライアントライブラリと同じデータ形式をサポートします。

注記
  • AggregatedFunction - ⚠️ SELECT * FROM table ... はサポートされません
  • Decimal 型 - 一貫性を保つため、バージョン 21.9 以降では SET output_format_decimal_trailing_zeros=1 を設定します
  • Enum - 文字列値および整数値の両方として扱える
  • UInt64 - client-v1 では long 型にマッピングされます

接続の作成

String url = "jdbc:ch://my-server/system"; // use http protocol and port 8123 by default

Properties properties = new Properties();

ClickHouseDataSource dataSource = new ClickHouseDataSource(url, properties);
try (Connection conn = dataSource.getConnection("default", "password");
    Statement stmt = conn.createStatement()) {
}

単純なステートメント


try (Connection conn = dataSource.getConnection(...);
    Statement stmt = conn.createStatement()) {
    ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
    while(rs.next()) {
        // ...
    }
}

Insert

注記
  • Statement ではなく PreparedStatement を使用してください

使用は容易ですが、input関数と比較してパフォーマンスが劣ります(以下を参照):

try (PreparedStatement ps = conn.prepareStatement("insert into mytable(* except (description))")) {
    ps.setString(1, "test"); // id
    ps.setObject(2, LocalDateTime.now()); // timestamp
    ps.addBatch(); // parameters will be write into buffered stream immediately in binary format
    ...
    ps.executeBatch(); // stream everything on-hand into ClickHouse
}

入力テーブル関数の使用

高い性能を持つオプション:

try (PreparedStatement ps = conn.prepareStatement(
    "insert into mytable select col1, col2 from input('col1 String, col2 DateTime64(3), col3 Int32')")) {
    // The column definition will be parsed so the driver knows there are 3 parameters: col1, col2 and col3
    ps.setString(1, "test"); // col1
    ps.setObject(2, LocalDateTime.now()); // col2, setTimestamp is slow and not recommended
    ps.setInt(3, 123); // col3
    ps.addBatch(); // parameters will be write into buffered stream immediately in binary format
    ...
    ps.executeBatch(); // stream everything on-hand into ClickHouse
}

プレースホルダーを使用した挿入

このオプションは小規模な INSERT の場合にのみ推奨されます。クライアント側でパースされて CPU とメモリを消費する長い SQL 式が必要になるためです:

try (PreparedStatement ps = conn.prepareStatement("insert into mytable values(trim(?),?,?)")) {
    ps.setString(1, "test"); // id
    ps.setObject(2, LocalDateTime.now()); // timestamp
    ps.setString(3, null); // description
    ps.addBatch(); // append parameters to the query
    ...
    ps.executeBatch(); // issue the composed query: insert into mytable values(...)(...)...(...)
}

DateTimeとタイムゾーンの取り扱い

java.sql.Timestampの代わりにjava.time.LocalDateTimeまたはjava.time.OffsetDateTimeを、java.sql.Dateの代わりにjava.time.LocalDateを使用してください。

try (PreparedStatement ps = conn.prepareStatement("select date_time from mytable where date_time > ?")) {
    ps.setObject(2, LocalDateTime.now());
    ResultSet rs = ps.executeQuery();
    while(rs.next()) {
        LocalDateTime dateTime = (LocalDateTime) rs.getObject(1);
    }
    ...
}

AggregateFunctionの扱い

注記

現時点では、groupBitmap のみサポートされています。

// batch insert using input function
try (ClickHouseConnection conn = newConnection(props);
        Statement s = conn.createStatement();
        PreparedStatement stmt = conn.prepareStatement(
                "insert into test_batch_input select id, name, value from input('id Int32, name Nullable(String), desc Nullable(String), value AggregateFunction(groupBitmap, UInt32)')")) {
    s.execute("drop table if exists test_batch_input;"
            + "create table test_batch_input(id Int32, name Nullable(String), value AggregateFunction(groupBitmap, UInt32))engine=Memory");
    Object[][] objs = new Object[][] {
            new Object[] { 1, "a", "aaaaa", ClickHouseBitmap.wrap(1, 2, 3, 4, 5) },
            new Object[] { 2, "b", null, ClickHouseBitmap.wrap(6, 7, 8, 9, 10) },
            new Object[] { 3, null, "33333", ClickHouseBitmap.wrap(11, 12, 13) }
    };
    for (Object[] v : objs) {
        stmt.setInt(1, (int) v[0]);
        stmt.setString(2, (String) v[1]);
        stmt.setString(3, (String) v[2]);
        stmt.setObject(4, v[3]);
        stmt.addBatch();
    }
    int[] results = stmt.executeBatch();
    ...
}

// use bitmap as query parameter
try (PreparedStatement stmt = conn.prepareStatement(
    "SELECT bitmapContains(my_bitmap, toUInt32(1)) as v1, bitmapContains(my_bitmap, toUInt32(2)) as v2 from {tt 'ext_table'}")) {
    stmt.setObject(1, ClickHouseExternalTable.builder().name("ext_table")
            .columns("my_bitmap AggregateFunction(groupBitmap,UInt32)").format(ClickHouseFormat.RowBinary)
            .content(new ByteArrayInputStream(ClickHouseBitmap.wrap(1, 3, 5).toBytes()))
            .asTempTable()
            .build());
    ResultSet rs = stmt.executeQuery();
    Assert.assertTrue(rs.next());
    Assert.assertEquals(rs.getInt(1), 1);
    Assert.assertEquals(rs.getInt(2), 0);
    Assert.assertFalse(rs.next());
}

HTTPライブラリの設定

ClickHouse JDBCコネクタは、HttpClientHttpURLConnection、およびApache HttpClientの3つのHTTPライブラリをサポートしています。

注記

HttpClient は JDK 11 以降でのみサポートされています。

JDBCドライバーはデフォルトでHttpClientを使用します。次のプロパティを設定して、ClickHouse JDBCコネクタが使用するHTTPライブラリを変更できます。

properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");

対応する値の一覧は以下のとおりです:

プロパティ値HTTP ライブラリ
HTTP_CLIENTHttpClient
HTTP_URL_CONNECTIONHttpURLConnection
APACHE_HTTP_CLIENTApache HttpClient

SSL を使用して ClickHouse に接続する

SSLを使用してClickHouseへの安全なJDBC接続を確立するには、JDBCプロパティにSSLパラメータを含めるように設定する必要があります。通常、JDBC URLやPropertiesオブジェクトにsslmodesslrootcertなどのSSLプロパティを指定します。

SSLプロパティ

名前デフォルト値選択可能な値説明
sslfalsetrue, false接続で SSL/TLS を有効にするかどうか
sslmodestrictstrict, noneSSL/TLS 証明書を検証するかどうか
sslrootcertSSL/TLS ルート証明書へのパス
sslcertSSL/TLS 証明書ファイルへのパス
sslkeyPKCS#8 形式の RSA 鍵
key_store_typeJKS, PKCS12KeyStore/TrustStore ファイルの種類または形式を指定します
trust_storeTrustStore ファイルへのパス
key_store_passwordKeyStore 設定で指定された KeyStore ファイルにアクセスするためのパスワード

これらのプロパティを設定することで、Java アプリケーションと ClickHouse サーバー間の通信が暗号化された接続経由で行われ、送信中のデータのセキュリティが強化されます。

  String url = "jdbc:ch://your-server:8443/system";

  Properties properties = new Properties();
  properties.setProperty("ssl", "true");
  properties.setProperty("sslmode", "strict"); // NONE to trust all servers; STRICT for trusted only
  properties.setProperty("sslrootcert", "/mine.crt");
  try (Connection con = DriverManager
          .getConnection(url, properties)) {

      try (PreparedStatement stmt = con.prepareStatement(

          // place your code here

      }
  }

大量挿入時のJDBCタイムアウトの解決

ClickHouseで実行時間が長くなる大規模な挿入 (INSERT) を行う際、次のようなJDBCタイムアウトエラーが発生することがあります:

Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]

これらのエラーはデータ挿入プロセスを中断させ、システムの安定性に影響を及ぼす可能性があります。この問題に対処するには、クライアントOS側のタイムアウト関連設定をいくつか調整する必要があります。

Mac OS

macOSでは、以下の設定を調整することで問題を解決できます:

  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1

Linux

Linuxでは、同等の設定のみでは問題が解決しない場合があります。Linuxがソケットのキープアライブ設定を扱う方法の違いにより、追加の手順が必要です。以下の手順に従ってください:

  1. /etc/sysctl.conf または関連する設定ファイルで次の Linux カーネルパラメータを調整します。
  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1
  • net.ipv4.tcp_keepalive_intvl: 75
  • net.ipv4.tcp_keepalive_probes: 9
  • net.ipv4.tcp_keepalive_time: 60 (この値をデフォルトの 300 秒から短くすることも検討できます)
  1. カーネルパラメータを変更したら、次のコマンドを実行して変更を適用します:
sudo sysctl -p

これらの設定を行った後、クライアント側でソケットの Keep-Alive オプションが有効になっていることを確認する必要があります:

properties.setProperty("socket_keepalive", "true");
注記

現在、ソケットのキープアライブを設定する際は Apache HTTP Client ライブラリを使用する必要があります。clickhouse-java がサポートする他の2つの HTTP クライアントライブラリではソケットオプションの設定ができないためです。詳細なガイドは HTTPライブラリの設定 を参照してください。

または JDBC URL に同等のパラメータを追加することもできます。

JDBCドライバーのデフォルトのソケットおよび接続タイムアウト値は30秒です。大規模なデータ挿入操作をサポートするために、このタイムアウト値を長く設定できます。ClickHouseClientoptionsメソッドを使用し、ClickHouseClientOptionで定義されているSOCKET_TIMEOUTおよびCONNECTION_TIMEOUTオプションを指定してください:

final int MS_12H = 12 * 60 * 60 * 1000; // 12 h in ms
final String sql = "insert into table_a (c1, c2, c3) select c1, c2, c3 from table_b;";

try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP)) {
    client.read(servers).write()
        .option(ClickHouseClientOption.SOCKET_TIMEOUT, MS_12H)
        .option(ClickHouseClientOption.CONNECTION_TIMEOUT, MS_12H)
        .query(sql)
        .executeAndWait();
}