跳转到主内容
跳转到主内容

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.6</version>
    <classifier>all</classifier>
</dependency>

如果您在应用程序中使用 JDBC 驱动程序,且该应用程序需要将 jar 添加到 classpath,则需要从以下地址下载 jar 文件:

配置

驱动程序类: 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&param2=value2][#tag1,tag2,...],例如:

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

关于 URL 语法,有以下几点需要注意:

  • URL 中 只能 指定一个 endpoint
  • 当使用的协议不是默认的 'HTTP' 时,应显式指定 protocol。
  • 如果端口不是默认的 '8123',则必须显式指定。
  • 驱动程序不会根据端口推断协议,因此必须显式指定协议
  • 如果已指定协议,则无需设置 ssl 参数。

连接属性

主要配置参数在 java client 中定义。这些参数应按原样传递给驱动程序。驱动程序还有一些自有属性,它们不属于客户端配置的一部分,具体列举如下。

驱动属性:

属性默认值描述
disable_frameworks_detectiontrue禁用基于 User-Agent 的框架识别
jdbc_ignore_unsupported_valuesfalse在不影响驱动工作的情况下抑制 SQLFeatureNotSupportedException 异常
clickhouse.jdbc.v1false使用旧版 JDBC 实现,而不是新版实现
default_query_settingsnull允许在执行查询时传递默认查询设置
jdbc_resultset_auto_closetrue在关闭 Statement 时自动关闭 ResultSet
beta.row_binary_for_simple_insertfalse使用基于 RowBinary writer 的 PreparedStatement 实现,仅适用于 INSERT INTO ... VALUES 形式的查询。
jdbc_resultset_auto_closetrue在关闭 Statement 时自动关闭 ResultSet
jdbc_use_max_result_rowsfalse启用该选项后,将使用服务器属性 max_result_rows 来限制查询返回的行数。启用时会覆盖用户设置的溢出模式。详细信息请参见 JavaDoc。
jdbc_sql_parserJAVACC配置要使用的 SQL 解析器类型。可选值:ANTLR4ANTLR4_PARAMS_PARSERJAVACC
服务器设置

所有服务器设置都应以 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 编码,系统将自动进行编码。

客户端标识

有两种方式可以标识发起请求的应用程序:通过连接属性设置 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 方法。这需要先将 StatementPreparedStatement 强制转换为 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
  • 数值类型之间可以相互转换。因此,可以将 Int8 读取为 Float64,反之亦然:
    • rs.getObject(1, Float64.class) 将返回 Int8 列的 Float64 值。
    • rs.getLong(1) 将返回 Int8 列的 Long 值。
    • 如果值在 Byte 的取值范围内,rs.getByte(1) 可以返回 Int16 列的 Byte 值。
  • 由于存在数据损坏风险,不建议将更宽类型转换为更窄类型。
  • Bool 类型也可以作为数值使用。
  • 所有数值类型都可以读取为 java.lang.String

字符串类型

ClickHouse 类型JDBC 类型Java 类
StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.String
  • String 只能被读取为 java.lang.Stringbyte[] 类型。
  • FixedString 读取时按原样返回,并在末尾用零字节补齐至该列的长度。(例如,将 'John' 定义为 FixedString(10) 时,读取结果为 'John\0\0\0\0\0\0\0\0\0'。)

枚举类型

ClickHouse 类型JDBC 类型Java 类
Enum8OTHERjava.lang.String
Enum16OTHERjava.lang.String
  • Enum8Enum16 默认会映射到 java.lang.String 类型。
  • 枚举值可以通过相应的数值类型专用 getter 方法,或使用 getObject(columnIndex, Integer.class) 方法读取为数值类型。
  • Enum16 在内部映射为 short 类型,而 Enum8 映射为 byte 类型。应避免将 Enum16byte 形式读取,否则存在数据损坏风险。
  • 可以在 PreparedStatement 中将枚举值设置为字符串或数值。

日期/时间类型

ClickHouse 类型JDBC 类型Java 类
DateDATEjava.sql.Date
Date32DATEjava.sql.Date
DateTimeTIMESTAMPjava.sql.Timestamp
DateTime64TIMESTAMPjava.sql.Timestamp
TimeTIMEjava.sql.Time
Time64TIMEjava.sql.Time
  • 日期/时间类型会映射为 java.sql 类型,以便与 JDBC 有更好的兼容性。不过,仍然可以通过 ResultSet#getObject(columnIndex, Class<T>) 方法,并将相应的类作为第二个参数传入,来获取 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 的形式返回。
  • Date, Date32, Time, Time64 不受服务器时区的影响。
  • DateTimeDateTime64 会受到服务器时区或会话时区的影响。
  • DateTimeDateTime64 可以通过 getObject(colIndex, ZonedDateTime.class) 获取为 ZonedDateTime 对象。

嵌套类型

ClickHouse 类型JDBC 类型Java 类
ArrayARRAYjava.sql.Array
TupleOTHERcom.clickhouse.data.Tuple
MapJAVA_OBJECTjava.util.Map
NestedARRAYjava.sql.Array
  • 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 列的定义。

写入数组

使用 java.sql.Connection#createArrayOf 来实例化 java.sql.Array 对象。该对象旨在统一不同数据库中的数组处理方式。 需要通过 Connection 将配置传递给 Array 的工厂方法。

该方法接受两个参数:

  • typeName - 数组元素的类型名称。例如 Array(Int32) -> "Int32"
  • elements - 数组中的实际元素。例如 [[1, 2, 3], [4, 5, 6]] -> new Integer[][] {{1, 2, 3}, {4, 5, 6}}

Tuple 可以表示为 Object[]java.sql.Struct(请参阅下文中关于如何写入 Tuple 的说明)。

示例

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

读取数组

使用 ResultSet#getArray(columnIndex) 读取 Array 对象。该对象可用于访问任意嵌套深度的数组。 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 类型

Tuple 类型会映射为 com.clickhouse.data.Tuple 对象,应通过调用 setObject(columnIndex, tuple) 方法将其作为该对象写入。 也可以使用 java.sql.Struct 对象来写入 Tuple,以获得更好的可移植性。

示例

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) 方法,可以将 Tuple 读取为 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.collections.Map 对象,因为该类型需要键值对(java.sql.Struct 不支持键值对)。

