Перейти к основному содержимому
Перейти к основному содержимому

JDBC-драйвер

Примечание

clickhouse-jdbc реализует стандартный интерфейс JDBC с использованием последней версии Java-клиента. Рекомендуется напрямую использовать последнюю версию 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 допускается только одна конечная точка
  • протокол следует указывать, если используется не протокол по умолчанию — 'HTTP'
  • порт следует указывать, если используется порт, отличный от порта по умолчанию '8123'
  • драйвер не определяет протокол по номеру порта, его нужно указывать явно
  • Параметр ssl не требуется, если протокол указан.

Свойства соединения

Основные параметры конфигурации определены в Java-клиенте. Их следует передавать драйверу без изменений. Драйвер имеет собственные свойства, которые не входят в конфигурацию клиента; они перечислены ниже.

Свойства драйвера:

СвойствоЗначение по умолчаниюОписание
disable_frameworks_detectiontrueОтключить определение фреймворков по заголовку User-Agent
jdbc_ignore_unsupported_valuesfalseПодавляет SQLFeatureNotSupportedException в случаях, когда это не влияет на работу драйвера
clickhouse.jdbc.v1falseИспользовать старую реализацию JDBC вместо новой
default_query_settingsnullПозволяет передавать настройки запроса по умолчанию при выполнении запросов
jdbc_resultset_auto_closetrueАвтоматически закрывает ResultSet при закрытии Statement
beta.row_binary_for_simple_insertfalseИспользовать реализацию PreparedStatement, основанную на записи в формате RowBinary. Работает только для запросов INSERT INTO ... VALUES.
jdbc_resultset_auto_closetrueАвтоматически закрывает ResultSet при закрытии объекта Statement
jdbc_use_max_result_rowsfalseВключает использование серверного свойства max_result_rows для ограничения количества строк, возвращаемых запросом. При включении переопределяет режим переполнения, установленный пользователем. Подробности см. в JavaDoc.
jdbc_sql_parserJAVACCНастраивает, какой SQL‑парсер использовать. Доступные варианты: ANTLR4, ANTLR4_PARAMS_PARSER, JAVACC.
Настройки сервера

Все настройки сервера должны иметь префикс 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.

Примечание: URL-кодирование JDBC 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
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) вернёт значение типа Float64 из столбца Int8.
    • rs.getLong(1) вернёт значение типа Long из столбца Int8.
    • rs.getByte(1) может вернуть значение типа Byte из столбца Int16, если оно укладывается в диапазон типа Byte.
  • Преобразование из более широкого типа в более узкий не рекомендуется из‑за риска повреждения данных.
  • Тип Bool также ведёт себя как числовой тип.
  • Все числовые типы можно читать как java.lang.String.

Строковые типы

Тип в ClickHouseТип в JDBCКласс в Java
StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.String
  • String можно читать только как java.lang.String или byte[].
  • FixedString считывается как есть и дополняется нулевыми байтами до длины столбца. (Например, 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 можно читать как числовые, используя специализированный числовой геттер или метод getObject(columnIndex, Integer.class).
  • Enum16 внутренне сопоставляется с типом short, а Enum8 — с типом byte. Не следует читать значения Enum16 как byte из‑за риска повреждения данных.
  • Значения 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 сопоставляются с типами java.sql для лучшей совместимости с JDBC. Однако можно получить объекты java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, используя ResultSet#getObject(columnIndex, Class<T>) и передав соответствующий класс в качестве второго аргумента.
    • rs.getObject(1, java.time.LocalDate.class) вернёт значение java.time.LocalDate из столбца Date.
    • rs.getObject(1, java.time.LocalDateTime.class) вернёт значение java.time.LocalDateTime из столбца DateTime.
    • rs.getObject(1, java.time.LocalTime.class) вернёт значение java.time.LocalTime из столбца Time.
  • Date, Date32, Time, Time64 не зависят от часового пояса сервера.
  • DateTime, DateTime64 зависят от часового пояса сервера или сеанса.
  • DateTime и DateTime64 можно получить в виде ZonedDateTime, используя getObject(colIndex, ZonedDateTime.class).

Вложенные типы

Тип 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 можно прочитать как Array с помощью метода getObject(columnIndex, Array.class). В этом случае Array#baseTypeName вернёт определение столбца типа Tuple.

Запись массивов

Используйте 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 (см. ниже, как записывать кортежи).

Пример

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

Запись кортежей

Кортежи сопоставляются с объектом 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();
    }
}

