JDBC 驱动
clickhouse-jdbc 使用最新的 Java 客户端实现了标准 JDBC 接口。
如果性能或直接访问至关重要,建议直接使用最新的 Java 客户端。
环境要求
- OpenJDK 8 或更高版本
设置
- Maven
- Gradle(Kotlin)
- Gradle
配置
驱动程序类: com.clickhouse.jdbc.ClickHouseDriver
com.clickhouse.jdbc.ClickHouseDriver 是新旧 JDBC 实现的外观类,默认使用新的 JDBC 实现。
如需使用旧的 JDBC 实现,可在连接属性中将 clickhouse.jdbc.v1 属性设置为 true。
com.clickhouse.jdbc.Driver 是新版 JDBC 实现。
com.clickhouse.jdbc.DriverV1 是旧版 JDBC 实现。
URL 语法: jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...],例如:
jdbc:clickhouse:http://localhost:8123jdbc:clickhouse:https://localhost:8443?ssl=true
关于 URL 语法,有以下几点需要注意:
- URL 中只允许包含一个 endpoint
- 当所用协议不是默认的 'HTTP' 时,应显式指定协议
- 当端口不是默认值 '8123' 时,必须显式指定端口。
- 驱动程序不会根据端口推断协议,你需要显式指定协议。
- 如果已指定协议,则无需设置
ssl参数。
连接属性
主要配置参数在 java client 中定义。这些参数应按原样传递给驱动程序。驱动程序还有一些自有属性,它们不属于客户端配置的一部分,具体列举如下。
驱动属性:
| 属性 | 默认值 | 描述 |
|---|---|---|
disable_frameworks_detection | true | 禁用基于 User-Agent 的框架识别 |
jdbc_ignore_unsupported_values | false | 在不影响驱动程序工作的情况下忽略 SQLFeatureNotSupportedException 异常 |
clickhouse.jdbc.v1 | false | 使用旧版 JDBC 实现,而不是新版 JDBC 实现 |
default_query_settings | null | 允许在查询中传递默认查询设置 |
jdbc_resultset_auto_close | true | 在关闭 Statement 时,会自动关闭 ResultSet |
beta.row_binary_for_simple_insert | false | 使用基于 RowBinary 写入器的 PreparedStatement 实现。仅对 INSERT INTO ... VALUES 查询有效。 |
jdbc_resultset_auto_close | true | 当关闭 Statement 时自动关闭 ResultSet |
jdbc_use_max_result_rows | false | 启用后,将使用服务器属性 max_result_rows 来限制查询返回的行数;启用该选项时,会覆盖用户配置的溢出模式。有关详细信息,请参见 JavaDoc。 |
jdbc_sql_parser | JAVACC | 配置要使用的 SQL 解析器类型。可选值为:ANTLR4、ANTLR4_PARAMS_PARSER、JAVACC。 |
配置示例:
这等同于以下 JDBC URL:
注意:无需对 JDBC URL 或属性进行 URL 编码,系统将自动进行编码。
支持的数据类型
JDBC 驱动支持与底层 Java 客户端相同的数据格式。
JDBC 类型映射
以下映射适用于:
ResultSet#getObject(columnIndex)方法将返回相应 Java 类的对象。(Int8->java.lang.Byte,Int16->java.lang.Short等)ResultSetMetaData#getColumnType(columnIndex)方法将返回对应的 JDBC 类型(Int8->java.lang.Byte,Int16->java.lang.Short等)。
有几种方法可以更改映射:
ResultSet#getObject(columnIndex, class)- 该方法会尝试将值转换为指定的class类型。此类转换存在一些限制。详情请参阅各节说明。
数值类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| Int8 | TINYINT | java.lang.Byte |
| Int16 | SMALLINT | java.lang.Short |
| Int32 | INTEGER | java.lang.Integer |
| Int64 | BIGINT | java.lang.Long |
| Int128 | OTHER | java.math.BigInteger |
| Int256 | OTHER | java.math.BigInteger |
| UInt8 | OTHER | java.lang.Short |
| UInt16 | OTHER | java.lang.Integer |
| UInt32 | OTHER | java.lang.Long |
| UInt64 | OTHER | java.math.BigInteger |
| UInt128 | OTHER | java.math.BigInteger |
| UInt256 | OTHER | java.math.BigInteger |
| Float32 | REAL | java.lang.Float |
| Float64 | DOUBLE | java.lang.Double |
| Decimal32 | DECIMAL | java.math.BigDecimal |
| Decimal64 | DECIMAL | java.math.BigDecimal |
| Decimal128 | DECIMAL | java.math.BigDecimal |
| Decimal256 | DECIMAL | java.math.BigDecimal |
| Bool | BOOLEAN | java.lang.Boolean |
- 数值类型之间可以相互转换。因此,可以将
Int8读取为Float64,反之亦然:rs.getObject(1, Float64.class)将返回Int8列的Float64值。rs.getLong(1)将返回Int8列的Long值。rs.getByte(1)可以返回Int16列的Byte值,如果该值在Byte的表示范围内。
- 由于存在数据损坏风险,不建议将宽类型转换为窄类型。
Bool类型也可以作为数值使用。- 所有数值类型都可以读取为
java.lang.String。
字符串类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| String | VARCHAR | java.lang.String |
| FixedString | VARCHAR | java.lang.String |
String只能读取为java.lang.String或byte[]。FixedString会按原样读取,并用零字节填充到该列的长度。(例如,将'John'存储为FixedString(10)时,读取结果为'John\0\0\0\0\0\0\0\0\0'。)
枚举类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| Enum8 | OTHER | java.lang.String |
| Enum16 | OTHER | java.lang.String |
Enum8和Enum16默认映射为java.lang.String。- 枚举值可以通过指定的 getter 方法或
getObject(columnIndex, Integer.class)方法读取为数值。 Enum16在内部映射为 short 类型,而Enum8映射为 byte 类型。应避免将Enum16以 byte 形式读取,因为存在数据损坏的风险。- 可以在
PreparedStatement中将 Enum 值设置为字符串值或数值。
日期/时间类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| Date | DATE | java.sql.Date |
| Date32 | DATE | java.sql.Date |
| DateTime | TIMESTAMP | java.sql.Timestamp |
| DateTime64 | TIMESTAMP | java.sql.Timestamp |
| Time | TIME | java.sql.Time |
| Time64 | TIME | java.sql.Time |
- 日期/时间类型会映射为
java.sql类型,以便更好地兼容 JDBC。不过,仍然可以通过调用ResultSet#getObject(columnIndex, Class<T>),并将相应的类作为第二个参数,获取java.time.LocalDate、java.time.LocalDateTime、java.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值。
Date、Date32、Time、Time64不受服务器时区影响。DateTime、DateTime64会受到服务器时区或会话时区的影响。- 可以通过
getObject(colIndex, ZonedDateTime.class)将DateTime和DateTime64获取为ZonedDateTime。
采集类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| Array | ARRAY | java.sql.Array |
| Tuple | OTHER | Object[] |
| Map | JAVA_OBJECT | java.util.Map |
Array默认会映射为java.sql.Array,以保证与 JDBC 的兼容性。同时还能提供有关返回数组值的更多信息,有利于进行类型推断。Array实现了getResultSet()方法,用于返回内容与原始数组相同的java.sql.ResultSet。- 集合类型不应读取为
java.lang.String,因为那并不是一种有效的数据表示方式(例如:数组中的字符串值不会被加引号)。 Map映射为JAVA_OBJECT,因为其值只能通过getObject(columnIndex, Class<T>)方法读取。Map不是java.sql.Struct,因为它没有具名列。
Tuple映射为Object[],因为它可以包含不同类型的数据,使用List并不合适。Tuple可以通过调用getObject(columnIndex, Array.class)方法作为Array读取。在这种情况下,Array#baseTypeName将返回Tuple的列定义。
地理类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| Point | OTHER | double[] |
| Ring | OTHER | double[][] |
| Polygon | OTHER | double[][][] |
| MultiPolygon | OTHER | double[][][][] |
Nullable 和 LowCardinality 类型
Nullable和LowCardinality是用于包装其他类型的特殊类型。Nullable会影响ResultSetMetaData返回类型名称的方式
特殊类型
| ClickHouse 类型 | JDBC 类型 | Java 类 |
|---|---|---|
| UUID | OTHER | java.util.UUID |
| IPv4 | OTHER | java.net.Inet4Address |
| IPv6 | OTHER | java.net.Inet6Address |
| JSON | OTHER | java.lang.String |
| AggregateFunction | OTHER | (二进制表示) |
| SimpleAggregateFunction | (包装类型) | (包装类) |
UUID不是 JDBC 标准类型,但它是 JDK 的一部分。默认情况下,通过getObject()方法会返回java.util.UUID实例。- 可以使用
getObject(columnIndex, String.class)方法,将UUID以String的形式进行读写。 IPv4和IPv6不是 JDBC 标准类型,但它们属于 JDK 的一部分。默认情况下,通过getObject()方法会返回java.net.Inet4Address和java.net.Inet6Address。IPv4和IPv6可以通过getObject(columnIndex, String.class)方法以String形式进行读写。
处理日期、时间和时区
java.sql.Date、java.sql.Time 和 java.sql.Timestamp 可能会使时区计算变得复杂——尽管它们当然是受支持的,
您可以考虑使用 java.time 包。ZonedDateTime 和
OffsetDateTime 都是 java.sql.Timestamp、java.sql.Date 和 java.sql.Time 的理想替代方案。
Date 存储时不包含时区信息,而 DateTime 存储时包含时区信息。如果不注意这一点,可能会导致意外结果。
创建连接
提供凭据和设置
简单语句
插入
HikariCP
更多信息
如需了解更多信息,请参阅我们的 GitHub 仓库和 Java 客户端文档。
故障排除
日志
该驱动使用 slf4j 进行日志记录,并将使用 classpath 中首个可用的实现。
解决大批量插入时的 JDBC 超时问题
在 ClickHouse 中执行耗时较长的大批量插入操作时,可能会遇到如下 JDBC 超时错误:
这些错误可能会中断数据插入过程并影响系统稳定性。要解决此问题,您可能需要调整客户端操作系统中的若干超时设置。
macOS
在 macOS 上,可以调整以下设置以解决此问题:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1
Linux
在 Linux 上,仅配置等效设置可能无法解决问题。由于 Linux 处理套接字 keep-alive 设置的方式不同,需要执行额外的步骤。请按照以下步骤操作:
- 在
/etc/sysctl.conf或其他相关配置文件中调整以下 Linux 内核参数:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1net.ipv4.tcp_keepalive_intvl: 75net.ipv4.tcp_keepalive_probes: 9net.ipv4.tcp_keepalive_time: 60(可考虑将该值从默认的 300 秒适当降低)
- 修改内核参数后,运行以下命令使更改生效:
设置这些配置后,您需要确保客户端在套接字上启用 Keep-Alive 选项:
迁移指南
主要变更
| 功能 | V1(旧版) | V2(新版) |
|---|---|---|
| 事务支持 | 部分支持 | 不支持 |
| 响应列重命名 | 部分支持 | 不支持 |
| 多语句 SQL | 不支持 | 不允许 |
| 命名参数 | 支持 | 不支持(JDBC 规范未定义) |
使用 PreparedStatement 进行流式数据传输 | 支持 | 不支持 |
- JDBC V2 采用更轻量级的实现,因此移除了一些功能。
- 在 JDBC V2 中不支持流式数据传输,因为它既不属于 JDBC 规范,也不属于 Java 标准的一部分。
- JDBC V2 需要显式配置。不提供任何故障转移相关的默认设置。
- 协议应在 URL 中显式指定,不进行基于端口号的隐式协议检测。
配置更改
仅有两个枚举:
com.clickhouse.jdbc.DriverProperties- 驱动程序自身的配置属性。com.clickhouse.client.api.ClientConfigProperties- 客户端配置相关属性。关于客户端配置变更的说明,请参见 Java Client 文档。
连接属性按以下方式解析:
- 会优先从 URL 中解析属性,这些属性将覆盖所有其他属性。
- 驱动属性不会被传递给客户端。
- 从 URL 中解析端点(主机、端口、协议)。
示例:
数据类型变更
数值类型
| ClickHouse 类型 | 是否与 V1 兼容 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| Int8 | ✅ | TINYINT | java.lang.Byte | TINYINT | java.lang.Byte |
| Int16 | ✅ | SMALLINT | java.lang.Short | SMALLINT | java.lang.Short |
| Int32 | ✅ | INTEGER | java.lang.Integer | INTEGER | java.lang.Integer |
| Int64 | ✅ | BIGINT | java.lang.Long | BIGINT | java.lang.Long |
| Int128 | ✅ | OTHER | java.math.BigInteger | OTHER | java.math.BigInteger |
| Int256 | ✅ | OTHER | java.math.BigInteger | OTHER | java.math.BigInteger |
| UInt8 | ❌ | OTHER | java.lang.Short | OTHER | com.clickhouse.data.value.UnsignedByte |
| UInt16 | ❌ | OTHER | java.lang.Integer | OTHER | com.clickhouse.data.value.UnsignedShort |
| UInt32 | ❌ | OTHER | java.lang.Long | OTHER | com.clickhouse.data.value.UnsignedInteger |
| UInt64 | ❌ | OTHER | java.math.BigInteger | OTHER | com.clickhouse.data.value.UnsignedLong |
| UInt128 | ✅ | OTHER | java.math.BigInteger | OTHER | java.math.BigInteger |
| UInt256 | ✅ | OTHER | java.math.BigInteger | OTHER | java.math.BigInteger |
| Float32 | ✅ | REAL | java.lang.Float | REAL | java.lang.Float |
| Float64 | ✅ | DOUBLE | java.lang.Double | DOUBLE | java.lang.Double |
| Decimal32 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Decimal64 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Decimal128 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Decimal256 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Bool | ✅ | BOOLEAN | java.lang.Boolean | BOOLEAN | java.lang.Boolean |
- 主要区别在于:为提高可移植性,无符号类型现在映射为标准 Java 类型。
字符串类型
| ClickHouse 类型 | 是否更改 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| String | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String |
| FixedString | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String |
FixedString在两个版本中都会按原样读取。例如,对于'John',FixedString(10)将会被读取为'John\0\0\0\0\0\0\0\0\0'。- 当使用
PreparedStatement#setBytes时,其值会先被转换为unhex('<hex_string>'),然后按String类型读取。
日期/时间类型
| ClickHouse 类型 | 与 V1 兼容 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| Date | ❌ | DATE | java.sql.Date | DATE | java.time.LocalDate |
| Date32 | ❌ | DATE | java.sql.Date | DATE | java.time.LocalDate |
| DateTime | ❌ | TIMESTAMP | java.sql.Timestamp | TIMESTAMP | java.time.OffsetDateTime |
| DateTime64 | ❌ | TIMESTAMP | java.sql.Timestamp | TIMESTAMP | java.time.OffsetDateTime |
| Time | ✅ | TIME | java.sql.Time | 新类型/不支持 | 新类型/不支持 |
| Time64 | ✅ | TIME | java.sql.Time | 新类型 / 不受支持 | 新类型 / 不受支持 |
Time和Time64仅在 V2 中作为新增类型得到支持。DateTime和DateTime64映射到java.sql.Timestamp,以实现更好的 JDBC 兼容性。
枚举类型
| ClickHouse 类型 | 是否已更改 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| Enum | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String |
| Enum8 | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String |
| Enum16 | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String |
采集类型
| ClickHouse 类型 | 是否变更 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| Array | ❌ | ARRAY | java.sql.Array | ARRAY | Object[] 或原始类型的数组 |
| Tuple | ❌ | OTHER | Object[] | STRUCT | java.sql.Struct |
| Map | ❌ | JAVA_OBJECT | java.util.Map | STRUCT | java.util.Map |
- 在 V2 中,
Array默认映射为java.sql.Array,以保证与 JDBC 的兼容性。这也能提供更多关于返回数组值的信息,便于类型推断。 - 在 V2 中,
Array实现了getResultSet()方法,以返回一个内容与原始数组相同的java.sql.ResultSet对象。 - V1 对
Map使用STRUCT,但始终返回java.util.Map对象。V2 通过将Map映射为JAVA_OBJECT修复了这一点。此外,将Map映射为STRUCT也是不正确的,因为Map本身并不包含具名列。 - V1 对
Tuple使用STRUCT,但始终返回List<Object>对象。V2 通过将Tuple映射为OTHER并返回Object[]来修复此问题,因为Tuple中可以包含不同类型元素,使用List作为返回类型并不合适。 PreparedStatement#setBytes和ResultSet#getBytes不能与集合类型一起使用。这些方法是为处理二进制字符串而设计的。- 通常会使用
java.sql.Array来读写 Array 类型。JDBC 驱动对此提供了完备支持。
地理类型
| ClickHouse 类型 | 是否与 V1 兼容 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| Point | ✅ | OTHER | double[] | OTHER | double[] |
| Ring | ✅ | OTHER | double[][] | OTHER | double[][] |
| Polygon | ✅ | OTHER | double[][][] | OTHER | double[][][] |
| MultiPolygon | ✅ | OTHER | double[][][][] | OTHER | double[][][][] |
Nullable 和 LowCardinality 类型
Nullable和LowCardinality是用于封装其他类型的特殊类型。- 在 V2 中,这些类型没有更改。
特殊类型
| ClickHouse 类型 | 与 V1 兼容 | JDBC 类型(V2) | Java 类(V2) | JDBC 类型(V1) | Java 类(V1) |
|---|---|---|---|---|---|
| JSON | ❌ | OTHER | java.lang.String | 不支持 | 不支持 |
| AggregateFunction | ✅ | OTHER | (二进制表示) | OTHER | (二进制表示) |
| SimpleAggregateFunction | ✅ | (封装类型) | (封装类) | (封装类型) | (封装类) |
| UUID | ✅ | OTHER | java.util.UUID | VARCHAR | java.util.UUID |
| IPv4 | ✅ | OTHER | java.net.Inet4Address | VARCHAR | java.net.Inet4Address |
| IPv6 | ✅ | OTHER | java.net.Inet6Address | VARCHAR | java.net.Inet6Address |
| Dynamic | ❌ | OTHER | java.lang.Object | 不支持 | 不支持 |
| Variant | ❌ | OTHER | java.lang.Object | 不支持 | 不支持 |
- V1 将
UUID映射为VARCHAR类型,但始终返回java.util.UUID对象。V2 通过将UUID映射到OTHER并同样返回java.util.UUID对象来修复了这一问题。 - V1 对
IPv4和IPv6使用VARCHAR类型,但始终返回java.net.Inet4Address和java.net.Inet6Address对象。V2 通过将IPv4和IPv6映射为 JDBC 类型OTHER,并返回java.net.Inet4Address和java.net.Inet6Address对象来修复此问题。 Dynamic和Variant是在 V2 中引入的新类型。在 V1 中不支持。JSON基于Dynamic类型,因此仅在 V2 中受支持。- 可以通过
getBytes(columnIndex)方法将 IPv4 和 IPv6 的值读取为byte[]。不过,建议为这些类型使用专用的类。 - V2 不支持将 IP 地址读取为数值形式,因为在 InetAddress 类中进行转换的实现更为合适。
数据库元数据变更
- V2 仅使用
Schema一词来表示数据库。Catalog一词保留供将来使用。 - V2 会在
DatabaseMetaData.supportsTransactions()和DatabaseMetaData.supportsSavepoints()中返回false。这一点将在后续开发中进行修改。