Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Driver JDBC

Requisitos del entorno

Setup

{/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */}
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.9.8</version>
    <classifier>all</classifier>
</dependency>

Si utiliza el JDBC driver en una aplicación que requiere añadir el jar al classpath, debe descargar el jar desde:

Configuración

Clase del driver: com.clickhouse.jdbc.ClickHouseDriver

Sintaxis de URL: jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1&param2=value2][#tag1,tag2,...], por ejemplo:

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

Hay algunos aspectos que conviene tener en cuenta sobre la sintaxis de la URL:

  • solo se permite un único endpoint en la URL
  • se debe especificar el protocolo cuando no sea el predeterminado - 'HTTP'
  • se debe especificar el puerto cuando no sea el predeterminado '8123'
  • el driver no infiere el protocolo a partir del puerto, debe especificarlo explícitamente
  • El parámetro ssl no se requiere cuando se especifica el protocolo.

Propiedades de conexión

Los principales parámetros de configuración se definen en el cliente de Java. Deben pasarse tal cual al driver. El driver tiene algunas propiedades propias que no forman parte de la configuración del cliente; estas se enumeran a continuación.

Propiedades del driver:

Propiedad Predeterminado Descripción
disable_frameworks_detection true Desactiva la detección de frameworks del User-Agent
jdbc_ignore_unsupported_values false Suprime SQLFeatureNotSupportedException cuando no afecta al funcionamiento del driver
clickhouse.jdbc.v1 false Usa la implementación anterior de JDBC en lugar de la nueva
default_query_settings null Permite pasar ajustes de consulta predeterminados en las operaciones de consulta
jdbc_resultset_auto_close true Cierra automáticamente ResultSet cuando se cierra el Statement
beta.row_binary_for_simple_insert false Utiliza la implementación de PreparedStatement basada en el generador RowBinary. Solo funciona con consultas INSERT INTO ... VALUES.
jdbc_resultset_auto_close true Cierra automáticamente ResultSet cuando se cierra el Statement
jdbc_use_max_result_rows false Permite usar la propiedad del servidor max_result_rows para limitar la cantidad de filas que devuelve la consulta. Cuando está habilitado, anula el modo de desbordamiento configurado por el usuario. Consulte JavaDoc para obtener más información.
jdbc_sql_parser JAVACC Configura qué parser de SQL usar. Opciones: ANTLR4, ANTLR4_PARAMS_PARSER, JAVACC.
remember_last_set_roles true Recuerda los últimos roles establecidos para la conexión.

Ejemplo de configuración:

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

lo que será equivalente a la siguiente URL JDBC:

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.

Nota: no es necesario codificar en URL la URL JDBC ni las propiedades; se codificarán automáticamente.

Perfiles de solo lectura

Evitamos deliberadamente añadir configuraciones predeterminadas a las propiedades de conexión para evitar problemas con los perfiles de solo lectura. Sin embargo, algunos usuarios necesitan pasar configuraciones de formato (por ejemplo, para leer JSON como String) y recomendamos usar el perfil readonly=2. Lea más sobre los perfiles de solo lectura aquí.

Identificación del cliente

Existen dos formas de identificar la aplicación que originó una solicitud: establecer com.clickhouse.client.api.ClientConfigProperties#CLIENT_NAME mediante las propiedades de conexión o utilizar el método 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");

Ambas formas producirán el siguiente valor de http_user_agent en el registro de consultas:

my-app-01/1.0 jdbc-v2/0.9.7 clickhouse-java-v2/0.9.6 (Linux; jvm:17.0.17) Apache-HttpClient/5.4.4

Nota: Recomendamos usar el formato app_name/version para la propiedad client_name, ya que facilita la identificación de la aplicación en el registro de consultas.

Identificación de operaciones

El controlador JDBC genera un query_id para cada operación (actualmente se incluye en las excepciones del servidor).

Para establecer log_comment en una operación, utilice el método com.clickhouse.jdbc.StatementImpl#getLocalSettings. Para ello, es necesario hacer un cast de Statement o PreparedStatement a com.clickhouse.jdbc.StatementImpl primero.

StatementImpl stmt = (StatementImpl) conn.createStatement();
stmt.getLocalSettings().logComment("some-comment");

Nota: este enfoque funciona para usos de sentencias en un solo hilo porque localSettings se comparte entre hilos.

Tipos de datos compatibles

El JDBC driver admite los mismos formatos de datos que el java client subyacente.

Correspondencia de tipos JDBC

La siguiente correspondencia se aplica a:

  • ResultSet#getObject(columnIndex) - el método devuelve un objeto de la clase Java correspondiente. (Int8 -> java.lang.Byte, Int16 -> java.lang.Short, etc.)
  • ResultSetMetaData#getColumnType(columnIndex) - el método devuelve el tipo JDBC correspondiente. (Int8 -> java.lang.Byte, Int16 -> java.lang.Short, etc.)

Hay varias formas de cambiar la correspondencia:

  • ResultSet#getObject(columnIndex, class) - el método intentará convertir el valor al tipo class. Hay algunas limitaciones de conversión. Consulta cada sección para ver más detalles.

Tipos numéricos

Tipo de ClickHouse Tipo JDBC Clase Java
Int8 TINYINT java.lang.Byte
Int16 SMALLINT java.lang.Short
Int32 INTEGER java.lang.Integer
Int64 BIGINT java.lang.Long
Int128 NUMERIC java.math.BigInteger
Int256 NUMERIC java.math.BigInteger
UInt8 SMALLINT java.lang.Short
UInt16 INTEGER java.lang.Integer
UInt32 BIGINT java.lang.Long
UInt64 NUMERIC java.math.BigInteger
UInt128 NUMERIC java.math.BigInteger
UInt256 NUMERIC java.math.BigInteger
Float32 FLOAT 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
  • los tipos numéricos son convertibles entre sí. Así, Int8 se puede obtener como Float64 y viceversa.:
    • rs.getObject(1, Float64.class) devolverá un valor Float64 de la columna Int8.
    • rs.getLong(1) devolverá un valor Long de la columna Int8.
    • rs.getByte(1) puede devolver un valor Byte de la columna Int16 si cabe en Byte.
  • no se recomienda la conversión de un tipo de mayor capacidad a uno de menor capacidad debido al riesgo de corrupción de datos.
  • El tipo Bool también funciona como número.
  • Todos los tipos numéricos pueden leerse como java.lang.String.
  • Almacenar Float.MAX_VALUE de Java como Float presenta un problema (https://github.com/ClickHouse/clickhouse-java/issues/809). Guardar ese mismo valor como Double lo resuelve.

Tipos de cadena

Tipo de ClickHouse Tipo JDBC Clase Java
String VARCHAR java.lang.String
FixedString VARCHAR java.lang.String
  • String solo se puede leer como java.lang.String o byte[].
  • FixedString se lee tal cual y se rellena con ceros hasta alcanzar la longitud de la columna. (Por ejemplo, FixedString(10) para 'John' se leerá como 'John\0\0\0\0\0\0\0\0\0'.)

Tipos enum

Tipo de ClickHouse Tipo JDBC Clase Java
Enum8 VARCHAR java.lang.String
Enum16 VARCHAR java.lang.String
  • Enum8 y Enum16 se asignan a java.lang.String de manera predeterminada.
  • Los valores enum pueden leerse como valores numéricos mediante el método getter correspondiente o el método getObject(columnIndex, Integer.class).
  • Enum16 se mapea internamente a short y Enum8 se mapea a byte. Debe evitarse leer Enum16 como byte debido al riesgo de corrupción de datos.
  • Los valores de enum pueden establecerse como una cadena o un valor numérico en PreparedStatement.

Tipos de fecha/hora

Tipo de ClickHouse Tipo JDBC Clase 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
  • Los tipos Date / Time se asignan a tipos java.sql para lograr una mejor compatibilidad con JDBC. Sin embargo, es posible obtener java.time.LocalDate, java.time.LocalDateTime y java.time.LocalTime mediante ResultSet#getObject(columnIndex, Class<T>), usando la clase correspondiente como segundo argumento.
    • rs.getObject(1, java.time.LocalDate.class) devolverá un valor java.time.LocalDate de la columna Date.
    • rs.getObject(1, java.time.LocalDateTime.class) devolverá un valor java.time.LocalDateTime de la columna DateTime.
    • rs.getObject(1, java.time.LocalTime.class) devolverá un valor java.time.LocalTime de la columna Time.
  • Date, Date32, Time, Time64 no se ven afectados por la zona horaria del servidor.
  • DateTime y DateTime64 se ven afectados por la zona horaria del servidor o de la sesión.
  • DateTime y DateTime64 se pueden recuperar como ZonedDateTime mediante getObject(colIndex, ZonedDateTime.class).

Tipos anidados

Tipo de ClickHouse Tipo JDBC Clase Java
Array ARRAY java.sql.Array
Tuple OTHER com.clickhouse.data.Tuple
Map OTHER java.util.Map
Nested ARRAY java.sql.Array
  • Array se asigna a java.sql.Array de forma predeterminada para mantener la compatibilidad con JDBC. Esto también se hace para proporcionar más información sobre el valor de array devuelto. Resulta útil para la inferencia de tipos.
  • Array implementa el método getResultSet() para devolver un java.sql.ResultSet con el mismo contenido que el array original.
  • Los tipos de colección no deberían interpretarse como java.lang.String, ya que no es una forma válida de representar los datos (p. ej., en un array no se usan comillas para los valores de cadena).
  • Map se asigna a OTHER porque el valor solo puede leerse mediante el método getObject(columnIndex, Class<T>).
    • Map no es un java.sql.Struct porque no tiene columnas con nombres.
  • Tuple se mapea a Object[] porque puede contener distintos tipos y no es válido usar List.
  • Tuple puede leerse como Array mediante el método getObject(columnIndex, Array.class). En este caso, Array#baseTypeName devolverá la definición de la columna Tuple.

Metadatos del tipo de elemento de Array

Array.getBaseTypeName() devuelve el nombre del tipo de elemento de ClickHouse; Array.getBaseType() devuelve el código de tipo JDBC. JDBC V2 conserva las firmas de tipo completas (tipos envoltorio, parámetros de tipo) que V1 elimina.

Las reglas generales de correspondencia para los arrays son:

Tipo de ClickHouse getBaseTypeName() getBaseType()
Array(<Tipo primitivo>) <Tipo primitivo> <Tipo primitivo de JDBC>
Array(<Parameterized Type>(<N>)) <Tipo parametrizado>(<N>) <tipo JDBC del tipo base>
Array(Nullable(<Type>)) Nullable(<Type>) <Tipo JDBC del Type interno>
Array(LowCardinality(<Type>)) LowCardinality(<Type>) <Tipo JDBC del Type interno>
Array(Array(...(<Type>))) <Type> (elemento más interno) <Tipo JDBC del elemento más interno>
Array(Tuple(...)) Tuple(...) (definición completa) OTHER
Array(Enum8(...)) / Array(Enum16(...)) Enum8(...) / Enum16(...) (definición completa) VARCHAR

Notas sobre las reglas anteriores:

  • Los tipos de envoltorio (Nullable, LowCardinality) se conservan en getBaseTypeName(), pero getBaseType() se resuelve como el código JDBC del tipo interno.
  • Los arrays anidados se aplanan en los metadatos: getBaseTypeName() devuelve el tipo del elemento más interno que no sea un array, no el hijo inmediato.
  • Los tipos parametrizados (FixedString(N), definiciones completas de Enum/Tuple) conservan sus parámetros en getBaseTypeName().

Ejemplos:

Tipo de ClickHouse (Ejemplo) getBaseTypeName() getBaseType()
Array(Int8) Int8 TINYINT
Array(Int16) Int16 SMALLINT
Array(Int32) Int32 INTEGER
Array(Int64) Int64 BIGINT
Array(UInt8) UInt8 SMALLINT
Array(UInt16) UInt16 INTEGER
Array(UInt32) UInt32 BIGINT
Array(UInt64) UInt64 NUMERIC
Array(Float32) Float32 FLOAT
Array(Float64) Float64 DOUBLE
Array(String) String VARCHAR
Array(FixedString(8)) FixedString(8) VARCHAR
Array(Bool) Bool BOOLEAN
Array(Date) Date DATE
Array(DateTime) DateTime TIMESTAMP
Array(UUID) UUID OTHER
Array(Nullable(Int32)) Nullable(Int32) INTEGER
Array(Nullable(String)) Nullable(String) VARCHAR
Array(LowCardinality(String)) LowCardinality(String) VARCHAR
Array(LowCardinality(Nullable(String))) LowCardinality(Nullable(String)) VARCHAR
Array(Array(Int32)) Int32 INTEGER
Array(Array(Array(String))) String VARCHAR
Array(Tuple(name String, val Int32)) Tuple(name String, val Int32) OTHER
Array(Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3)) Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3) VARCHAR
  • En V2, getBaseTypeName() conserva la firma de tipo completa, incluidos los tipos envoltorio (Nullable, LowCardinality) y los parámetros de tipo (FixedString(8), las definiciones completas de Enum y Tuple). V1 los elimina y devuelve solo el nombre del tipo base.
  • Los arrays de Tuple usan OTHER (1111) en V2 en lugar de STRUCT (2002) porque las tuplas de ClickHouse tienen campos con nombre, algo que java.sql.Struct no admite.
  • Los arrays de UUID usan OTHER (1111) en V2, en consonancia con la asignación escalar de UUID.
  • Los valores Enum se asignan a VARCHAR: los miembros de enum se identifican por su nombre de cadena, independientemente de su codificación numérica subyacente.