Чтение кортежей

Метод getObject(columnIndex) вернёт Object[]. Кортежи можно читать как java.sql.Array с помощью метода getObject(columnIndex, Array.class).

Пример

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

Map можно получить как объект java.util.Map, используя метод getObject(columnIndex, Map.class).

Пример

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. Этот объект предназначен для унифицированной обработки вложенных структур в разных базах данных. Объект 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

  • Nullable и LowCardinality — это специальные типы-обёртки для других типов.
  • 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.
  • UUID можно считывать и записывать в виде String с помощью метода getObject(columnIndex, String.class).
  • IPv4 и IPv6 не являются стандартными типами JDBC. Однако они входят в JDK. По умолчанию метод getObject() возвращает объекты типов java.net.Inet4Address и java.net.Inet6Address.
  • IPv4 и IPv6 можно читать и записывать как значения типа String с помощью метода getObject(columnIndex, String.class).

Обработка дат, времени и часовых поясов

java.sql.Date, java.sql.Time и java.sql.Timestamp могут усложнять вычисление часовых поясов — хотя они, разумеется, поддерживаются, имеет смысл рассмотреть использование пакета java.time. ZonedDateTime и OffsetDateTime хорошо подходят на замену типам java.sql.Timestamp, java.sql.Date и java.sql.Time.

Date vs DateTime

Date хранится без часового пояса, тогда как DateTime хранится с часовым поясом. Это может привести к неожиданным результатам, если обращаться неосторожно.

Создание соединения

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. Настройте следующие параметры ядра Linux в /etc/sysctl.conf или соответствующем конфигурационном файле:
  • 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‑клиенту.

Свойства подключения разбираются следующим образом:

  • Сначала свойства берутся из URL. Они переопределяют все остальные свойства.
  • Свойства драйвера не передаются клиенту.
  • Конечные точки (host, port, protocol) определяются из URL.

Example:

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 читается «как есть» в обеих версиях. Например, FixedString(10) для 'John' будет прочитан как '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новый тип/не поддерживаетсяновый тип/не поддерживается
  • Time и Time64 поддерживаются в V2 только как новые типы данных.
  • DateTime и DateTime64 сопоставляются с java.sql.Timestamp для обеспечения лучшей совместимости с JDBC.

Типы Enum