示例

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) 方法将 Map 读取为 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);
                // ...
            }
        }
    }
}

写入嵌套类型

使用 java.sql.Connection#createStruct 来实例化 java.sql.Struct 对象。该对象旨在在不同数据库之间统一嵌套数据的处理。 需要通过 Connection 将配置信息传递给 Struct 的工厂方法。

该方法接受两个参数:

  • 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();
    }
}

读取嵌套类型

使用 ResultSet#getStruct(columnIndex, StructDescriptor) 来读取 Nested 对象。该对象可用于访问任意嵌套深度的嵌套结构。 可以使用 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) 方法将 UUID 作为 String 类型进行读写。
  • IPv4IPv6 不是 JDBC 标准类型,但它们是 JDK 的一部分。默认情况下,调用 getObject() 方法时会返回 java.net.Inet4Addressjava.net.Inet6Address 实例。
  • 可以通过 getObject(columnIndex, String.class) 方法将 IPv4IPv6 读取/写入为 String

处理日期、时间和时区

请阅读 日期/时间指南,其中说明了驱动程序在处理 Date/Time 和 Timestamp 时的常见问题及相关逻辑。

创建连接

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

插入

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 中执行耗时较长的大批量插入操作时,可能会遇到如下 JDBC 超时错误:

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

这些错误可能会中断数据插入过程并影响系统稳定性。为解决该问题,可能需要在客户端操作系统中调整若干超时设置。

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 处理套接字 keep-alive 设置的方式不同,需要执行额外的步骤。请按照以下步骤操作:

  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 V2 不支持流式数据传输,因为该功能既不在 JDBC 规范中定义,也未在 Java 标准中定义。
  • JDBC V2 需要显式配置,不提供任何故障转移的默认配置。
    • 协议应在 URL 中显式指定,不要基于端口号隐式推断协议。

配置更改

仅有两个枚举:

  • com.clickhouse.jdbc.DriverProperties - 驱动程序自身的配置属性。
  • com.clickhouse.client.api.ClientConfigProperties - 客户端配置属性。客户端配置 变更详见 Java Client 文档

连接属性按以下方式解析:

  • 会优先从 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新类型/不支持新类型/不支持
  • 在 V2 中,TimeTime64 仅作为新增类型提供支持。
  • DateTimeDateTime64 映射到 java.sql.Timestamp 类型,以提高与 JDBC 的兼容性。