Escritura de Arrays

Use java.sql.Connection#createArrayOf para instanciar el objeto java.sql.Array. Este objeto está diseñado para unificar el manejo de arrays en distintas bases de datos. Se necesita una conexión para pasar la configuración al método de fábrica de Array.

El método acepta dos argumentos:

  • typeName - nombre del tipo de los elementos del array. Por ejemplo, Array(Int32) -> "Int32".
  • elements - los elementos reales del array. Por ejemplo [[1, 2, 3], [4, 5, 6]] -> new Integer[][] {{1, 2, 3}, {4, 5, 6}}.

Tuple puede representarse como Object[] o como java.sql.Struct (consulte cómo escribir tuples más adelante).

Ejemplo

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

Lectura de Arrays

Use ResultSet#getArray(columnIndex) para leer un objeto Array. El objeto puede utilizarse para acceder a arrays de cualquier profundidad de anidamiento. El método Array#getResultSet() puede emplearse para leer los elementos del array de forma más uniforme como java.sql.ResultSet. Resulta útil cuando se desconoce el tipo exacto de los elementos del array.

Ejemplo

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

Escritura de Tuples

Los Tuples se mapean al objeto com.clickhouse.data.Tuple y deben escribirse como dicho objeto mediante una llamada al método setObject(columnIndex, tuple). Es posible usar el objeto java.sql.Struct para escribir tuples con mayor portabilidad.