Тип ClickHouseСовместим с V1JDBC Type (V2)Java Class (V2)JDBC Type (V1)Java Class (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 использует STRUCT для Map, но всегда возвращает объект типа java.util.Map. V2 исправляет это за счёт сопоставления Map с JAVA_OBJECT.
  • V1 использует STRUCT для Tuple, но всегда возвращает объект типа List<Object>. V2 сопоставляет Tuple с OTHER и по умолчанию возвращает Object[].
  • V2 добавляет com.clickhouse.data.Tuple#Tuple для записи кортежей. Это упрощает определение, является ли значение кортежем или массивом.
  • Методы PreparedStatement#setBytes и ResultSet#getBytes нельзя применять к типам коллекций. Они предназначены для работы с бинарными строками.
  • Обычно для записи и чтения значений типа Array используется java.sql.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

  • Nullable и LowCardinality — это специальные типы, которые оборачивают другие типы.
  • В 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 использует VARCHAR для UUID, но при этом всегда возвращает объект java.util.UUID. V2 исправляет это, сопоставляя UUID с OTHER и также возвращая объект java.util.UUID.
  • V1 использует VARCHAR для IPv4 и IPv6, но всегда возвращает объекты 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 можно считывать в виде массива байт (byte[]), используя метод getBytes(columnIndex). Однако рекомендуется использовать специализированные классы для этих типов.
  • V2 не поддерживает чтение IP‑адресов в виде числовых значений, поскольку преобразование лучше реализовано в классах InetAddress.

Изменения метаданных базы данных

  • В V2 для обозначения баз данных используется только термин Schema. Термин Catalog зарезервирован для будущего использования.
  • V2 возвращает false для DatabaseMetaData.supportsTransactions() и DatabaseMetaData.supportsSavepoints(). Это поведение будет изменено в последующих версиях.

clickhouse-jdbc реализует стандартный интерфейс JDBC. Будучи построенным на основе clickhouse-client, он предоставляет дополнительные возможности, такие как пользовательское сопоставление типов, поддержка транзакций и стандартные синхронные команды UPDATE и DELETE, что позволяет легко использовать его с устаревшими приложениями и инструментами.

Примечание

Последняя версия JDBC (0.7.2) использует Client-V1

API clickhouse-jdbc является синхронным и, как правило, имеет больше накладных расходов (например, парсинг SQL и маппинг/преобразование типов и т. д.). Рассмотрите возможность использования clickhouse-client, когда производительность критична или если вы предпочитаете более прямой способ доступа к ClickHouse.

Требования к среде

Настройка

<!-- 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, включённый в клиент. Поскольку общей (shared) версии этого пакета нет, необходимо добавить логгер в качестве зависимости.

<!-- 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 как есть и возбуждать исключение при попытке вставить NULL в столбец без типа Nullable; 1 - обрабатывать значение NULL как есть и отключать проверку на NULL при вставке; 2 - заменять NULL на значение по умолчанию для соответствующего типа данных как при выполнении запроса, так и при вставке
jdbcCompliancetrueОпределяет, следует ли поддерживать стандартные синхронные операции UPDATE/DELETE и псевдотранзакции
typeMappingsПозволяет настроить сопоставление между типом данных ClickHouse и классом Java, что повлияет на результат работы как getColumnType(), так и getObject(Class<>?>). Например: UInt128=java.lang.String,UInt256=java.lang.String
wrapperObjectfalseОпределяет, должен ли getObject() возвращать объекты java.sql.Array / java.sql.Struct для Array / Tuple.

Примечание: дополнительную информацию см. в разделе конфигурация JDBC.

Поддерживаемые типы данных

Драйвер JDBC поддерживает те же форматы данных, что и клиентская библиотека.

Примечание
  • AggregatedFunction — ⚠️ не поддерживает конструкции вида SELECT * FROM table ...
  • Decimal — SET output_format_decimal_trailing_zeros=1 в 21.9+ для единообразия вывода
  • 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

Проще в использовании, но работает медленнее, чем функция 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-выражение (которое будет разбираться на стороне клиента и потреблять ресурсы процессора и памяти):

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.LocalDateTime или java.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-библиотеки

JDBC-коннектор ClickHouse поддерживает три HTTP-библиотеки: HttpClient, HttpURLConnection и Apache HttpClient.

Примечание

HttpClient поддерживается только в JDK 11 и выше.

Драйвер JDBC по умолчанию использует HttpClient. Вы можете изменить HTTP-библиотеку, используемую JDBC-коннектором ClickHouse, задав следующее свойство:

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

Полный список соответствующих значений:

Значение свойстваHTTP-библиотека
HTTP_CLIENTHttpClient
HTTP_URL_CONNECTIONHttpURLConnection
APACHE_HTTP_CLIENTApache HttpClient

Подключение к ClickHouse по SSL

Для установки защищённого JDBC-соединения с ClickHouse через SSL необходимо настроить свойства JDBC, включив в них параметры SSL. Как правило, это требует указания таких свойств SSL, как sslmode и sslrootcert, в JDBC URL или объекте Properties.

Свойства SSL

ИмяЗначение по умолчаниюДопустимые значенияОписание
sslfalsetrue, falseВключать ли SSL/TLS для соединения
sslmodestrictstrict, noneПроверять ли сертификат SSL/TLS
sslrootcertПуть к файлу корневых сертификатов SSL/TLS
sslcertПуть к файлу сертификата SSL/TLS
sslkeyRSA-ключ в формате PKCS#8
key_store_typeJKS, PKCS12Указывает тип или формат файла KeyStore/TrustStore
trust_storeПуть к файлу TrustStore
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. Настройте следующие параметры ядра Linux в /etc/sysctl.conf или соответствующем конфигурационном файле:
  • 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");
Примечание

В настоящее время для настройки сокетного keep-alive необходимо использовать библиотеку Apache HTTP Client, так как две другие HTTP-клиентские библиотеки, поддерживаемые clickhouse-java, не позволяют задавать параметры сокетов. Подробное руководство см. в разделе Настройка HTTP-библиотеки.

Также можно добавить эквивалентные параметры в JDBC URL.

Время ожидания сокета и подключения по умолчанию для драйвера JDBC составляет 30 секунд. Этот тайм-аут можно увеличить для поддержки операций вставки больших объёмов данных. Используйте метод options у ClickHouseClient вместе с параметрами SOCKET_TIMEOUT и CONNECTION_TIMEOUT, определёнными в ClickHouseClientOption:

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