跳到主要内容

JDBC 驱动

clickhouse-jdbc 实现标准 JDBC 接口。它建立在 clickhouse-client 之上,提供诸如自定义类型映射、事务支持以及标准同步 UPDATEDELETE 语句等附加功能,因此可以轻松地与旧版应用程序和工具一起使用。

注意
Latest JDBC (0.6.5) version uses 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.6.5</version>
<!-- use uber jar with all dependencies included, change classifier to http for smaller jar -->
<classifier>all</classifier>
</dependency>

从版本 0.5.0 开始,我们使用打包了客户端的 Apache HTTP 客户端。由于没有共享版本的包,您需要添加一个日志记录器作为依赖项。

<!-- 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://127.0.0.1 等同于 jdbc:clickhouse:https://127.0.0.1:8123
  • jdbc:ch:https://127.0.0.1 等同于 jdbc:clickhouse:https://127.0.0.1:8443?ssl=true&sslmode=STRICT
  • jdbc:ch:grpc://127.0.0.1 等同于 jdbc:clickhouse:grpc://127.0.0.1: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 - 将空值按原样处理,并在将空值插入非空列时抛出异常;1 - 将空值按原样处理,并禁用插入时的空值检查;2 - 将空值替换为对应数据类型的默认值,适用于查询和插入
jdbcCompliancetrue是否支持标准同步 UPDATE/DELETE 和伪事务
typeMappings自定义 ClickHouse 数据类型和 Java 类之间的映射,这将影响 getColumnType()getObject(Class<?>) 的结果。例如:UInt128=java.lang.String,UInt256=java.lang.String
wrapperObjectfalse是否 getObject() 应为 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 - 映射到 long(在 client-v1 中)

创建连接

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()) {
// ...
}
}

插入

注意
  • 使用 PreparedStatement 而不是 Statement

它更易于使用,但与输入函数相比性能较慢(见下文)

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
}

使用占位符插入

此选项仅推荐用于小型插入,因为它需要一个很长的 SQL 表达式(将在客户端解析,并且会消耗 CPU 和内存)

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(...)(...)...(...)
}

处理日期时间和时区

请使用 java.time.LocalDateTimejava.time.OffsetDateTime 而不是 java.sql.Timestamp,以及 java.time.LocalDate 而不是 java.sql.Date

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 连接器支持三个 HTTP 库:HttpClientHttpURLConnectionApache HttpClient

注意

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 对象中指定 SSL 属性,例如 sslmodesslrootcert

SSL 属性

名称默认值可选值描述
sslfalsetrue, false是否为连接启用 SSL/TLS
sslmodestrictstrict, none是否验证 SSL/TLS 证书
sslrootcertSSL/TLS 根证书路径
sslcertSSL/TLS 证书路径
sslkeyPKCS#8 格式的 RSA 密钥
key_store_typeJKS, PKCS12指定密钥库/信任库文件的类型或格式
trust_store信任库文件路径
key_store_password访问密钥库配置文件中指定的密钥库文件所需的密码

这些属性确保您的 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 执行大型插入且执行时间很长时,您可能会遇到 JDBC 超时错误,例如

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

这些错误会中断数据插入过程并影响系统稳定性。要解决此问题,您需要调整客户端操作系统中的一些超时设置。

Mac OS

在 Mac OS 上,可以调整以下设置来解决此问题

  • 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 秒降低)
  2. 修改内核参数后,通过运行以下命令应用更改

    sudo sysctl -p

设置完这些设置后,您需要确保客户端在套接字上启用了保持活动选项

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

目前,您必须在设置套接字保持活动时使用 Apache HTTP 客户端库,因为 clickhouse-java 支持的另外两个 HTTP 客户端库不允许设置套接字选项。有关详细指南,请参见 配置 HTTP 库

或者,您可以在 JDBC URL 中添加等效参数。

JDBC 驱动程序的默认套接字和连接超时时间为 30 秒。可以增加超时时间以支持大型数据插入操作。使用 ClickHouseClient 上的 options 方法,以及 ClickHouseClientOption 定义的 SOCKET_TIMEOUTCONNECTION_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();
}