Ce guide explique comment se connecter à un cluster ApsaraDB for ClickHouse avec Java Database Connectivity (JDBC) dans un projet Maven. À la fin de ce tutoriel, vous disposerez d'une connexion fonctionnelle dotée d'un pool de connexions HikariCP, d'une table créée et de données insérées simultanément sur plusieurs threads.
Prérequis
Avant de commencer, vérifiez que vous disposez des éléments suivants :
L'adresse IP de votre serveur d'applications a été ajoutée à la liste d'autorisation du cluster. Consultez Définir une liste d'autorisation
Un compte de base de données et son mot de passe. Consultez Créer un compte
Maven 3.9.6 et JDK 1.8 sont installés
Si votre serveur d'applications et le cluster se trouvent dans des VPC différents, résolvez d'abord les problèmes de connectivité réseau. Consultez Comment résoudre les problèmes de connectivité réseau entre un cluster de destination et une source de données ? Vous pouvez également demander un endpoint public. Consultez Demander et libérer un endpoint public .
Étape 1 : Ajouter les dépendances Maven
Ajoutez les dépendances suivantes à votre fichier pom.xml :
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.4.6</version>
</dependency>
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
Étape 2 : Comprendre le format de l'URL JDBC
L'URL JDBC suit le modèle suivant :
jdbc:clickhouse:<protocol>://<endpoint>/<database>
Par exemple :
jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123/default
Points clés :
Le protocole doit être spécifié explicitement ; le pilote ne le déduit pas du numéro de port.
Le protocole par défaut est HTTP et le port par défaut est 8123. Spécifiez le port 8123 sauf si vous utilisez un port personnalisé.
-
Le format de l'endpoint est
VPC_ENDPOINT:8123, oùVPC_ENDPOINTcorrespond à l'endpoint VPC ou à l'endpoint public de votre cluster.
Étape 3 : Écrire le code de l'application
Fonctionnement
L'exemple de code suit le flux ci-dessous :
-
Générez un objet
HikariDataSourceavec les paramètres du pool de connexions et les propriétés JDBC spécifiques à ClickHouse. Créez une table : une seule table MergeTree pour les clusters Enterprise Edition, ou une table locale plus une table distribuée pour les clusters Community Edition.
Insérez des données simultanément sur 5 threads, chacun insérant 10 lots de 10 000 lignes.
Comptez le nombre total de lignes dans la table pour vérifier les insertions.
Paramètres de connexion
Remplacez les valeurs fictives du code par les valeurs réelles de votre cluster.
| Paramètre | Description | Exemple |
|---|---|---|
YOUR_INSTANCE_PROTOCOL |
Le protocole de connexion. La valeur est fixée à "http". |
http |
YOUR_INSTANCE_ENDPOINT |
L'endpoint. Format : VPC_ENDPOINT:8123 |
cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123 |
DATABASE |
La base de données à laquelle se connecter | default |
YOUR_INSTANCE_USER |
Le compte de base de données | test |
YOUR_INSTANCE_PASSWORD |
Le mot de passe du compte de base de données | Password**** |
ENTERPRISE |
Le moteur de table à utiliser. true pour les clusters Enterprise Edition, false pour les clusters Community Edition |
true |
INSERT_BATCH_SIZE |
Nombre de lignes par lot | 10000 |
INSERT_BATCH_NUM |
Nombre de lots par thread | 10 |
INSERT_OPTIMIZE_LEVEL |
Niveau d'optimisation de l'insertion. Valeurs valides : 1, 2, 3. Plus le niveau est élevé, plus la vitesse est grande : 3 > 2 > 1 |
3 |
Niveaux d'optimisation de l'insertion
Les trois niveaux utilisent des instructions préparées. Choisissez en fonction de vos besoins en matière de portabilité :
| Niveau | Modèle SQL | Vitesse | Portable |
|---|---|---|---|
| 1 | INSERT INTO ... VALUES (?, ?) |
Référence | Oui — JDBC standard |
| 2 | INSERT INTO ... SELECT ... FROM input(...) |
Plus rapide | Non — Spécifique à ClickHouse |
| 3 | INSERT INTO ... FORMAT RowBinary |
Le plus rapide | Non — Spécifique à ClickHouse, nécessite une sérialisation manuelle |
Exemple de code complet
Le point d'entrée est la méthode main. Avant l'exécution, mettez à jour les constantes en haut de la classe avec les valeurs de votre cluster.
package com.aliyun;
import com.clickhouse.jdbc.ClickHouseDataSource;
import com.clickhouse.data.ClickHouseOutputStream;
import com.clickhouse.data.ClickHouseWriter;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
private final static String YOUR_INSTANCE_PROTOCOL = "http";
private final static String YOUR_INSTANCE_ENDPOINT = "VPC_ENDPOINT:8123"; // YOUR CONFIG HERE
private final static String DATABASE = "default"; // YOUR CONFIG HERE
private final static String YOUR_INSTANCE_USER = "USER"; // YOUR CONFIG HERE
private final static String YOUR_INSTANCE_PASSWORD = "PASSWORD"; // YOUR CONFIG HERE
private final static String JDBC_URL = "jdbc:clickhouse:%s://%s/%s";
private final static Integer INSERT_BATCH_SIZE = 10000;
private final static Integer INSERT_BATCH_NUM = 10;
private final static boolean ENTERPRISE = true; // YOUR CONFIG HERE
private final static Integer INSERT_OPTIMIZE_LEVEL = 3;
public static void main(String[] args) {
try {
HikariConfig conf = buildHikariDataSource();
try(HikariDataSource ds = new HikariDataSource(conf)) {
// Create a table.
Connection conn = ds.getConnection();
createTable(conn);
conn.close();
// Concurrently insert data.
int concurrentNum = 5;
CountDownLatch countDownLatch = new CountDownLatch(concurrentNum);
ExecutorService executorService = Executors.newFixedThreadPool(concurrentNum);
for (int i = 0; i < concurrentNum; i++) {
executorService.submit(() -> {
System.out.printf("[%d] Thread starts inserting\n", Thread.currentThread().getId());
try(Connection connection = ds.getConnection()) {
batchInsert(connection, INSERT_OPTIMIZE_LEVEL);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.printf("[%d] Thread stops inserting\n", Thread.currentThread().getId());
countDownLatch.countDown();
}
});
}
// Wait for all threads to finish.
countDownLatch.await();
// Count the table.
conn = ds.getConnection();
count(conn);
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Generate the JDBC URL.
* @param protocol The protocol. Supported protocols include http, https, and grpc.
* @param endpoint The endpoint.
* @return The JDBC URL.
*/
public static String getJdbcUrl(String protocol, String endpoint, String database) {
return String.format(JDBC_URL, protocol, endpoint, database);
}
/**
* Build HikariDataSource.
* @return The HikariConfig.
*/
public static HikariConfig buildHikariDataSource() throws Exception {
HikariConfig conf = new HikariConfig();
// Properties
Properties properties = new Properties();
/// Socket keepalive
properties.setProperty("socket_keepalive", "true");
properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");
/// Socket timeout
properties.setProperty("socket_timeout", "120000");
/// Timezone
properties.setProperty("use_server_time_zone", "true");
// Data source configuration
conf.setDataSource(new ClickHouseDataSource(getJdbcUrl(YOUR_INSTANCE_PROTOCOL, YOUR_INSTANCE_ENDPOINT, DATABASE), properties));
conf.setUsername(YOUR_INSTANCE_USER);
conf.setPassword(YOUR_INSTANCE_PASSWORD);
// Connection pool configuration
conf.setMaximumPoolSize(10);
conf.setMinimumIdle(5);
conf.setIdleTimeout(30000);
conf.setMaxLifetime(60000);
conf.setConnectionTimeout(30000);
conf.setPoolName("HikariPool");
return conf;
}
/**
* Create a table.
* @param conn The ClickHouse connection.
* @throws Exception
*/
public static void createTable(Connection conn) throws Exception {
try(Statement statement = conn.createStatement()) {
if (ENTERPRISE) {
statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
} else {
// Create a local table.
statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test_local` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
// Create a distributed table.
statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = Distributed(default, default, test_local, rand());");
}
}
}
/**
* Insert data in batches.
* @param conn The ClickHouse connection.
* @param optimizeLevel The insert optimization level. 3 is faster than 2, and 2 is faster than 1.<br/>
* 1: insert into `default`.`test` (id, name) values(?, ?) -- with an additional query to get the table structure.
* This is portable.<br/>
* 2: insert into `default`.`test` select id, name from input('id Int64, name String') -- effectively converts and inserts data sent to the server
* with a given structure into the table with another structure. This is NOT portable because it is limited to ClickHouse.<br/>
* 3: insert into `default`.`test` format RowBinary -- fastest (close to the Java client) with streaming mode but requires manual serialization.
* This is NOT portable because it is limited to ClickHouse.
* @throws Exception
*/
public static void batchInsert(Connection conn, int optimizeLevel) throws Exception {
PreparedStatement preparedStatement = null;
try {
// Prepared statement
switch (optimizeLevel) {
case 1:
preparedStatement = conn.prepareStatement("insert into `default`.`test` (id, name) values(?, ?)");
break;
case 2:
preparedStatement = conn.prepareStatement("insert into `default`.`test` select id, name from input('id Int64, name String')");
break;
case 3:
preparedStatement = conn.prepareStatement("insert into `default`.`test` format RowBinary");
break;
default:
throw new IllegalArgumentException("optimizeLevel must be 1, 2 or 3");
}
// Insert data.
long randBase = (long) (Math.random() * 1000000); // A random number to prevent data duplication and loss.
for (int i = 0; i < INSERT_BATCH_NUM; i++) {
long insertStartTime = System.currentTimeMillis();
switch (optimizeLevel) {
case 1:
case 2:
for (int j = 0; j < INSERT_BATCH_SIZE; j++) {
long id = (long) i * INSERT_BATCH_SIZE + j + randBase;
preparedStatement.setLong(1, id);
preparedStatement.setString(2, "name" + id);
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
break;
case 3:
class MyClickHouseWriter implements ClickHouseWriter {
int batchIndex = 0;
public MyClickHouseWriter(int batchIndex) {
this.batchIndex = batchIndex;
}
@Override
public void write(ClickHouseOutputStream clickHouseOutputStream) throws IOException {
for (int j = 0; j < INSERT_BATCH_SIZE; j++) {
long id = (long) batchIndex * INSERT_BATCH_SIZE + j + randBase;
// Write id (Int64).
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putLong(id);
clickHouseOutputStream.write(buffer.array());
// Write name (String).
clickHouseOutputStream.writeUnicodeString("name" + id);
}
}
}
preparedStatement.setObject(1, new MyClickHouseWriter(i));
preparedStatement.executeUpdate();
break;
}
System.out.printf("[%d] optimizeLevel=%d, insert batch [%d/%d] succeeded, cost %d ms\n",
Thread.currentThread().getId(), optimizeLevel, i + 1, INSERT_BATCH_NUM, System.currentTimeMillis() - insertStartTime);
}
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
}
}
/**
* Count the table.
* @param conn The ClickHouse connection.
* @throws Exception
*/
public static void count(Connection conn) throws Exception {
try(Statement statement = conn.createStatement()) {
ResultSet resultSet = statement.executeQuery("SELECT count() as cnt FROM `default`.`test`");
if (resultSet.next()) {
System.out.printf("Table `default`.`test` has %d rows\n", resultSet.getInt("cnt"));
} else {
throw new RuntimeException("Failed to count table `default`.`test`");
}
}
}
}
Exécuter le code
Compilez et exécutez depuis la racine du projet :
mvn compile && mvn exec:java -Dexec.mainClass="com.aliyun.Main"
Si la connexion réussit et que les insertions sont terminées, la sortie se termine par une ligne similaire à :
Table `default`.`test` has 500000 rows
Télécharger le projet complet
Cliquez sur awesome-clickhouse-jdbc-0.2.1.zip pour télécharger l'exemple de projet.
Le projet contient deux sous-projets :

| Sous-projet | Description |
|---|---|
native-example |
Utilise HikariCP et JDBC standard avec une seule classe Main. Utilisez ce projet pour apprendre la connectivité JDBC ou effectuer des tests de performance de base. |
mybatis-hikari-example |
Utilise HikariCP, MyBatis (ORM) et JDBC standard, avec une structure complète en couches entité-mapper-service. Utilisez ce projet si vous intégrez ClickHouse dans un projet basé sur MyBatis. |
Configurer mybatis-hikari-example
La logique globale est identique à celle de native-example. Configurez les éléments suivants avant l'exécution :
| Fichier | Paramètre | Description | Exemple |
|---|---|---|---|
src/main/resources/application.yml |
url |
URL de connexion JDBC. Format : jdbc:clickhouse:http://VPC_ENDPOINT:8123 |
jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123 |
username |
Le compte de base de données | test |
|
password |
Le mot de passe du compte de base de données | Password**** |
|
src/main/java/com/aliyun/Main.java |
INSERT_BATCH_SIZE |
Nombre de lignes par lot | 10000 |
INSERT_BATCH_NUM |
Nombre de lots à insérer | 10 |
|
ENTERPRISE |
true pour les clusters Enterprise Edition, false pour les clusters Community Edition |
true |
|
INSERT_OPTIMIZE_LEVEL |
Niveau d'optimisation de l'insertion. Valeurs valides : 1, 2, 3. Plus le niveau est élevé, plus la vitesse est grande : 3 > 2 > 1 |
3 |
native-example
Le point d'entrée du code et toutes les configurations de paramètres pour ce projet se trouvent dans src/main/java/com/aliyun/Main.java. Pour plus d'informations, consultez Étape 3 : Écrire le code de l'application.
Dépannage
Délai de connexion dépassé
Vérifiez les points suivants dans l'ordre :
Liste d'autorisation : Assurez-vous que l'adresse IP du serveur d'applications a été ajoutée à la liste d'autorisation du cluster. Consultez Définir une liste d'autorisation.
-
Réseau : Vérifiez si l'application et le cluster se trouvent dans le même VPC.
Si oui, utilisez l'endpoint VPC pour vous connecter.
Si non, résolvez le problème de connectivité réseau. Consultez Comment résoudre les problèmes de connectivité réseau entre un cluster de destination et une source de données ? Vous pouvez également demander un endpoint public. Consultez Demander et libérer un endpoint public.
-
Endpoint et port : Vérifiez que l'endpoint est correct et que le port est
8123.
Délai de lecture dépassé
Cette erreur survient généralement lors d'insertions volumineuses avec des temps d'exécution longs. Configurez les paramètres TCP keepalive pour le système d'exploitation et définissez les propriétés JDBC suivantes, comme indiqué dans l'exemple de code :
properties.setProperty("socket_keepalive", "true");
properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");
Ces deux paramètres sont déjà inclus dans l'exemple de code. Pour plus d'informations, consultez Dépannage.
HikariPool — la connexion n'est pas disponible
Fermez la connexion après utilisation. L'exemple de code utilise l'instruction try-with-resources pour fermer automatiquement les connexions ; appliquez le même modèle dans votre code.
Étapes suivantes
Connectez-vous à votre cluster à l'aide d'autres outils :