枚举类型

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
  • 在 V2 中,Array 默认会映射为 java.sql.Array,以保证与 JDBC 的兼容性。这样做也能提供更多有关返回数组值的信息,有助于进行类型推断。
  • 在 V2 中,Array 实现了 getResultSet() 方法,用于返回与原始数组内容相同的 java.sql.ResultSet
  • V1 对 Map 使用 STRUCT,但始终返回 java.util.Map 对象。V2 通过将 Map 映射为 JAVA_OBJECT 解决了这一问题。
  • V1 使用 STRUCT 表示 Tuple,但始终返回 List<Object> 对象。V2 将 Tuple 映射为 OTHER,并默认返回 Object[]
  • V2 引入了用于写入 Tuple 的 com.clickhouse.data.Tuple#Tuple。这有助于更容易判断某个值是 Tuple 还是数组。
  • PreparedStatement#setBytesResultSet#getBytes 不适用于集合类型。这些方法专门用于处理二进制字符串(字节数据)。
  • 通常使用 java.sql.Array 来写入和读取 Array 类型。JDBC 驱动对此提供了完整支持。
  • 在 V2 中,Nested 被映射为 Array,并将其表示为元组数组。
  • V2 对 java.sql.Struct 提供了部分支持,因为它与 Array 类型非常相似,但本身不支持键值对。Struct 可用于写入 Tuple 类型的值。

地理类型

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.Object不支持不支持
VariantOTHERjava.Object不支持不支持
  • V1 使用 VARCHAR 作为 UUID 的 JDBC 类型,但始终返回 java.util.UUID 对象。V2 通过将 UUID 映射为 JDBC 类型 OTHER,并同样返回 java.util.UUID 对象来修复此问题。
  • V1 对 IPv4IPv6 使用 VARCHAR,但始终返回 java.net.Inet4Addressjava.net.Inet6Address 对象。V2 通过将 IPv4IPv6 映射为 JDBC 类型 OTHER 来修复这一问题,并返回 java.net.Inet4Addressjava.net.Inet6Address 对象。
  • DynamicVariant 是 V2 中引入的新类型,在 V1 中不受支持。
  • JSON 基于 Dynamic 类型,因此仅在 V2 中受支持。
  • 可以通过 getBytes(columnIndex) 方法将 IPv4 和 IPv6 的值读取为 byte[]。不过,建议针对这些类型使用专门的类。
  • V2 不支持将 IP 地址读取为数值类型,因为更适合在 InetAddress 类中执行该转换。

数据库元数据变更

  • V2 中仅使用术语 Schema 来指代数据库,将术语 Catalog 预留用于今后使用。
  • V2 对 DatabaseMetaData.supportsTransactions()DatabaseMetaData.supportsSavepoints() 返回 false。这一行为将在后续开发中进行调整。

clickhouse-jdbc 实现了标准的 JDBC 接口。它构建于 clickhouse-client 之上,提供了诸如自定义类型映射、事务支持以及标准的同步 UPDATEDELETE 语句等附加功能,因此可以轻松与传统应用程序和工具配合使用。

注意

最新的 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,将 classifier 改为 http 可生成更小的 JAR -->
    <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://localhost 等同于 jdbc:clickhouse:http://localhost:8123
  • jdbc:ch:https://localhost 等同于 jdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICT
  • jdbc:ch:grpc://localhost 等同于 jdbc: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 值,在向非 Nullable 列插入 null 时抛出异常;1 - 按原样保留 null 值,并在插入时禁用 null 检查;2 - 将 null 替换为相应数据类型的默认值(对查询和插入均生效)
jdbcCompliancetrue是否启用对标准同步 UPDATE/DELETE 操作和伪事务的支持
typeMappings自定义 ClickHouse 数据类型与 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()) {
        // ...
    }
}

插入

注意
  • 请使用 PreparedStatement 而非 Statement

它更易于使用,但性能比 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
}

使用 input 表函数

一种性能表现优异的方案:

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

处理 DateTime 和时区

请使用 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指定 KeyStore/TrustStore 文件的类型或格式
trust_storeTrustStore 文件的路径
key_store_password用于访问 KeyStore 配置中指定的 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 中执行耗时较长的大批量插入操作时,可能会遇到如下 JDBC 超时错误:

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

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

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 处理套接字 keep-alive 设置的方式不同,需要执行额外的步骤。请按照以下步骤操作:

  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 支持的其他两个 HTTP 客户端库不允许设置套接字选项。详细指南请参阅配置 HTTP 库

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

JDBC 驱动程序的默认套接字和连接超时时间为 30 秒。可以适当增大该超时时间,以支持大批量数据插入操作。使用 ClickHouseClientoptions 方法,并结合 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();
}