Ejemplo

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

Lectura de Tuples

El método getObject(columnIndex) devolverá Object[]. Los Tuples pueden leerse como java.sql.Array mediante el método getObject(columnIndex, Array.class).

Ejemplo

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

Escritura de Maps

Map solo puede escribirse como un objeto java.collections.Map porque este tipo requiere pares clave-valor (java.sql.Struct no admite pares clave-valor).

Ejemplo

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

Lectura de mapas

Map puede leerse como un objeto java.collections.Map utilizando el método getObject(columnIndex, Map.class).

Ejemplo

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

Escritura de Nested

Utilice java.sql.Connection#createStruct para instanciar el objeto java.sql.Struct. Este objeto está diseñado para unificar el manejo de estructuras anidadas en distintas bases de datos. Se necesita una conexión para pasar la configuración al método de fábrica de Struct.

El método acepta dos argumentos:

  • typeName - nombre del tipo de los elementos anidados. Por ejemplo, Nested(Tuple(Int32, String)) -> "Nested(Tuple(Int32, String))".
  • elements - los elementos anidados propiamente dichos. Por ejemplo [1, 'test'] -> new Object[] {1, 'test'}.

Ejemplo

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

Lectura de Nested

Use ResultSet#getStruct(columnIndex, StructDescriptor) para leer el objeto Nested. El objeto puede utilizarse para acceder a elementos anidados de cualquier nivel de profundidad. El método Struct#getResultSet() puede emplearse para leer elementos anidados de forma más uniforme, como java.sql.ResultSet. Resulta útil cuando el tipo exacto de los elementos anidados es desconocido.

