Esta guía explica cómo conectarse a un clúster de ApsaraDB for ClickHouse mediante Java Database Connectivity (JDBC) en un proyecto Maven. Al finalizar, contará con una conexión funcional que utiliza un pool de conexiones HikariCP, una tabla creada y datos insertados de forma concurrente en varios hilos.
Requisitos previos
Antes de empezar, asegúrese de cumplir con lo siguiente:
La dirección IP del servidor de su aplicación debe estar agregada a la lista de permitidos del clúster. Consulte Set a whitelist
Debe contar con una cuenta de base de datos y su contraseña. Consulte Create an account
Tener instalados Maven 3.9.6 y JDK 1.8
Si el servidor de su aplicación y el clúster se encuentran en distintas VPC, resuelva primero el problema de conectividad de red. Consulte How do I resolve network connectivity issues between a destination cluster and a data source? Como alternativa, solicite un endpoint público. Consulte Apply for and release a public endpoint .
Paso 1: Agregar dependencias de Maven
Agregue las siguientes dependencias a su archivo 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>
Paso 2: Comprender el formato de la URL JDBC
La URL JDBC sigue este patrón:
jdbc:clickhouse:<protocol>://<endpoint>/<database>
Por ejemplo:
jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123/default
Puntos clave:
El protocolo debe especificarse explícitamente, ya que el controlador no lo infiere a partir del número de puerto.
El protocolo predeterminado es HTTP y el puerto predeterminado es 8123. Especifique el puerto 8123 a menos que utilice un puerto personalizado.
El formato del endpoint es
VPC_ENDPOINT:8123, dondeVPC_ENDPOINTcorresponde al endpoint de VPC o al endpoint público de su clúster.
Paso 3: Escribir el código de la aplicación
Funcionamiento
El código de ejemplo sigue este flujo:
Construye un objeto
HikariDataSourcecon la configuración del pool de conexiones y las propiedades JDBC específicas de ClickHouse.Crea una tabla: una única tabla MergeTree para clústeres Enterprise Edition, o bien una tabla local más una tabla Distributed para clústeres Community Edition.
Inserta datos de forma concurrente en 5 hilos; cada uno inserta 10 lotes de 10,000 filas.
Cuenta el total de filas en la tabla para verificar las inserciones.
Parámetros de conexión
Reemplace los valores de marcador de posición en el código con los valores reales de su clúster.
|
Parámetro |
Descripción |
Ejemplo |
|
|
Protocolo de conexión. El valor es fijo: |
|
|
|
Endpoint. Formato: |
|
|
|
Base de datos a la que se conectará |
|
|
|
Cuenta de la base de datos |
|
|
|
Contraseña de la cuenta de la base de datos |
|
|
|
Motor de tabla por utilizar. Use |
|
|
|
Número de filas por lote |
|
|
|
Número de lotes por hilo |
|
|
|
Nivel de optimización de inserción. Valores válidos: |
|
Niveles de optimización de inserción
Los tres niveles utilizan sentencias preparadas. Elija el nivel adecuado según sus requisitos de portabilidad:
|
Nivel |
Patrón SQL |
Velocidad |
Portabilidad |
|
1 |
|
Referencia |
Sí — JDBC estándar |
|
2 |
|
Más rápido |
No — específico de ClickHouse |
|
3 |
|
El más rápido |
No — específico de ClickHouse; requiere serialización manual |
Código de ejemplo completo
El punto de entrada es el método main. Antes de ejecutarlo, actualice las constantes al inicio de la clase con los valores de su clúster.
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`");
}
}
}
}
Ejecutar el código
Compile y ejecute desde la raíz del proyecto:
mvn compile && mvn exec:java -Dexec.mainClass="com.aliyun.Main"
Si la conexión es exitosa y las inserciones se completan, la salida terminará con una línea similar a la siguiente:
Table `default`.`test` has 500000 rows
Descargar el proyecto completo
Haga clic en awesome-clickhouse-jdbc-0.2.1.zip para descargar el proyecto de ejemplo.
El proyecto contiene dos subproyectos:

|
Subproyecto |
Descripción |
|
|
Utiliza HikariCP y JDBC estándar con una única clase |
|
|
Utiliza HikariCP, MyBatis (ORM) y JDBC estándar, con una estructura completa de capas entidad-mapper-servicio. Úselo si va a integrar ClickHouse en un proyecto basado en MyBatis. |
Configure mybatis-hikari-example
La lógica general es la misma que en native-example. Configure los siguientes parámetros antes de ejecutar:
| Archivo | Parámetro | Descripción | Ejemplo |
|---|---|---|---|
src/main/resources/application.yml |
url |
URL de conexión JDBC. Formato: jdbc:clickhouse:http://VPC_ENDPOINT:8123 |
jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123 |
username |
Cuenta de la base de datos | test |
|
password |
Contraseña de la cuenta de la base de datos | Password**** |
|
src/main/java/com/aliyun/Main.java |
INSERT_BATCH_SIZE |
Número de filas por lote | 10000 |
INSERT_BATCH_NUM |
Número de lotes a insertar | 10 |
|
ENTERPRISE |
true para clústeres Enterprise Edition, false para clústeres Community Edition |
true |
|
INSERT_OPTIMIZE_LEVEL |
Nivel de optimización de inserción. Valores válidos: 1, 2, 3. Un valor mayor implica mayor velocidad: 3 > 2 > 1 |
3 |
native-example
El punto de entrada del código y todas las configuraciones de parámetros de este proyecto se encuentran en src/main/java/com/aliyun/Main.java. Para obtener más información, consulte Step 3: Write the application code.
Solución de problemas
Tiempo de espera de conexión agotado
Revise los siguientes puntos en orden:
Lista de permitidos: Asegúrese de que la dirección IP del servidor de la aplicación esté agregada a la lista de permitidos del clúster. Consulte Set a whitelist.
-
Red: Compruebe si la aplicación y el clúster se encuentran en la misma VPC.
En caso afirmativo, utilice el endpoint de VPC para conectarse.
En caso negativo, resuelva el problema de conectividad de red. Consulte How do I resolve network connectivity issues between a destination cluster and a data source?. Como alternativa, solicite un endpoint público. Consulte Apply for and release a public endpoint.
Endpoint y puerto: Verifique que el endpoint sea correcto y que el puerto sea
8123.
Tiempo de espera de lectura agotado
Este error suele ocurrir durante inserciones grandes con tiempos de ejecución prolongados. Configure los parámetros TCP keepalive del sistema operativo y establezca las siguientes propiedades JDBC, tal como se muestra en el código de ejemplo:
properties.setProperty("socket_keepalive", "true");
properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");
Ambas configuraciones ya están incluidas en el código de ejemplo. Para obtener más información, consulte Troubleshooting.
HikariPool — la conexión no está disponible
Cierre la conexión después de usarla. El código de ejemplo utiliza try-with-resources para cerrar las conexiones automáticamente; siga el mismo patrón en su código.
Pasos siguientes
Conéctese a su clúster mediante otras herramientas: