Requisitos del entorno
- OpenJDK versión >= 8
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>// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
implementation("com.clickhouse:clickhouse-jdbc:0.9.8:all")// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
implementation 'com.clickhouse:clickhouse-jdbc:0.9.8:all'Si utiliza el JDBC driver en una aplicación que requiere añadir el jar al classpath, debe descargar el jar desde:
- Maven Central y agrégalo al classpath
- a partir de la versión
0.9.4, está disponible el artefacto https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc-all - use el calificador
allpara obtener el JAR con todas las dependencias empaquetadas incluidas.
- a partir de la versión
- o desde el repositorio oficial aquí
Configuración
Clase del driver: com.clickhouse.jdbc.ClickHouseDriver
Sintaxis de URL: jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...], por ejemplo:
jdbc:clickhouse:http://localhost:8123jdbc: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
sslno 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.4Nota: 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 tipoclass. 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í,
Int8se puede obtener comoFloat64y viceversa.:rs.getObject(1, Float64.class)devolverá un valorFloat64de la columnaInt8.rs.getLong(1)devolverá un valorLongde la columnaInt8.rs.getByte(1)puede devolver un valorBytede la columnaInt16si cabe enByte.
- 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
Booltambién funciona como número. - Todos los tipos numéricos pueden leerse como
java.lang.String. - Almacenar
Float.MAX_VALUEde Java comoFloatpresenta un problema (https://github.com/ClickHouse/clickhouse-java/issues/809). Guardar ese mismo valor comoDoublelo resuelve.
Tipos de cadena
| Tipo de ClickHouse | Tipo JDBC | Clase Java |
|---|---|---|
| String | VARCHAR | java.lang.String |
| FixedString | VARCHAR | java.lang.String |
Stringsolo se puede leer comojava.lang.Stringobyte[].FixedStringse 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 |
Enum8yEnum16se asignan ajava.lang.Stringde manera predeterminada.- Los valores
enumpueden leerse como valores numéricos mediante el método getter correspondiente o el métodogetObject(columnIndex, Integer.class). Enum16se mapea internamente a short yEnum8se mapea a byte. Debe evitarse leerEnum16como byte debido al riesgo de corrupción de datos.- Los valores de
enumpueden establecerse como una cadena o un valor numérico enPreparedStatement.
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.sqlpara lograr una mejor compatibilidad con JDBC. Sin embargo, es posible obtenerjava.time.LocalDate,java.time.LocalDateTimeyjava.time.LocalTimemedianteResultSet#getObject(columnIndex, Class<T>), usando la clase correspondiente como segundo argumento.rs.getObject(1, java.time.LocalDate.class)devolverá un valorjava.time.LocalDatede la columnaDate.rs.getObject(1, java.time.LocalDateTime.class)devolverá un valorjava.time.LocalDateTimede la columnaDateTime.rs.getObject(1, java.time.LocalTime.class)devolverá un valorjava.time.LocalTimede la columnaTime.
Date,Date32,Time,Time64no se ven afectados por la zona horaria del servidor.DateTimeyDateTime64se ven afectados por la zona horaria del servidor o de la sesión.DateTimeyDateTime64se pueden recuperar comoZonedDateTimemediantegetObject(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 |
Arrayse asigna ajava.sql.Arrayde 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.Arrayimplementa el métodogetResultSet()para devolver unjava.sql.ResultSetcon 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). Mapse asigna aOTHERporque el valor solo puede leerse mediante el métodogetObject(columnIndex, Class<T>).Mapno es unjava.sql.Structporque no tiene columnas con nombres.
Tuplese mapea aObject[]porque puede contener distintos tipos y no es válido usarList.Tuplepuede leerse comoArraymediante el métodogetObject(columnIndex, Array.class). En este caso,Array#baseTypeNamedevolverá la definición de la columnaTuple.
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 engetBaseTypeName(), perogetBaseType()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 deEnum/Tuple) conservan sus parámetros engetBaseTypeName().
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 deEnumyTuple). V1 los elimina y devuelve solo el nombre del tipo base. - Los arrays de
TupleusanOTHER (1111)en V2 en lugar deSTRUCT (2002)porque las tuplas de ClickHouse tienen campos con nombre, algo quejava.sql.Structno admite. - Los arrays de
UUIDusanOTHER (1111)en V2, en consonancia con la asignación escalar deUUID. - Los valores
Enumse asignan aVARCHAR: los miembros deenumse 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
NullableyLowCardinalityson tipos especiales que envuelven otros tipos.Nullableafecta a la forma en que se devuelven los nombres de tipo enResultSetMetaData
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) |
UUIDno es un tipo estándar de JDBC. Sin embargo, forma parte del JDK. De forma predeterminada, el métodogetObject()devuelvejava.util.UUID.UUIDpuede leerse y escribirse comoStringmediante el métodogetObject(columnIndex, String.class).IPv4eIPv6no son tipos estándar de JDBC. Sin embargo, forman parte del JDK. De forma predeterminada, el métodogetObject()devuelvejava.net.Inet4Addressyjava.net.Inet6Address.IPv4yIPv6se pueden leer y escribir comoStringmediante el métodogetObject(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 connectionSuministro 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: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.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:
- Ajuste los siguientes parámetros del kernel de Linux en
/etc/sysctl.confo en un archivo de configuración relacionado:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1net.ipv4.tcp_keepalive_intvl: 75net.ipv4.tcp_keepalive_probes: 9net.ipv4.tcp_keepalive_time: 60 (Puede considerar reducir este valor frente al valor predeterminado de 300 segundos)
- Después de modificar los parámetros del kernel, aplique los cambios ejecutando el siguiente comando:
sudo sysctl -pTras 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 |
FixedStringse 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 enunhex('<hex_string>')y luego se interpreta comoString. - 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 |
TimeyTime64solo son compatibles en V2 como tipos nuevos.DateTimeyDateTime64se asignan ajava.sql.Timestamppara 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,
Arrayse asigna ajava.sql.Arrayde 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,
Arrayimplementa el métodogetResultSet()para devolver unjava.sql.ResultSetcon el mismo contenido que el array original. - V1 usa
STRUCTparaMap, pero siempre devuelve un objetojava.util.Map. V2 corrige esto al mapearMapaJAVA_OBJECT. - V1 usa
STRUCTparaTuple, pero siempre devuelve un objetoList<Object>. V2 asignaTupleaOTHERy devuelveObject[]de forma predeterminada. - V2 introduce
com.clickhouse.data.Tuple#Tuplepara escribir tuplas. Simplifica la tarea de determinar si un valor es una tupla o un Array. PreparedStatement#setBytesyResultSet#getBytesno pueden usarse con tipos de colección. Estos métodos están diseñados para trabajar con cadenas binarias.- Normalmente,
java.sql.Arrayse utiliza para escribir y leer tipos Array. El controlador JDBC ofrece compatibilidad total con ello. - V2 asigna
NestedaArrayy lo presenta como un array de tuplas. - V2 ofrece compatibilidad parcial con
java.sql.Structporque es muy similar al tipoArrayy no admite pares clave-valor.Structpuede utilizarse para escribir valoresTuple.
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
NullableyLowCardinalityson 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
VARCHARparaUUID, pero siempre devuelve un objetojava.util.UUID. V2 corrige esto al mapearUUIDaOTHERy devuelve un objetojava.util.UUID. - V1 usa
VARCHARparaIPv4eIPv6, pero siempre devuelve objetosjava.net.Inet4Addressyjava.net.Inet6Address. V2 corrige esto asignandoIPv4eIPv6aOTHERy devuelve objetosjava.net.Inet4Addressyjava.net.Inet6Address. DynamicyVariantson tipos nuevos en V2. No se admiten en V1.JSONse basa en el tipoDynamic. Por ello, solo se admite en V2.- Los valores IPv4 e IPv6 pueden leerse como
byte[]mediante el métodogetBytes(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
Schemapara referirse a las bases de datos. El términoCatalogqueda reservado para usos futuros. - V2 devuelve
falseparaDatabaseMetaData.supportsTransactions()yDatabaseMetaData.supportsSavepoints(). Esto cambiará en el futuro. - En
DatabaseMetaData.getTypeInfo(), las columnasLITERAL_PREFIXyLITERAL_SUFFIXahora devuelvennullpara 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
- OpenJDK versión >= 8
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>// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
// usa el uber JAR con todas las dependencias incluidas; cambia el classifier a http para obtener un JAR más pequeño
implementation("com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all")// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
// usa el uber JAR con todas las dependencias incluidas; cambia el classifier a http para obtener un JAR más pequeño
implementation 'com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all'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>// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
implementation("org.slf4j:slf4j-api:2.0.16")// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
implementation 'org.slf4j:slf4j-api:2.0.16'Configuración
Clase del driver: com.clickhouse.jdbc.ClickHouseDriver
Sintaxis de URL: jdbc:(ch|clickhouse)[:<protocol>]://endpoint1[,endpoint2,...][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...], por ejemplo:
jdbc:ch://localhostes igual quejdbc:clickhouse:http://localhost:8123jdbc:ch:https://localhostes lo mismo quejdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICTjdbc:ch:grpc://localhostes lo mismo quejdbc: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
}- la documentación de la función input siempre que sea posible
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: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.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:
- Ajuste los siguientes parámetros del kernel de Linux en
/etc/sysctl.confo en un archivo de configuración relacionado:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1net.ipv4.tcp_keepalive_intvl: 75net.ipv4.tcp_keepalive_probes: 9net.ipv4.tcp_keepalive_time: 60 (Puede considerar reducir este valor respecto del valor predeterminado de 300 segundos)
- Después de modificar los parámetros del kernel, aplique los cambios ejecutando el siguiente comando:
sudo sysctl -pTras 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();
}