Ejemplo

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

Geo Types

Tipo de ClickHouse Tipo JDBC Clase Java
Point OTHER double[]
Ring OTHER double[][]
Polygon OTHER double[][][]
MultiPolygon OTHER double[][][][]

Tipos Nullable y LowCardinality

  • Nullable y LowCardinality son tipos especiales que envuelven otros tipos.
  • Nullable afecta a la forma en que se devuelven los nombres de tipo en ResultSetMetaData

Tipos especiales

Tipo de ClickHouse Tipo JDBC Clase Java
UUID OTHER java.util.UUID
IPv4 OTHER java.net.Inet4Address
IPv6 OTHER java.net.Inet6Address
JSON OTHER java.lang.String
AggregateFunction OTHER (representación binaria)
SimpleAggregateFunction (tipo envuelto) (clase envuelta)
  • UUID no es un tipo estándar de JDBC. Sin embargo, forma parte del JDK. De forma predeterminada, el método getObject() devuelve java.util.UUID.
  • UUID puede leerse y escribirse como String mediante el método getObject(columnIndex, String.class).
  • IPv4 e IPv6 no son tipos estándar de JDBC. Sin embargo, forman parte del JDK. De forma predeterminada, el método getObject() devuelve java.net.Inet4Address y java.net.Inet6Address.
  • IPv4 y IPv6 se pueden leer y escribir como String mediante el método getObject(columnIndex, String.class).

Tipo JSON

El tipo JSON se asigna a Map<String, Object> de forma predeterminada, donde las claves son las claves del objeto JSON y los valores son los valores del objeto JSON. Por ejemplo:

{
    "key1": "value1",
    "key2": ["value2", "value3"]
    "key3": {
        "key4": "value4",
        "key5": "value5"
    }
}

se asignará a:

Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", Arrays.asList("value2", "value3"));
map.put("key3", new HashMap<String, Object>() {{
    put("key4", "value4");
    put("key5", "value5");
}});

