Prérequis environnementaux
- OpenJDK version >= 8
Configuration
{/* 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 vous utilisez le pilote JDBC dans une application nécessitant l'ajout d'un jar au classpath, vous devez télécharger le jar depuis :
- Maven Central et ajoutez-le au classpath
- à partir de la version
0.9.4, un artefact est disponible : https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc-all - utilisez le classificateur
allpour obtenir le JAR avec toutes les dépendances embarquées.
- à partir de la version
- ou depuis le dépôt officiel ici
Configuration
Classe du driver: com.clickhouse.jdbc.ClickHouseDriver
Syntaxe d'URL : jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...], par exemple :
jdbc:clickhouse:http://localhost:8123jdbc:clickhouse:https://localhost:8443?ssl=true
Voici quelques points à noter concernant la syntaxe des URL :
- un seul endpoint est autorisé dans l’URL
- le protocole doit être spécifié lorsqu’il ne s’agit pas du protocole par défaut - 'HTTP'
- le port doit être spécifié lorsqu’il ne s’agit pas du port par défaut '8123'
- le driver ne déduit pas le protocole à partir du port, vous devez le spécifier explicitement
- le paramètre
ssln’est pas requis lorsque le protocole est spécifié.
Propriétés de connexion
Les principaux paramètres de configuration sont définis dans le client Java. Ils doivent être transmis tels quels au driver. Le driver possède également des propriétés qui lui sont propres et qui ne font pas partie de la configuration du client ; elles sont listées ci-dessous.
Propriétés du driver :
| Propriété | Par défaut | Description |
|---|---|---|
disable_frameworks_detection |
true |
Désactive la détection des frameworks pour le User-Agent |
jdbc_ignore_unsupported_values |
false |
Ignore SQLFeatureNotSupportedException lorsque cela n’affecte pas le fonctionnement du driver |
clickhouse.jdbc.v1 |
false |
Utilise l’ancienne implémentation JDBC plutôt que la nouvelle |
default_query_settings |
null |
Permet de transmettre les paramètres de requête par défaut lors des opérations de requête |
jdbc_resultset_auto_close |
true |
Ferme automatiquement ResultSet à la fermeture de Statement |
beta.row_binary_for_simple_insert |
false |
Utilise une implémentation de PreparedStatement basée sur l’écriture RowBinary. Fonctionne uniquement pour les requêtes INSERT INTO ... VALUES. |
jdbc_resultset_auto_close |
true |
Ferme automatiquement ResultSet à la fermeture de Statement |
jdbc_use_max_result_rows |
false |
Permet d’utiliser la propriété serveur max_result_rows pour limiter le nombre de lignes renvoyées par la requête. Lorsqu’elle est activée, cette option remplace le mode de dépassement défini par l’utilisateur. Consultez la JavaDoc pour plus de détails. |
jdbc_sql_parser |
JAVACC |
Définit le parseur SQL à utiliser. Choix : ANTLR4, ANTLR4_PARAMS_PARSER, JAVACC. |
remember_last_set_roles |
true |
Mémorise les derniers rôles définis pour la connexion. |
Exemple de configuration :
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);ce qui sera équivalent à l'URL JDBC suivante :
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.Note : inutile d'encoder l'URL JDBC ou les propriétés en URL, elles seront encodées automatiquement.
Profils en lecture seule
Nous évitons délibérément d'ajouter des paramètres par défaut aux propriétés de connexion afin d'éviter tout problème avec les profils en lecture seule.
Cependant, certains utilisateurs ont besoin de transmettre des paramètres de format (par exemple pour lire du JSON en tant que String) et nous recommandons d'utiliser le profil readonly=2.
Pour en savoir plus sur les profils en lecture seule, consultez cette page.
Identification du client
Il existe deux façons d'identifier l'application à l'origine d'une requête : définir com.clickhouse.client.api.ClientConfigProperties#CLIENT_NAME via
les propriétés de connexion ou utiliser la méthode 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");Les deux méthodes produiront la valeur http_user_agent suivante dans le journal des requêtes :
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.4Note : Nous recommandons d'utiliser le format app_name/version pour la propriété client_name, car cela facilite l'identification de l'application dans le journal des requêtes.
Identification des opérations
Le pilote JDBC génère un query_id pour chaque opération (actuellement inclus dans les exceptions du serveur).
Pour définir log_comment pour une opération, utilisez la méthode com.clickhouse.jdbc.StatementImpl#getLocalSettings. Cela nécessite
que Statement ou PreparedStatement soit préalablement casté en com.clickhouse.jdbc.StatementImpl.
StatementImpl stmt = (StatementImpl) conn.createStatement();
stmt.getLocalSettings().logComment("some-comment");Note : cette approche fonctionne pour les utilisations mono-thread d'une instruction, car localSettings est partagé entre les threads.
Types de données pris en charge
Le JDBC driver prend en charge les mêmes formats de données que le client Java sous-jacent.
Correspondance de types JDBC
La correspondance suivante s'applique à :
ResultSet#getObject(columnIndex)- la méthode retourne un objet de la classe Java correspondante. (Int8->java.lang.Byte,Int16->java.lang.Short, etc.)ResultSetMetaData#getColumnType(columnIndex)- la méthode retourne le type JDBC correspondant. (Int8->java.lang.Byte,Int16->java.lang.Short, etc.)
Il existe plusieurs façons de modifier la correspondance :
ResultSet#getObject(columnIndex, class)- la méthode essaiera de convertir la valeur en typeclass. Certaines conversions présentent des limitations. Voir chaque section pour plus de détails.
Types numériques
| Type ClickHouse | Type JDBC | Classe 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 |
- les types numériques sont convertibles entre eux. Ainsi,
Int8peut être lu commeFloat64, et inversement.:rs.getObject(1, Float64.class)renverra une valeurFloat64pour la colonneInt8.rs.getLong(1)renverra une valeurLongpour la colonneInt8.rs.getByte(1)peut renvoyer une valeurBytepour la colonneInt16si celle-ci tient dansByte.
- la conversion d'un type plus large vers un type plus étroit n'est pas recommandée en raison du risque de corruption des données.
- Le type
Boolse comporte aussi comme un nombre. - Tous les types numériques peuvent également être lus comme
java.lang.String. - Le stockage de
Float.MAX_VALUEde Java en tant queFloatpose problème (https://github.com/ClickHouse/clickhouse-java/issues/809). Enregistrer la même valeur en tant queDoublerésout le problème.
Types String
| Type ClickHouse | Type JDBC | Classe Java |
|---|---|---|
| String | VARCHAR | java.lang.String |
| FixedString | VARCHAR | java.lang.String |
Stringne peut être lu que sous la forme dejava.lang.Stringou debyte[].FixedStringest lu tel quel et sera complété par des zéros jusqu'à atteindre la longueur de la colonne. (Par exemple,FixedString(10)pour'John'sera lu comme'John\0\0\0\0\0\0\0\0\0'.)
Types enum
| Type ClickHouse | Type JDBC | Classe Java |
|---|---|---|
| Enum8 | VARCHAR | java.lang.String |
| Enum16 | VARCHAR | java.lang.String |
Enum8etEnum16sont associés àjava.lang.Stringpar défaut.- Les valeurs d'énumération peuvent être lécues comme valeurs numériques à l'aide de la méthode d'accès dédiée ou de la méthode
getObject(columnIndex, Integer.class). Enum16est associé en interne à short etEnum8à byte. Il faut éviter de lireEnum16comme un byte en raison du risque de corruption des données.- Les valeurs d'énumération peuvent être définies comme chaîne de caractères ou comme valeur numérique dans
PreparedStatement.
Types Date/Heure
| Type ClickHouse | Type JDBC | Classe 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 |
- Les types Date / Time sont associés aux types
java.sqlpour une meilleure compatibilité avec JDBC. Cependant, il est possible d'obtenir des objetsjava.time.LocalDate,java.time.LocalDateTimeoujava.time.LocalTimeen utilisantResultSet#getObject(columnIndex, Class<T>)avec la classe correspondante comme deuxième argument.rs.getObject(1, java.time.LocalDate.class)renvoie la valeurjava.time.LocalDatede la colonneDate.rs.getObject(1, java.time.LocalDateTime.class)renvoie la valeurjava.time.LocalDateTimede la colonneDateTime.rs.getObject(1, java.time.LocalTime.class)renvoie la valeurjava.time.LocalTimede la colonneTime.
Date,Date32,Time,Time64ne sont pas affectés par le fuseau horaire du serveur.DateTime,DateTime64sont affectés par le fuseau horaire du serveur ou le fuseau horaire de la session.DateTimeetDateTime64peuvent être récupérés sous forme deZonedDateTimeà l’aide degetObject(colIndex, ZonedDateTime.class).
Types imbriqués
| Type ClickHouse | Type JDBC | Classe Java |
|---|---|---|
| Array | ARRAY | java.sql.Array |
| Tuple | OTHER | com.clickhouse.data.Tuple |
| Map | OTHER | java.util.Map |
| Nested | ARRAY | java.sql.Array |
Arrayest mappé àjava.sql.Arraypar défaut afin d’être compatible avec JDBC. Cela permet également de fournir plus d’informations sur la valeur de tableau renvoyée. Utile pour l’inférence de type.Arrayimplémente la méthodegetResultSet()pour renvoyer unjava.sql.ResultSetcontenant les mêmes données que le tableau d’origine.- Les types de collection ne doivent pas être lus comme des
java.lang.String, car ce n’est pas une manière valide de représenter les données (par ex., les valeurs de chaîne ne sont pas entre guillemets dans un tableau). Mapest associé àOTHER, car la valeur ne peut être lue qu’avec la méthodegetObject(columnIndex, Class<T>).Mapn'est pas unjava.sql.Struct, car il ne comporte pas de colonnes nommées.
Tupleest associé àObject[], car il peut contenir différents types et qu’utiliserListn’est pas possible.Tuplepeut être lu comme unArrayà l’aide de la méthodegetObject(columnIndex, Array.class). Dans ce cas,Array#baseTypeNamerenverra la définition de la colonneTuple.
Métadonnées du type d'élément de tableau
Array.getBaseTypeName() renvoie le nom du type d'élément ClickHouse ; Array.getBaseType() renvoie le code de type JDBC.
JDBC V2 préserve les signatures de type complètes (types wrapper, paramètres de type) que V1 supprime.
Les règles générales de correspondance pour les arrays sont :
| Type ClickHouse | getBaseTypeName() |
getBaseType() |
|---|---|---|
Array(<Type primitif>) |
<Type primitif> |
<Type primitif JDBC> |
Array(<Parameterized Type>(<N>)) |
<Type paramétré>(<N>) |
<Type JDBC du type de base> |
Array(Nullable(<Type>)) |
Nullable(<Type>) |
<type JDBC du Type interne> |
Array(LowCardinality(<Type>)) |
LowCardinality(<Type>) |
<type JDBC du Type interne> |
Array(Array(...(<Type>))) |
<Type> (élément le plus imbriqué) |
<type JDBC du type le plus imbriqué> |
Array(Tuple(...)) |
Tuple(...) (définition complète) |
OTHER |
Array(Enum8(...)) / Array(Enum16(...)) |
Enum8(...) / Enum16(...) (définition complète) |
VARCHAR |
Remarques sur les règles ci-dessus :
- Les types enveloppes (
Nullable,LowCardinality) sont préservés dansgetBaseTypeName(), maisgetBaseType()correspond au code JDBC du type interne. - Les tableaux imbriqués sont aplatis dans les métadonnées :
getBaseTypeName()renvoie le type de l’élément le plus interne qui n’est pas un tableau, et non l’élément enfant immédiat. - Les types paramétrés (
FixedString(N), définitions complètes deEnum/Tuple) conservent leurs paramètres dansgetBaseTypeName().
Exemples :
| Type ClickHouse (Exemple) | 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 |
- Dans V2,
getBaseTypeName()conserve la signature de type complète, y compris les types wrapper (Nullable,LowCardinality) et les paramètres de type (FixedString(8), définitions complètes deEnumetTuple). V1 les supprime et renvoie uniquement le nom du type de base. - Les tableaux
TupleutilisentOTHER (1111)dans V2 au lieu deSTRUCT (2002), car les tuples ClickHouse ont des champs nommés, ce quejava.sql.Structne prend pas en charge. - Les tableaux
UUIDutilisentOTHER (1111)dans V2, comme pour le mappage duUUIDscalaire. - Les valeurs
Enumsont mappées versVARCHAR— les membres d’enum sont identifiés par leur nom sous forme de chaîne, indépendamment de leur encodage numérique sous-jacent.
Écriture des Arrays
Utilisez java.sql.Connection#createArrayOf pour instancier un objet java.sql.Array. Cet objet est conçu pour uniformiser la gestion des tableaux entre différentes bases de données.
Une connexion est nécessaire pour transmettre la configuration à la méthode factory d'Array.
La méthode accepte deux arguments :
typeName- nom du type des éléments du tableau. Par exempleArray(Int32)->"Int32".elements- éléments effectifs du tableau. Par exemple[[1, 2, 3], [4, 5, 6]]->new Integer[][] {{1, 2, 3}, {4, 5, 6}}.
Tuple peut être représenté sous la forme Object[] ou java.sql.Struct (voir comment écrire des tuples ci-dessous).
Exemple
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();
}
}Lecture des Arrays
Utilisez ResultSet#getArray(columnIndex) pour lire un objet Array. Cet objet permet d'accéder à un tableau quelle que soit sa profondeur d'imbrication.
La méthode Array#getResultSet() peut être utilisée pour lire les éléments du tableau de façon plus uniforme sous forme de java.sql.ResultSet. Elle est utile
lorsque le type exact des éléments du tableau est inconnu.
Exemple
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()) {
// ...
}
}
}
}
}Écriture de Tuples
Les Tuples sont mappés à l'objet com.clickhouse.data.Tuple et doivent être écrits sous cette forme en appelant la méthode setObject(columnIndex, tuple).
Il est possible d'utiliser l'objet java.sql.Struct pour écrire des tuples et améliorer la portabilité.
Exemple
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();
}
}Lecture de tuples
La méthode getObject(columnIndex) renvoie Object[]. Les Tuples peuvent être lus en tant que java.sql.Array via la méthode getObject(columnIndex, Array.class).
Exemple
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());
}
}
}Écriture de Maps
Map ne peut être écrit qu'en tant qu'objet java.collections.Map, car ce type nécessite des paires clé-valeur (java.sql.Struct ne prend pas en charge les paires clé-valeur).
Exemple
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();
}
}Lecture des cartes
Map peut être lu en tant qu'objet java.collections.Map via la méthode getObject(columnIndex, Map.class).
Exemple
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);
// ...
}
}
}
}Écriture dans Nested
Utilisez java.sql.Connection#createStruct pour instancier un objet java.sql.Struct. Cet objet est conçu pour unifier la gestion des types imbriqués entre différentes bases de données.
Une connexion est nécessaire pour transmettre la configuration à la méthode factory de Struct.
La méthode accepte deux arguments :
typeName- nom du type des éléments imbriqués. Par exempleNested(Tuple(Int32, String))->"Nested(Tuple(Int32, String))".elements- éléments imbriqués proprement dits. Par exemple[1, 'test']->new Object[] {1, 'test'}.
Exemple
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();
}
}Lecture de Nested
Utilisez ResultSet#getStruct(columnIndex, StructDescriptor) pour lire un objet Nested. Cet objet permet d'accéder à des éléments imbriqués quelle que soit leur profondeur d'imbrication.
La méthode Struct#getResultSet() permet de lire les éléments imbriqués de façon plus uniforme sous forme de java.sql.ResultSet. Elle est utile
lorsque le type exact des éléments imbriqués est inconnu.
Exemple
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
| Type ClickHouse | Type JDBC | Classe Java |
|---|---|---|
| Point | OTHER | double[] |
| Ring | OTHER | double[][] |
| Polygon | OTHER | double[][][] |
| MultiPolygon | OTHER | double[][][][] |
Types Nullable et LowCardinality
NullableetLowCardinalitysont des types spéciaux qui encapsulent d'autres types.Nullableinflue sur la manière dont les noms de type sont renvoyés dansResultSetMetaData
Types spéciaux
| Type ClickHouse | Type JDBC | Classe Java |
|---|---|---|
| UUID | OTHER | java.util.UUID |
| IPv4 | OTHER | java.net.Inet4Address |
| IPv6 | OTHER | java.net.Inet6Address |
| JSON | OTHER | java.lang.String |
| AggregateFunction | OTHER | (représentation binaire) |
| SimpleAggregateFunction | (type encapsulé) | (classe encapsulée) |
UUIDn'est pas un type JDBC standard. Il fait toutefois partie du JDK. Par défaut, la méthodegetObject()renvoie unjava.util.UUID.UUIDpeut être lu/écrit sous forme deStringà l'aide de la méthodegetObject(columnIndex, String.class).IPv4etIPv6ne sont pas des types JDBC standard. Ils font toutefois partie du JDK. Par défaut, la méthodegetObject()renvoie unjava.net.Inet4Addresset unjava.net.Inet6Address.IPv4etIPv6peuvent être lus/écrits sous forme deStringà l'aide de la méthodegetObject(columnIndex, String.class).
Type JSON
Le type JSON est mappé sur Map<String, Object> par défaut, où les clés sont les clés de l'objet JSON et les valeurs sont les valeurs de l'objet JSON.
Par exemple :
{
"key1": "value1",
"key2": ["value2", "value3"]
"key3": {
"key4": "value4",
"key5": "value5"
}
}sera associé à :
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");
}});Il existe une option plus pratique pour lire le JSON en tant que String en passant le server setting jdbc_read_json_as_string=true aux properties de connexion.
Cela permet au driver de retourner les values JSON en tant que String, qui peuvent ensuite être parsées à l'aide de n'importe quelle bibliothèque 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");
// ...
}
}
}À partir de la version 25.8 de ClickHouse, les nombres ne sont plus mis entre guillemets par défaut. Pour les versions antérieures, vous pouvez désactiver cette mise entre guillemets en transmettant des paramètres serveur aux propriétés de connexion :
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");Gestion des dates, des heures et des fuseaux horaires
Veuillez consulter le Guide Date/Time, qui explique les pièges courants et la logique du driver lors de la gestion des valeurs Date/Time et des timestamps.
Création d'une connexion
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 connectionFournir les identifiants et les paramètres
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
}Instruction 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
}
}Pour aller plus loin
Pour plus d'informations, consultez notre dépôt GitHub et la documentation du client Java.
Résolution des problèmes
Journalisation
Le driver utilise slf4j pour la journalisation et utilisera la première implémentation disponible sur le classpath.
Résolution du timeout JDBC sur les insertions volumineuses
Lors de l'exécution de grands inserts dans ClickHouse avec de longs temps d'exécution, vous pouvez rencontrer des erreurs de timeout JDBC du type :
Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]Ces erreurs peuvent perturber le processus d'insertion de données et affecter la stabilité du système. Pour résoudre ce problème, vous devrez peut-être ajuster quelques paramètres de timeout dans le système d'exploitation du client.
Mac OS
Sur Mac OS, les paramètres suivants peuvent être modifiés pour résoudre le problème :
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1
Linux
Sur Linux, les paramètres équivalents seuls peuvent ne pas suffire à résoudre le problème. Des étapes supplémentaires sont nécessaires en raison des différences dans la manière dont Linux gère les paramètres keep-alive des sockets. Suivez ces étapes :
- Ajustez les paramètres suivants du noyau Linux dans
/etc/sysctl.confou dans un fichier de configuration connexe :
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 (Vous pouvez envisager d’abaisser cette valeur par rapport à la valeurdefaultde 300 secondes)
- Après avoir modifié les paramètres du noyau, appliquez les modifications en exécutant la commande suivante :
sudo sysctl -pAprès avoir configuré ces paramètres, assurez-vous que votre client active l'option Keep Alive sur le socket :
properties.setProperty("socket_keepalive", "true");Guide de migration
Modifications principales
| Fonctionnalité | V1 (ancienne) | V2 (nouvelle) |
|---|---|---|
| Prise en charge des transactions | Prise en charge partielle | Non pris en charge |
| Renommage des colonnes de réponse | Prise en charge partielle | Non pris en charge |
| SQL à instructions multiples | Non pris en charge | Non autorisé |
| Paramètres nommés | Pris en charge | Non pris en charge (non prévu par la spécification JDBC) |
Données en streaming avec PreparedStatement |
Pris en charge | Non pris en charge |
- JDBC V2 a été conçu pour être plus léger, et certaines fonctionnalités ont été supprimées.
- Les données en streaming ne sont pas prises en charge dans JDBC V2, car elles ne font partie ni de la spécification JDBC ni de Java.
- JDBC V2 nécessite une configuration explicite. Aucun basculement n’est défini par défaut.
- Le protocole doit être indiqué dans l’URL. Aucune détection implicite du protocole en fonction du numéro de port.
Modifications de configuration
Il n'existe que deux enums :
com.clickhouse.jdbc.DriverProperties- les propriétés de configuration propres au driver.com.clickhouse.client.api.ClientConfigProperties- les propriétés de configuration du client. Les changements de configuration du client sont décrits dans la documentation du client Java.
Les propriétés de connexion sont analysées de la manière suivante :
- L’URL est d’abord analysée pour les propriétés. Celles-ci remplacent toutes les autres propriétés.
- Les propriétés du driver ne sont pas transmises au client.
- Les endpoints (hôte, port, protocole) sont extraits de l’URL.
Exemple :
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
}Modifications des types de données
Types numériques
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe 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 principale différence est que les types non signés sont associés à des types Java pour une meilleure portabilité.
Types de chaînes
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe Java (V1) |
|---|---|---|---|---|---|
| String | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String |
| FixedString | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String |
FixedStringest lu tel quel dans les deux versions. Par exemple,FixedString(10)pour'John'sera lu sous la forme'John\0\0\0\0\0\0\0\0\0'.- Lorsque
PreparedStatement#setBytesest utilisé, il est converti enunhex('<hex_string>'), puis lu en tant queString. - Les chaînes sont stockées en encodage UTF-8.
Types Date/Heure
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe Java (V1) |
|---|---|---|---|---|---|
| Date | ❌ | 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 | nouveau type/non pris en charge | nouveau type/non pris en charge |
| Time64 | ✅ | TIME | java.sql.Time | nouveau type / non pris en charge | nouveau type / non pris en charge |
TimeetTime64sont pris en charge dans V2 uniquement en tant que nouveaux types.DateTimeetDateTime64sont associés àjava.sql.Timestamppour une meilleure compatibilité avec JDBC.
Types enum
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe 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 |
Types imbriqués
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe Java (V1) |
|---|---|---|---|---|---|
| Array | ❌ | ARRAY | java.sql.Array | ARRAY | Object[] ou tableau de types primitifs |
| 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,
Arrayest associé àjava.sql.Arraypar défaut pour assurer la compatibilité avec JDBC. Cela permet aussi de fournir davantage d’informations sur la valeur de tableau renvoyée. Utile pour l’inférence de type. - En V2,
Arrayimplémente la méthodegetResultSet()afin de renvoyer unjava.sql.ResultSetavec le même contenu que le tableau d’origine. - V1 utilise
STRUCTpourMap, mais renvoie toujours un objetjava.util.Map. V2 corrige cela en associantMapàJAVA_OBJECT. - V1 utilise
STRUCTpourTuple, mais renvoie toujours un objetList<Object>. V2 associeTupleàOTHERet renvoieObject[]par défaut. - V2 introduit
com.clickhouse.data.Tuple#Tuplepour écrire des tuples. Cela permet de déterminer plus facilement si une valeur est un tuple ou un tableau. PreparedStatement#setBytesetResultSet#getBytesne peuvent pas être utilisés avec des types de collection. Ces méthodes sont conçues pour fonctionner avec des chaînes binaires.- En général,
java.sql.Arrayest utilisé pour écrire et lire les typesArray. Le pilote JDBC le prend entièrement en charge. - En V2,
Nestedest associé àArrayet est présenté comme un tableau de tuples. - V2 prend partiellement en charge
java.sql.Struct, car il est très similaire au typeArrayet ne prend pas en charge les paires clé-valeur.Structpeut être utilisé pour écrire des valeursTuple.
Geo Types
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe Java (V1) |
|---|---|---|---|---|---|
| Point | ✅ | OTHER | double[] | OTHER | double[] |
| Ring | ✅ | OTHER | double[][] | OTHER | double[][] |
| Polygon | ✅ | OTHER | double[][][] | OTHER | double[][][] |
| MultiPolygon | ✅ | OTHER | double[][][][] | OTHER | double[][][][] |
Types Nullable et LowCardinality
NullableetLowCardinalitysont des types spéciaux qui encapsulent d’autres types.- Aucun changement n’est apporté à ces types dans V2.
Types spéciaux
| Type ClickHouse | Compatible avec la V1 | Type JDBC (V2) | Classe Java (V2) | Type JDBC (V1) | Classe Java (V1) |
|---|---|---|---|---|---|
| JSON | ❌ | OTHER | java.lang.String | non pris en charge | non pris en charge |
| AggregateFunction | ✅ | OTHER | (représentation binaire) | OTHER | (représentation binaire) |
| SimpleAggregateFunction | ✅ | (type encapsulé) | (classe encapsulée) | (type encapsulé) | (classe encapsulée) |
| 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 | non pris en charge | non pris en charge |
| Variant | ❌ | OTHER | java.Object | non pris en charge | non pris en charge |
- V1 utilise
VARCHARpourUUID, mais renvoie toujours un objetjava.util.UUID. V2 corrige cela en associantUUIDàOTHERet renvoie un objetjava.util.UUID. - V1 utilise
VARCHARpourIPv4etIPv6, mais renvoie toujours des objetsjava.net.Inet4Addressetjava.net.Inet6Address. V2 corrige cela en associantIPv4etIPv6àOTHERet renvoie des objetsjava.net.Inet4Addressetjava.net.Inet6Address. DynamicetVariantsont de nouveaux types dans V2. Ils ne sont pas pris en charge dans V1.JSONest basé sur le typeDynamic. Il n'est donc pris en charge que dans V2.- Les valeurs IPv4 et IPv6 peuvent être lues sous forme de
byte[]à l'aide de la méthodegetBytes(columnIndex). Il est toutefois recommandé d'utiliser les classes prévues pour ces types. - V2 ne prend pas en charge la lecture d'une adresse IP sous forme de valeurs numériques, car la conversion est mieux gérée dans les classes InetAddress.
Modifications des métadonnées de la base de données
- V2 utilise uniquement le terme
Schemapour désigner les bases de données. Le termeCatalogest réservé à un usage futur. - V2 renvoie
falsepourDatabaseMetaData.supportsTransactions()etDatabaseMetaData.supportsSavepoints(). Cela sera modifié dans une future version. - Dans
DatabaseMetaData.getTypeInfo(), les colonnesLITERAL_PREFIXetLITERAL_SUFFIXrenvoient désormaisnullpour les types de données auxquels des préfixes et suffixes ne s'appliquent pas (par ex., les types numériques). Dans V1, ces colonnes renvoyaient des valeurs non nulles pour ces types. Ces colonnes doivent être utilisées lors de la génération de requêtes SQL afin de mettre correctement entre guillemets les valeurs littérales en fonction de leur type de données.
clickhouse-jdbc implémente l'interface JDBC standard. Construit au-dessus de clickhouse-client, il offre des fonctionnalités supplémentaires telles que le type mapping personnalisé, la prise en charge des transactions et les instructions synchrones standard UPDATE et DELETE, ce qui lui permet d'être facilement utilisé avec des applications et des outils legacy.
L'API clickhouse-jdbc est synchrone et présente généralement davantage de surcharge (par exemple, le parsing SQL et le type mapping/conversion, etc.). Privilégiez clickhouse-client lorsque les performances sont critiques ou si vous préférez un accès plus direct à ClickHouse.
Prérequis environnementaux
- OpenJDK version >= 8
Configuration
{/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */}
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.7.2</version>
{/* utilisez l’uber JAR avec toutes les dépendances incluses ; remplacez classifier par http pour obtenir un JAR plus léger */}
<classifier>shaded-all</classifier>
</dependency>// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
// utilisez l’uber JAR avec toutes les dépendances incluses ; remplacez classifier par http pour obtenir un JAR plus léger
implementation("com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all")// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
// utilisez l’uber JAR avec toutes les dépendances incluses ; remplacez classifier par http pour obtenir un JAR plus léger
implementation 'com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all'Depuis la version 0.5.0, nous utilisons Apache HTTP Client, qui embarque le Client sous forme compacte. Comme il n'existe pas de version partagée du paquet, vous devez ajouter un logger comme dépendance.
{/* 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'Configuration
Classe du driver: com.clickhouse.jdbc.ClickHouseDriver
Syntaxe d'URL : jdbc:(ch|clickhouse)[:<protocol>]://endpoint1[,endpoint2,...][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...], par exemple :
jdbc:ch://localhostéquivaut àjdbc:clickhouse:http://localhost:8123jdbc:ch:https://localhostéquivaut àjdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICTjdbc:ch:grpc://localhostéquivaut àjdbc:clickhouse:grpc://localhost:9100
Propriétés de connexion :
| Propriété | Par défaut | Description |
|---|---|---|
continueBatchOnError |
false |
Indique s’il faut poursuivre le traitement par lot en cas d’erreur |
createDatabaseIfNotExist |
false |
Indique s’il faut créer la base de données si elle n'existe pas |
custom_http_headers |
en-têtes HTTP personnalisés séparés par des virgules, par exemple : User-Agent=client1,X-Gateway-Id=123 |
|
custom_http_params |
paramètres de requête http personnalisés, séparés par des virgules, par exemple : extremes=0,max_result_rows=100 |
|
nullAsDefault |
0 |
0 - conserver la valeur null telle quelle et lever une exception lors de l’insertion d’une valeur null dans une colonne non nullable ; 1 - conserver la valeur null telle quelle et désactiver la vérification des valeurs null à l’insertion ; 2 - remplacer null par la valeur par défaut du type de données correspondant, à la fois pour la requête et l’insertion |
jdbcCompliance |
true |
Indique s’il faut prendre en charge les opérations UPDATE/DELETE synchrones standard ainsi qu’une pseudo-transaction |
typeMappings |
Personnalisez la correspondance entre le type de données ClickHouse et la classe Java, ce qui affectera le résultat de getColumnType() comme de getObject(Class<>?>). Par exemple : UInt128=java.lang.String,UInt256=java.lang.String |
|
wrapperObject |
false |
Indique si getObject() doit renvoyer un(e) java.sql.Array / java.sql.Struct pour les types Array / Tuple. |
Remarque : veuillez consulter la configuration spécifique à JDBC pour plus d'informations.
Types de données pris en charge
Le pilote JDBC prend en charge les mêmes formats de données que la bibliothèque client.
Création de la connexion
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()) {
}Instruction simple
try (Connection conn = dataSource.getConnection(...);
Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
while(rs.next()) {
// ...
}
}Insert
C'est plus facile à utiliser, mais les performances sont moins bonnes que celles de la fonction input (voir ci-dessous) :
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
}Avec la fonction de table input
Une option offrant d'excellentes caractéristiques de performance :
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
}- documentation sur la fonction input si possible
Insert avec espaces réservés
Cette option est recommandée uniquement pour les insertions de petite taille, car elle nécessiterait une longue expression SQL (qui sera analysée côté client et consommera du CPU & de la mémoire) :
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(...)(...)...(...)
}Gestion de DateTime et des fuseaux horaires
Veuillez utiliser java.time.LocalDateTime ou java.time.OffsetDateTime plutôt que java.sql.Timestamp, et java.time.LocalDate plutôt que 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);
}
...
}Gestion 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());
}Configuration de la bibliothèque HTTP
Le connecteur JDBC ClickHouse prend en charge trois bibliothèques HTTP : HttpClient, HttpURLConnection et Apache HttpClient.
Le pilote JDBC utilise HttpClient par défaut. Vous pouvez modifier la bibliothèque HTTP utilisée par le connecteur JDBC ClickHouse en configurant la propriété suivante :
properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");Voici la liste complète des entrées de configuration correspondantes :
| Valeur de propriété | Bibliothèque HTTP |
|---|---|
| HTTP_CLIENT | HttpClient |
| HTTP_URL_CONNECTION | HttpURLConnection |
| APACHE_HTTP_CLIENT | Apache HttpClient |
Se connecter à ClickHouse avec SSL
Pour établir une connexion JDBC sécurisée à ClickHouse via SSL, vous devez configurer vos propriétés JDBC afin d'y inclure les paramètres SSL. Cela implique généralement de spécifier des propriétés SSL telles que sslmode et sslrootcert dans votre URL JDBC ou dans votre objet Properties.
Propriétés SSL
| Nom | Valeur par défaut | Valeurs possibles | Description |
|---|---|---|---|
ssl |
false | true, false | Indique s’il faut activer SSL/TLS pour la connexion |
sslmode |
strict | strict, none | Indique s’il faut vérifier le certificat SSL/TLS |
sslrootcert |
Chemin vers les certificats racines SSL/TLS | ||
sslcert |
Chemin vers le certificat SSL/TLS | ||
sslkey |
Clé RSA au format PKCS#8 | ||
key_store_type |
JKS, PKCS12 | Indique le type ou le format du fichier KeyStore/TrustStore |
|
trust_store |
Chemin du fichier TrustStore |
||
key_store_password |
Mot de passe requis pour accéder au fichier KeyStore spécifié dans la config KeyStore |
Ces propriétés garantissent que votre application Java communique avec le serveur ClickHouse via une connexion chiffrée, renforçant ainsi la sécurité des données lors de leur transmission.
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
}
}Résolution du timeout JDBC sur les insertions volumineuses
Lors de l'exécution de grands inserts dans ClickHouse avec de longs temps d'exécution, vous pouvez rencontrer des erreurs de timeout JDBC telles que :
Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]Ces erreurs peuvent perturber le processus d'insertion de données et affecter la stabilité du système. Pour résoudre ce problème, vous devez ajuster quelques paramètres de timeout dans le système d'exploitation du client.
Mac OS
Sur Mac OS, les paramètres suivants peuvent être modifiés pour résoudre le problème :
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1
Linux
Sur Linux, les paramètres équivalents seuls peuvent ne pas suffire à résoudre le problème. Des étapes supplémentaires sont nécessaires en raison des différences dans la manière dont Linux gère les paramètres de keep-alive des sockets. Suivez les étapes ci-dessous :
- Ajustez les paramètres suivants du noyau Linux dans
/etc/sysctl.confou dans un fichier de configuration connexe :
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 (Vous pouvez envisager d’abaisser cette valeur par rapport à la valeur default de 300 secondes)
- Après avoir modifié les paramètres du noyau, appliquez les modifications en exécutant la commande suivante :
sudo sysctl -pAprès avoir configuré ces paramètres, vous devez vous assurer que votre client active l'option Keep Alive sur le socket :
properties.setProperty("socket_keepalive", "true");Vous pouvez également ajouter des paramètres équivalents à l'URL JDBC.
Le timeout de socket et de connexion par défaut du pilote JDBC est de 30 secondes. Ce timeout peut être augmenté pour prendre en charge les opérations d'insertion de données volumineuses. Utilisez la méthode options sur ClickHouseClient avec les options SOCKET_TIMEOUT et CONNECTION_TIMEOUT telles que définies par 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();
}