clickhouse-jdbc implémente l’interface JDBC standard en s’appuyant sur le dernier client Java.
Nous recommandons d’utiliser directement le dernier client Java si les performances ou l’accès direct sont essentiels.Prérequis environnementaux
- OpenJDK version >= 8
Configuration
- Maven
- Gradle (Kotlin)
- Gradle
- 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.ClickHouseDrivercom.clickhouse.jdbc.ClickHouseDriver est une classe de façade pour les nouvelles et anciennes implémentations JDBC. Elle utilise par défaut la nouvelle implémentation JDBC.
Vous pouvez utiliser l’ancienne implémentation JDBC en définissant la propriété système clickhouse.jdbc.v1 sur true. Cette propriété doit être définie avant tout appel à
la classe Driver.Une autre façon de basculer entre les versions consiste à utiliser directement les classes Driver de chaque version :com.clickhouse.jdbc.Driverest la nouvelle implémentation JDBC (V2).com.clickhouse.jdbc.DriverV1est l’ancienne implémentation JDBC (V1).
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
- 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 :Paramètres du serveurTous les paramètres du serveur doivent être précédés du préfixe
clickhouse_setting_ (comme pour la configuration du client).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éfinircom.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).showLineNumbers
showLineNumbers
http_user_agent suivante dans le journal des requêtes :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 unquery_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.showLineNumbers
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 java client 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.)
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.
- 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.
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'.)
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.
- 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).
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.
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 :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().
- 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.
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}}.
Object[] ou java.sql.Struct (voir comment écrire des tuples ci-dessous).ExempleResultSet#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.Exemplecom.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é.ExemplegetObject(columnIndex) renvoie Object[]. Les Tuples peuvent être lus en tant que java.sql.Array via la méthode getObject(columnIndex, Array.class).Exemplejava.collections.Map, car ce type nécessite des paires clé-valeur (java.sql.Struct ne prend pas en charge les paires clé-valeur).Exemplejava.collections.Map via la méthode getObject(columnIndex, Map.class).Exemplejava.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'}.
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.ExempleTypes 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
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).
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 :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.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
Fournir les identifiants et les paramètres
showLineNumbers
Instruction simple
showLineNumbers
Insert
showLineNumbers
HikariCP
showLineNumbers
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 leclasspath.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 :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 :
Guide de migration
Modifications principales
- JDBC V2 a été conçu pour être plus léger, et certaines fonctionnalités ont été supprimées.
- JDBC V2 ne prend pas en charge les données en streaming, 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.
- 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.
Modifications des types de données
Types numériques- La principale différence est que les types non signés sont associés à des types Java pour une meilleure portabilité.
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.
TimeetTime64sont pris en charge dans V2 uniquement en tant que nouveaux types.DateTimeetDateTime64sont associés àjava.sql.Timestamppour une meilleure compatibilité avec JDBC.
Types imbriqués
- 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.
Types Nullable et LowCardinality
NullableetLowCardinalitysont des types spéciaux qui encapsulent d’autres types.- Aucun changement n’est apporté à ces types dans V2.
- 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.