Existe una opción más conveniente para leer JSON como String pasando la configuración del servidor jdbc_read_json_as_string=true a las propiedades de conexión. Esto hace que el driver devuelva los valores JSON como String y permite procesarlos con cualquier biblioteca JSON.

Properties properties = new Properties();
properties.setProperty(
                ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING),
                "1");
try (Connection conn = DriverManager.getConnection(url, properties)) {
     try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_json ORDER BY order")) {
        while (rs.next()) {
            String json = rs.getString("json");
            // ...
        }
    }
}

A partir de la versión 25.8 de ClickHouse, los números ya no se entrecomillan de forma predeterminada. Para versiones anteriores, puede deshabilitar el entrecomillado pasando la configuración del servidor a las propiedades de conexión:

Properties properties = new Properties();
properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0");
properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0");
properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0");

Manejo de fechas, horas y zonas horarias

Lea la Guía de Date/Time, que explica los errores más comunes y la lógica del driver al gestionar valores de Date/Time y timestamps.

Creación de conexión

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

Suministro de credenciales y configuración

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
}

Instrucción simple


try (Connection conn = dataSource.getConnection(...);
    Statement stmt = conn.createStatement()) {
    ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
    while(rs.next()) {
        // ...
    }
}

Insert

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
    }
}

Más información

Para más información, consulte nuestro repositorio de GitHub y la documentación del cliente de Java.

Solución de problemas

Registro

El driver utiliza slf4j para el logging y usará la primera implementación disponible en el classpath.

Resolución de timeout de JDBC en inserts de gran volumen

Al realizar inserciones grandes en ClickHouse con tiempos de ejecución prolongados, es posible que encuentre errores de timeout de JDBC como los siguientes:

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

Estos errores pueden interrumpir el proceso de inserción de datos y afectar la estabilidad del sistema. Para resolver este problema, es posible que necesite ajustar algunas configuraciones de timeout en el sistema operativo del cliente.

Mac OS

En Mac OS, se pueden ajustar los siguientes parámetros para resolver el problema:

  • 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

En Linux, la configuración equivalente por sí sola puede no resolver el problema. Se requieren pasos adicionales debido a las diferencias en la forma en que Linux gestiona la configuración de keep-alive de los sockets. Siga estos pasos:

  1. Ajuste los siguientes parámetros del kernel de Linux en /etc/sysctl.conf o en un archivo de configuración relacionado:
  • 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 (Puede considerar reducir este valor frente al valor predeterminado de 300 segundos)
  1. Después de modificar los parámetros del kernel, aplique los cambios ejecutando el siguiente comando:
sudo sysctl -p

Tras configurar estos parámetros, debe asegurarse de que su cliente tenga habilitada la opción Keep Alive en el socket:

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

Guía de migración

Cambios principales

Funcionalidad V1 (Anterior) V2 (Nueva)
Compatibilidad con transacciones Compatibilidad parcial No compatible
Cambio de nombre de columnas en la respuesta Compatibilidad parcial No compatible
SQL con múltiples sentencias No compatible No permitido
Parámetros con nombre Compatible No compatible (no forma parte de la especificación JDBC)
Transmisión de datos con PreparedStatement Compatible No compatible
  • JDBC V2 se implementó para ser más ligero y se eliminaron algunas funcionalidades.
    • El streaming de datos no es compatible con JDBC V2 porque no forma parte de la especificación de JDBC ni de Java.
  • JDBC V2 requiere una configuración explícita. No hay valores predeterminados para failover.
    • El protocolo debe especificarse en la URL. No se detecta implícitamente a partir de los números de puerto.

Cambios de configuración

Solo hay dos enums:

  • com.clickhouse.jdbc.DriverProperties - las propiedades de configuración del propio driver.
  • com.clickhouse.client.api.ClientConfigProperties - las propiedades de configuración del cliente. Los cambios en la configuración del cliente se describen en la documentación del cliente Java.

Las propiedades de conexión se procesan de la siguiente manera:

  • La URL se procesa primero para extraer las propiedades. Estas prevalecen sobre todas las demás propiedades.
  • Las propiedades del driver no se pasan al cliente.
  • Los endpoints (host, port, protocol) se extraen de la URL.

Ejemplo:

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
}

Cambios en los tipos de datos

