clickhouse-jdbc 基于最新的 Java 客户端实现了标准 JDBC 接口。
如果对性能或直接访问有较高要求,我们建议直接使用最新的 Java 客户端。环境要求
- OpenJDK 版本 >= 8
设置
- Maven
- Gradle (Kotlin)
- Gradle
- Maven Central,并将其添加到类路径中
- 从
0.9.4版本开始,已提供制品 https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc-all - 使用限定符
all可获取包含所有已打包依赖项的 jar。
- 从
- 或从官方仓库这里获取
配置
驱动类:com.clickhouse.jdbc.ClickHouseDrivercom.clickhouse.jdbc.ClickHouseDriver 是新旧 JDBC 实现的门面类,默认使用新的 JDBC 实现。
你可以通过将 clickhouse.jdbc.v1 system 属性设置为 true 来使用旧版 JDBC 实现。该属性应在调用
Driver 类之前设置。另一种在不同版本之间切换的方法是直接使用各版本对应的 Driver 类:com.clickhouse.jdbc.Driver是新的 JDBC 实现 (V2) 。com.clickhouse.jdbc.DriverV1是旧的 JDBC 实现 (V1) 。
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 中仅允许有一个端点
- 当 protocol 不是默认协议 ‘HTTP’ 时,应显式指定
- 端口不是默认值 ‘8123’ 时,应显式指定端口
- 驱动程序不会根据端口来猜测协议,你需要明确指定协议
- 指定了 protocol 时,不需要
ssl参数。
连接属性
主要配置参数在 Java client 中定义,应原样传递给 driver。Driver 还有一些自有属性,不属于客户端配置的范畴,详见下文。驱动属性:示例配置:
readonly=2 profile。
有关只读 profile 的更多信息,请参阅此处。客户端标识
有两种方式可以标识发起请求的应用程序:通过连接属性设置com.clickhouse.client.api.ClientConfigProperties#CLIENT_NAME,或使用 java.sql.Connection#setClientInfo(String name, String value) 方法。showLineNumbers
showLineNumbers
http_user_agent 值:client_name 属性设置为 app_name/version 格式,以便在查询日志中识别应用程序。操作标识
JDBC 驱动为每个操作生成query_id (目前该信息包含在服务器异常中) 。要为某个操作设置 log_comment,请使用 com.clickhouse.jdbc.StatementImpl#getLocalSettings 方法。这需要先将 Statement 或 PreparedStatement 转型为 com.clickhouse.jdbc.StatementImpl。showLineNumbers
localSettings 在多个线程之间是共享的。支持的数据类型
JDBC 驱动支持与底层 java client 相同的数据格式。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类型。此类转换存在一些限制。详见各节。
- 数值类型之间可以相互转换。因此,
Int8可以获取为Float64,反之亦然。:rs.getObject(1, Float64.class)将返回Int8列的Float64值。rs.getLong(1)将返回Int8列的Long值。- 如果
Int16列的值可以放入Byte,rs.getByte(1)就可以返回Byte值。
- 不建议将较宽类型转换为较窄类型,因为存在数据损坏的风险。
Bool类型也可视为数值。- 所有数值类型都可以作为
java.lang.String读取。 - 将 Java
Float.MAX_VALUE存储为Float时会出现问题 (https://github.com/ClickHouse/clickhouse-java/issues/809) 。将同一数值存储为Double即可解决该问题。
String只能作为java.lang.String或byte[]读取。FixedString会按原样读取,并以零填充至该列的长度。 (例如,FixedString(10)中的'John'会被读取为'John\0\0\0\0\0\0\0\0\0'。)
Enum8和Enum16默认会映射为java.lang.String。- 枚举值可以通过指定的 getter 方法或
getObject(columnIndex, Integer.class)方法按数值读取。 Enum16在内部映射为 short,Enum8在内部映射为 byte。由于存在数据损坏风险,应避免将Enum16读取为 byte。- 枚举值可在
PreparedStatement中设置为字符串或数值。
- Date / 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的形式获取。
- 默认情况下,为了兼容 JDBC,
Array会映射为java.sql.Array。这样做还能提供有关返回数组值的更多信息,有助于类型推断。 Array实现了getResultSet()方法,返回一个与原始数组内容相同的java.sql.ResultSet。- 集合类型不应读取为
java.lang.String,因为这不是表示该数据的有效方式 (例如,数组中的字符串值没有引号) 。 Map会映射为OTHER,因为该值只能通过getObject(columnIndex, Class<T>)方法读取。Map不是java.sql.Struct,因为它不包含具名列。
Tuple会映射为Object[],因为它可以包含不同类型的值,而使用List并不成立。- 可以通过
getObject(columnIndex, Array.class)方法将Tuple作为Array读取。在这种情况下,Array#baseTypeName将返回Tuple的列定义。
Array.getBaseTypeName() 返回 ClickHouse 元素类型名称;Array.getBaseType() 返回 JDBC 类型代码。
JDBC V2 保留了完整的类型签名 (包装类型、类型参数) ,而 V1 会将其去除。数组的通用映射规则如下:以上规则的注意事项:
- 包装类型 (
Nullable,LowCardinality) 会在getBaseTypeName()中保留,但getBaseType()解析得到的是内部类型的 JDBC 代码。 - 元数据中的嵌套数组会被展平:
getBaseTypeName()返回最内层的非数组元素类型,而不是直接子类型。 - 参数化类型 (
FixedString(N)、完整的Enum/Tuple定义) 会在getBaseTypeName()中保留其参数。
- 在 V2 中,
getBaseTypeName()会保留完整的类型签名,包括包装类型 (Nullable、LowCardinality) 和类型参数 (FixedString(8)、完整的Enum和Tuple定义)。V1 则会去掉这些内容,只返回基本类型名称。 Tuple数组在 V2 中使用OTHER (1111),而不是STRUCT (2002),因为 ClickHouse 元组具有具名字段,而java.sql.Struct不支持这一点。UUID数组在 V2 中使用OTHER (1111),与标量UUID的映射一致。Enum值会映射为VARCHAR—— 枚举成员始终通过字符串名称标识,与其底层数值编码无关。
java.sql.Connection#createArrayOf 实例化 java.sql.Array 对象。该对象旨在统一不同数据库间的数组处理方式。
创建 Array 时需要通过连接将配置传递给其工厂方法。该方法接受两个参数:typeName- 数组元素的类型名称。例如,Array(Int32)->"Int32"。elements- 数组中的实际元素。例如,[[1, 2, 3], [4, 5, 6]]->new Integer[][] {{1, 2, 3}, {4, 5, 6}}。
Object[] 或 java.sql.Struct (有关如何写入 Tuple,请参阅下文) 。示例ResultSet#getArray(columnIndex) 读取 Array 对象。该对象可用于访问任意嵌套深度的数组。
Array#getResultSet() 方法可以以更统一的方式将数组元素作为 java.sql.ResultSet 进行读取,适用于数组元素的确切类型未知的场景。示例com.clickhouse.data.Tuple 对象,应通过调用 setObject(columnIndex, tuple) 方法将其写入该对象。
也可以使用 java.sql.Struct 对象来写入 Tuples,以提高可移植性。示例getObject(columnIndex) 将返回 Object[]。Tuples 可通过 getObject(columnIndex, Array.class) 方法以 java.sql.Array 的形式读取。示例java.collections.Map 对象的形式写入,因为该类型需要键值对 (java.sql.Struct 不支持键值对) 。示例getObject(columnIndex, Map.class) 方法将 Map 作为 java.collections.Map 对象读取。示例java.sql.Connection#createStruct 实例化 java.sql.Struct 对象。该对象旨在统一不同数据库中的嵌套处理方式。
向 Struct 工厂方法传递配置时,需要提供 Connection 对象。该方法接受两个参数:typeName- 嵌套元素的类型名。例如,Nested(Tuple(Int32, String))->"Nested(Tuple(Int32, String))"。elements- 实际的嵌套元素。例如,[1, 'test']->new Object[] {1, 'test'}。
ResultSet#getStruct(columnIndex, StructDescriptor) 读取 Nested 对象。该对象可用于访问任意嵌套深度的嵌套内容。
Struct#getResultSet() 方法可用于以更统一的方式将嵌套元素作为 java.sql.ResultSet 读取。当嵌套元素的确切类型未知时,此方法非常有用。示例Nullable 与 LowCardinality 类型
Nullable和LowCardinality是对其他类型进行封装的特殊类型。Nullable会影响ResultSetMetaData中类型名称的返回方式
UUID并不是 JDBC 标准类型,但它属于 JDK。默认情况下,getObject()方法返回的是java.util.UUID。- 通过
getObject(columnIndex, String.class)方法,可将UUID作为String类型读取或写入。 IPv4和IPv6不是 JDBC 标准类型,但它们属于 JDK。默认情况下,getObject()方法会返回java.net.Inet4Address和java.net.Inet6Address。- 通过
getObject(columnIndex, String.class)方法,可以将IPv4和IPv6作为String类型进行读写。
Map<String, Object>,其中键为 JSON 对象的键,值为 JSON 对象的值。
例如:jdbc_read_json_as_string=true 传入连接属性,以 String 形式读取 JSON。
这样驱动程序会将 JSON 值作为 String 返回,可使用任意 JSON 库进行解析。处理日期、时间与时区
请阅读 Date/Time Guide,其中说明了驱动程序在处理 Date/Time 和时间戳时的常见陷阱及处理逻辑。创建连接
提供凭据与配置项
showLineNumbers
简单语句
showLineNumbers
插入
showLineNumbers
HikariCP
showLineNumbers
更多信息
如需了解更多信息,请参阅我们的 GitHub 代码仓库 和 Java Client 文档。故障排查
日志
驱动程序使用 slf4j 进行日志记录,并会使用classpath 中第一个可用的实现。解决大批量插入时的 JDBC 超时问题
在 ClickHouse 中执行耗时较长的大批量插入操作时,您可能会遇到如下 JDBC 超时错误:Mac OS
在 Mac OS 上,可以通过调整以下设置来解决该问题: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 处理套接字保活设置的方式有所不同,还需执行额外步骤。请按以下步骤操作:- 在
/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 秒调低)
- 修改内核参数后,运行以下命令使更改生效:
迁移指南
主要变更
- JDBC V2 采用了更轻量的实现,因此移除了一些功能。
- JDBC V2 不支持流式数据,因为流式数据不属于 JDBC 规范,也不是 Java 的一部分。
- JDBC V2 需要显式配置。不默认启用故障转移。
- 应在 URL 中指定协议。不支持根据端口号隐式检测协议。
配置变更
只有两个枚举:com.clickhouse.jdbc.DriverProperties- 驱动自身的配置属性。com.clickhouse.client.api.ClientConfigProperties- 客户端配置属性。有关客户端配置的更改,请参见 Java Client 文档。
- 首先会解析 URL 中的属性,这些属性会覆盖所有其他属性。
- 驱动属性不会传递给客户端。
- 端点 (主机、端口、协议) 会从 URL 中解析出来。
数据类型变更
数值类型- 最大的区别在于,出于更好的可移植性考虑,无符号类型会映射到 Java 类型。
FixedString在两个版本中都会按原样读取。例如,'John'的FixedString(10)会读取为'John\0\0\0\0\0\0\0\0\0'。- 使用
PreparedStatement#setBytes时,会先将其转换为unhex('<hex_string>'),然后按String类型读取。 - String 类型以 UTF-8 编码存储。
Time和Time64仅在 V2 中作为新增类型受支持。DateTime和DateTime64会映射到java.sql.Timestamp,以更好地兼容 JDBC。
嵌套类型
- 在 V2 中,
Array默认映射为java.sql.Array,以兼容 JDBC。这样做也能提供有关返回数组值的更多信息,有助于类型推断。 - 在 V2 中,
Array实现了getResultSet()方法,用于返回一个内容与原始数组相同的java.sql.ResultSet。 - V1 将
Map视为STRUCT,但始终返回java.util.Map对象。V2 通过将Map映射为JAVA_OBJECT解决了这一问题。 - V1 对
Tuple使用STRUCT,但始终返回List<Object>。V2 将Tuple映射为OTHER,默认返回Object[]。 - V2 引入了
com.clickhouse.data.Tuple#Tuple,用于写入元组。它简化了判断某个值是元组还是数组的过程。 PreparedStatement#setBytes和ResultSet#getBytes不能用于 collection 类型。这些方法是为处理 binary string 而设计的。- 通常使用
java.sql.Array来读写 Array 类型。JDBC 驱动对此提供了全面支持。 - V2
Nested被映射为Array,并以元组数组的形式呈现。 - V2 对
java.sql.Struct提供了部分支持,因为它与 Array 类型非常相似,且不支持键值对。Struct可用于写入Tuple值。
Nullable 与 LowCardinality 类型
Nullable和LowCardinality是用于封装其他类型的特殊类型。- 这些类型在 V2 中没有变化。
- 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映射到OTHER解决了这一问题,并返回java.net.Inet4Address和java.net.Inet6Address对象。 Dynamic和Variant是 V2 中新增的类型,V1 不支持。JSON基于Dynamic类型,因此仅在 V2 中受支持。- IPv4 和 IPv6 的值可以通过
getBytes(columnIndex)方法读取为byte[]。不过,建议针对这些类型使用专门的类。 - V2 不支持将 IP 地址作为数值读取,因为这类转换更适合在 InetAddress 类中实现。
数据库元数据变更
- V2 仅使用
Schema这一术语来表示数据库。Catalog一词保留供将来使用。 - V2 对
DatabaseMetaData.supportsTransactions()和DatabaseMetaData.supportsSavepoints()返回false。后续开发中将对此进行调整。 - 在
DatabaseMetaData.getTypeInfo()中,对于不应具有前缀和后缀的数据类型 (例如数值类型) ,LITERAL_PREFIX和LITERAL_SUFFIX列现在会返回null。 在 V1 中,这些列对这类类型会返回非null值。在生成 SQL 查询时,应使用这些列根据数据类型正确地为字面量值添加引号。