Tipos numéricos

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase 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 NUMERIC java.math.BigInteger NUMERIC java.math.BigInteger
Int256 NUMERIC java.math.BigInteger NUMERIC java.math.BigInteger
UInt8 SMALLINT java.lang.Short SMALLINT com.clickhouse.data.value.UnsignedByte
UInt16 INTEGER java.lang.Integer INTEGER com.clickhouse.data.value.UnsignedShort
UInt32 BIGINT java.lang.Long BIGINT com.clickhouse.data.value.UnsignedInteger
UInt64 NUMERIC java.math.BigInteger NUMERIC com.clickhouse.data.value.UnsignedLong
UInt128 NUMERIC java.math.BigInteger NUMERIC java.math.BigInteger
UInt256 NUMERIC java.math.BigInteger NUMERIC java.math.BigInteger
Float32 FLOAT java.lang.Float FLOAT 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
  • La principal diferencia es que los tipos sin signo se corresponden con tipos de Java para mejorar la portabilidad.

Tipos de cadena

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase de Java (V1)
String VARCHAR java.lang.String VARCHAR java.lang.String
FixedString VARCHAR java.lang.String VARCHAR java.lang.String
  • FixedString se lee tal cual en ambas versiones. Por ejemplo, FixedString(10) para 'John' se leerá como 'John\0\0\0\0\0\0\0\0\0'.
  • Cuando se utiliza PreparedStatement#setBytes, se convierte en unhex('<hex_string>') y luego se interpreta como String.
  • Las cadenas se almacenan con codificación UTF-8.

Tipos de fecha/hora

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase Java (V1)
Fecha DATE java.sql.Date DATE java.time.LocalDate
Date32 DATE java.sql.Date DATE java.time.LocalDate
DateTime TIMESTAMP java.sql.Timestamp TIMESTAMP_WITH_TIMEZONE java.time.OffsetDateTime
DateTime64 TIMESTAMP java.sql.Timestamp TIMESTAMP_WITH_TIMEZONE java.time.OffsetDateTime
Time TIME java.sql.Time nuevo tipo/no compatible nuevo tipo/no compatible
Time64 TIME java.sql.Time tipo nuevo/no compatible tipo nuevo/no compatible
  • Time y Time64 solo son compatibles en V2 como tipos nuevos.
  • DateTime y DateTime64 se asignan a java.sql.Timestamp para mejorar la compatibilidad con JDBC.

Tipos enum

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase 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

Tipos anidados

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase Java (V1)
Array ARRAY java.sql.Array ARRAY Object[] o un array de tipos primitivos
Tuple OTHER Object[] STRUCT java.sql.Struct
Map JAVA_OBJECT java.util.Map STRUCT java.util.Map
Nested ARRAY java.sql.Array STRUCT java.sql.Struct
  • En V2, Array se asigna a java.sql.Array de forma predeterminada para ser compatible con JDBC. Esto también se hace para proporcionar más información sobre el valor de array devuelto. Útil para la inferencia de tipos.
  • En V2, Array implementa el método getResultSet() para devolver un java.sql.ResultSet con el mismo contenido que el array original.
  • V1 usa STRUCT para Map, pero siempre devuelve un objeto java.util.Map. V2 corrige esto al mapear Map a JAVA_OBJECT.
  • V1 usa STRUCT para Tuple, pero siempre devuelve un objeto List<Object>. V2 asigna Tuple a OTHER y devuelve Object[] de forma predeterminada.
  • V2 introduce com.clickhouse.data.Tuple#Tuple para escribir tuplas. Simplifica la tarea de determinar si un valor es una tupla o un Array.
  • PreparedStatement#setBytes y ResultSet#getBytes no pueden usarse con tipos de colección. Estos métodos están diseñados para trabajar con cadenas binarias.
  • Normalmente, java.sql.Array se utiliza para escribir y leer tipos Array. El controlador JDBC ofrece compatibilidad total con ello.
  • V2 asigna Nested a Array y lo presenta como un array de tuplas.
  • V2 ofrece compatibilidad parcial con java.sql.Struct porque es muy similar al tipo Array y no admite pares clave-valor. Struct puede utilizarse para escribir valores Tuple.

Geo Types

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase de Java (V1)
Point OTHER double[] OTHER double[]
Ring OTHER double[][] OTHER double[][]
Polygon OTHER double[][][] OTHER double[][][]
MultiPolygon OTHER double[][][][] OTHER double[][][][]

Tipos Nullable y LowCardinality

  • Nullable y LowCardinality son tipos especiales que encapsulan otros tipos.
  • No se realizan cambios en estos tipos en la V2.

Tipos especiales

Tipo de ClickHouse Compatible con V1 Tipo JDBC (V2) Clase Java (V2) Tipo JDBC (V1) Clase de Java (V1)
JSON OTHER java.lang.String no admitido no admitido
AggregateFunction OTHER (representación binaria) OTHER (representación binaria)
SimpleAggregateFunction (tipo envuelto) (clase envuelta) (tipo envuelto) (clase envuelta)
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.Object no admitido no admitido
Variant OTHER java.Object compatible compatible
  • V1 usa VARCHAR para UUID, pero siempre devuelve un objeto java.util.UUID. V2 corrige esto al mapear UUID a OTHER y devuelve un objeto java.util.UUID.
  • V1 usa VARCHAR para IPv4 e IPv6, pero siempre devuelve objetos java.net.Inet4Address y java.net.Inet6Address. V2 corrige esto asignando IPv4 e IPv6 a OTHER y devuelve objetos java.net.Inet4Address y java.net.Inet6Address.
  • Dynamic y Variant son tipos nuevos en V2. No se admiten en V1.
  • JSON se basa en el tipo Dynamic. Por ello, solo se admite en V2.
  • Los valores IPv4 e IPv6 pueden leerse como byte[] mediante el método getBytes(columnIndex). Sin embargo, se recomienda usar las clases específicas para estos tipos.
  • V2 no admite la lectura de direcciones IP como valores numéricos porque la conversión está mejor implementada en las clases InetAddress.

Cambios en los metadatos de la base de datos

  • V2 usa únicamente el término Schema para referirse a las bases de datos. El término Catalog queda reservado para usos futuros.
  • V2 devuelve false para DatabaseMetaData.supportsTransactions() y DatabaseMetaData.supportsSavepoints(). Esto cambiará en el futuro.
  • En DatabaseMetaData.getTypeInfo(), las columnas LITERAL_PREFIX y LITERAL_SUFFIX ahora devuelven null para los tipos de datos en los que no se esperan prefijos ni sufijos (por ejemplo, los tipos numéricos). En V1, estas columnas devolvían valores no nulos para esos tipos. Estas columnas deben usarse al generar consultas SQL para entrecomillar correctamente los valores literales según su tipo de dato.

clickhouse-jdbc implementa la interfaz JDBC estándar. Al estar construido sobre clickhouse-client, ofrece funcionalidades adicionales como correspondencia de tipos personalizada, soporte de transacciones y sentencias UPDATE y DELETE síncronas estándar, entre otras, lo que permite integrarlo fácilmente con aplicaciones y herramientas heredadas.

La API de clickhouse-jdbc es síncrona y, en general, presenta mayor sobrecarga (por ejemplo, análisis de SQL y correspondencia/conversión de tipos, etc.). Considere usar clickhouse-client cuando el rendimiento sea crítico o si prefiere una forma más directa de acceder a ClickHouse.

Requisitos del entorno

Setup

{/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */}
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.7.2</version>
    {/* usa el uber JAR con todas las dependencias incluidas; cambia el classifier a http para obtener un JAR más pequeño */}
    <classifier>shaded-all</classifier>
</dependency>

Desde la versión 0.5.0, utilizamos Apache HTTP Client empaquetado dentro del Client. Como no existe una versión compartida del paquete, es necesario añadir un logger como dependencia.

{/* https://mvnrepository.com/artifact/org.slf4j/slf4j-api */}
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.16</version>
</dependency>

Configuración

Clase del driver: com.clickhouse.jdbc.ClickHouseDriver

Sintaxis de URL: jdbc:(ch|clickhouse)[:<protocol>]://endpoint1[,endpoint2,...][/<database>][?param1=value1&param2=value2][#tag1,tag2,...], por ejemplo:

  • jdbc:ch://localhost es igual que jdbc:clickhouse:http://localhost:8123
  • jdbc:ch:https://localhost es lo mismo que jdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICT
  • jdbc:ch:grpc://localhost es lo mismo que jdbc:clickhouse:grpc://localhost:9100

Propiedades de conexión:

Propiedad Predeterminado Descripción
continueBatchOnError false Indica si se debe continuar el procesamiento por lotes cuando se produce un error
createDatabaseIfNotExist false Indica si se debe crear la base de datos si no existe
custom_http_headers HTTP headers personalizados separados por comas, por ejemplo: User-Agent=client1,X-Gateway-Id=123
custom_http_params parámetros de consulta HTTP personalizados separados por comas; por ejemplo: extremes=0,max_result_rows=100
nullAsDefault 0 0 - tratar el valor null tal cual y lanzar una excepción al insertar null en una columna no Nullable; 1 - tratar el valor null tal cual y desactivar la comprobación de null al insertar; 2 - sustituir null por el valor predeterminado del tipo de dato correspondiente tanto en la consulta como en la inserción
jdbcCompliance true Indica si se admiten las operaciones estándar UPDATE/DELETE síncronas y una transacción simulada
typeMappings Personalice la asignación entre el tipo de dato de ClickHouse y la clase de Java, lo que afectará al resultado de getColumnType() y de getObject(Class<>?>). Por ejemplo: UInt128=java.lang.String,UInt256=java.lang.String
wrapperObject false Indica si getObject() debe devolver java.sql.Array / java.sql.Struct para Array / Tuple.

Nota: consulte la configuración específica de JDBC para obtener más información.

Tipos de datos compatibles

El driver JDBC admite los mismos formatos de datos que la biblioteca cliente.

Creación de conexión

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

Instrucción simple


try (Connection conn = dataSource.getConnection(...);
    Statement stmt = conn.createStatement()) {
    ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
    while(rs.next()) {
        // ...
    }
}

Insert

Es más fácil de usar, pero ofrece un rendimiento más lento en comparación con la función input (ver más abajo):

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
}

Con la table function input

Una opción con excelentes características de rendimiento:

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
}

Insert con placeholders

Esta opción se recomienda únicamente para inserciones pequeñas, ya que requeriría una expresión SQL larga (que se procesará en el lado del cliente y consumirá CPU y memoria):

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

Gestión de DateTime y zonas horarias

Se recomienda usar java.time.LocalDateTime o java.time.OffsetDateTime en lugar de java.sql.Timestamp, y java.time.LocalDate en lugar de 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);
    }
    ...
}

Manejo de AggregateFunction

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

Configuración de la biblioteca HTTP

El conector JDBC de ClickHouse admite tres bibliotecas HTTP: HttpClient, HttpURLConnection y Apache HttpClient.

El JDBC driver utiliza HttpClient de forma predeterminada. Puede cambiar la biblioteca HTTP utilizada por el JDBC connector de ClickHouse mediante la siguiente propiedad:

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

A continuación se muestra una lista completa de los valores correspondientes:

Valor de la propiedad Biblioteca HTTP
HTTP_CLIENT HttpClient
HTTP_URL_CONNECTION HttpURLConnection
APACHE_HTTP_CLIENT Apache HttpClient

Conectarse a ClickHouse mediante SSL

Para establecer una conexión JDBC segura a ClickHouse mediante SSL, debe configurar las propiedades JDBC para incluir los parámetros SSL. Esto generalmente implica especificar propiedades SSL como sslmode y sslrootcert en la URL de JDBC o en el objeto Properties.

Propiedades SSL

Nombre Valor predeterminado Valores opcionales Descripción
ssl false true, false Indica si se debe habilitar SSL/TLS para la conexión.
sslmode strict strict, none Si se verifica el certificado SSL/TLS
sslrootcert Ruta de los certificados raíz SSL/TLS
sslcert Ruta del certificado SSL/TLS
sslkey Clave RSA en formato PKCS#8
key_store_type JKS, PKCS12 Especifica el tipo o formato del archivo KeyStore/TrustStore
trust_store Ruta del archivo TrustStore
key_store_password Contraseña necesaria para acceder al archivo KeyStore especificado en la configuración de KeyStore

Estas propiedades garantizan que su aplicación Java se comunique con el servidor de ClickHouse a través de una conexión cifrada, lo que mejora la seguridad de los datos durante la transmisión.

  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

      }
  }

Resolución de timeout de JDBC en inserts de gran volumen

Al realizar inserciones grandes en ClickHouse con tiempos de ejecución prolongados, es posible que encuentre errores de timeout de JDBC como los siguientes:

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

Estos errores pueden interrumpir el proceso de inserción de datos y afectar la estabilidad del sistema. Para resolver este problema, es necesario ajustar algunas configuraciones de timeout en el sistema operativo del cliente.

Mac OS

En Mac OS, se pueden ajustar los siguientes parámetros para resolver el problema:

  • 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

En Linux, la configuración equivalente por sí sola puede no resolver el problema. Se requieren pasos adicionales debido a las diferencias en la forma en que Linux gestiona la configuración de keep-alive de los sockets. Siga estos pasos:

  1. Ajuste los siguientes parámetros del kernel de Linux en /etc/sysctl.conf o en un archivo de configuración relacionado:
  • 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 (Puede considerar reducir este valor respecto del valor predeterminado de 300 segundos)
  1. Después de modificar los parámetros del kernel, aplique los cambios ejecutando el siguiente comando:
sudo sysctl -p

Tras configurar estos parámetros, debe asegurarse de que su cliente tenga habilitada la opción Keep Alive en el socket:

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

Como alternativa, puede agregar parámetros equivalentes a la URL de JDBC.

El socket predeterminado y el timeout de conexión para el driver JDBC es de 30 segundos. El timeout puede aumentarse para admitir operaciones de inserción de grandes volúmenes de datos. Utilice el método options en ClickHouseClient junto con las opciones SOCKET_TIMEOUT y CONNECTION_TIMEOUT tal como las define 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();
}
Navigation