Este guia orienta você na conexão com um cluster do ApsaraDB for ClickHouse usando Java Database Connectivity (JDBC) em um projeto Maven. Ao final, você terá uma conexão funcional com pool de conexões HikariCP, uma tabela criada e dados inseridos simultaneamente em múltiplas threads.
Pré-requisitos
Antes de começar, verifique se você possui:
Adicionado o endereço IP do servidor da aplicação à lista de permissões do cluster. Consulte Definir uma lista de permissões
Uma conta e senha de banco de dados. Consulte Criar uma conta
Maven 3.9.6 e JDK 1.8 instalados
Se o servidor da aplicação e o cluster estiverem em VPCs diferentes, resolva primeiro o problema de conectividade de rede. Consulte Como resolver problemas de conectividade de rede entre um cluster de destino e uma fonte de dados? Como alternativa, solicite um endpoint público. Consulte Solicitar e liberar um endpoint público .
Etapa 1: Adicionar dependências do Maven
Adicione as seguintes dependências ao arquivo 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>
Etapa 2: Entender o formato da URL JDBC
A URL JDBC segue este padrão:
jdbc:clickhouse:<protocol>://<endpoint>/<database>
Por exemplo:
jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123/default
Pontos importantes:
Especifique o protocolo explicitamente — o driver não o infere pelo número da porta.
O protocolo padrão é HTTP e a porta padrão é 8123. Use a porta 8123, a menos que utilize uma porta personalizada.
O formato do endpoint é
VPC_ENDPOINT:8123, ondeVPC_ENDPOINTcorresponde ao endpoint de VPC ou ao endpoint público do cluster.
Etapa 3: Escrever o código da aplicação
Funcionamento
O código de exemplo segue este fluxo:
Constrói um
HikariDataSourcecom configurações de pool de conexões e propriedades JDBC específicas do ClickHouse.Crie uma tabela — uma única tabela MergeTree para clusters Enterprise Edition ou uma tabela local mais uma tabela Distributed para clusters Community Edition.
Insere dados simultaneamente em 5 threads, cada uma inserindo 10 lotes de 10.000 linhas.
Conta o total de linhas na tabela para verificar as inserções.
Parâmetros de conexão
Substitua os valores de espaço reservado no código pelos valores reais do cluster.
|
Parâmetro |
Descrição |
Exemplo |
|
|
Protocolo de conexão. Valor fixo: |
|
|
|
Endpoint. Formato: |
|
|
|
Banco de dados de destino da conexão |
|
|
|
Conta do banco de dados |
|
|
|
Senha da conta do banco de dados |
|
|
|
Engine de tabela a ser utilizada. |
|
|
|
Quantidade de linhas por lote |
|
|
|
Quantidade de lotes por thread |
|
|
|
Nível de otimização de inserção. Valores válidos: |
|
Níveis de otimização de inserção
Todos os três níveis utilizam prepared statements. Escolha conforme seus requisitos de portabilidade:
|
Nível |
Padrão SQL |
Velocidade |
Portável |
|
1 |
|
Base |
Sim — JDBC padrão |
|
2 |
|
Mais rápido |
Não — específico do ClickHouse |
|
3 |
|
Mais rápido de todos |
Não — específico do ClickHouse, requer serialização manual |
Código de exemplo completo
O ponto de entrada é o método main. Antes de executar, atualize as constantes no início da classe com os valores do 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`");
}
}
}
}
Execute o código
Compile e execute a partir da raiz do projeto:
mvn compile && mvn exec:java -Dexec.mainClass="com.aliyun.Main"
Se a conexão for bem-sucedida e as inserções forem concluídas, a saída terminará com uma linha semelhante a:
Table `default`.`test` has 500000 rows
Baixe o projeto completo
Clique em awesome-clickhouse-jdbc-0.2.1.zip para baixar o projeto de exemplo.
O projeto contém dois subprojetos:

|
Subprojeto |
Descrição |
|
|
Utiliza HikariCP e JDBC padrão com uma única classe |
|
|
Emprega HikariCP, MyBatis (ORM) e JDBC padrão, com uma estrutura completa de camadas entity-mapper-service. Recomendado para integração do ClickHouse em projetos baseados em MyBatis. |
Configure mybatis-hikari-example
A lógica geral é a mesma do native-example. Defina os parâmetros abaixo antes de executar:
| Arquivo | Parâmetro | Descrição | Exemplo |
|---|---|---|---|
src/main/resources/application.yml |
url |
URL de conexão JDBC. Formato: jdbc:clickhouse:http://VPC_ENDPOINT:8123 |
jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123 |
username |
Conta do banco de dados | test |
|
password |
Senha da conta do banco de dados | Password**** |
|
src/main/java/com/aliyun/Main.java |
INSERT_BATCH_SIZE |
Quantidade de linhas por lote | 10000 |
INSERT_BATCH_NUM |
Quantidade de lotes a serem inseridos | 10 |
|
ENTERPRISE |
true para clusters Enterprise Edition, false para clusters Community Edition |
true |
|
INSERT_OPTIMIZE_LEVEL |
Nível de otimização de inserção. Valores válidos: 1, 2, 3. Quanto maior, mais rápido: 3 > 2 > 1 |
3 |
native-example
O ponto de entrada do código e todas as configurações de parâmetros deste projeto estão em src/main/java/com/aliyun/Main.java. Para mais informações, consulte Etapa 3: Escrever o código da aplicação.
Solução de problemas
Tempo limite de conexão
Verifique os itens abaixo nesta ordem:
Lista de permissões: Confirme se o endereço IP do servidor da aplicação foi adicionado à lista de permissões do cluster. Consulte Definir uma lista de permissões.
-
Rede: Verifique se a aplicação e o cluster estão na mesma VPC.
Em caso afirmativo, utilize o endpoint de VPC para conectar.
Caso contrário, resolva o problema de conectividade de rede. Consulte Como resolver problemas de conectividade de rede entre um cluster de destino e uma fonte de dados? Alternativamente, solicite um endpoint público. Consulte Solicitar e liberar um endpoint público.
Endpoint e porta: Valide se o endpoint está correto e se a porta é
8123.
Tempo limite de leitura
Esse erro geralmente ocorre durante inserções grandes com tempos de execução prolongados. Configure os parâmetros de keepalive TCP do sistema operacional e defina as seguintes propriedades JDBC, conforme demonstrado no código de exemplo:
properties.setProperty("socket_keepalive", "true");
properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");
Ambas as configurações já estão incluídas no código de exemplo. Para mais informações, consulte Solução de problemas.
HikariPool — conexão não disponível
Feche a conexão após o uso. O código de exemplo utiliza try-with-resources para fechar conexões automaticamente — adote o mesmo padrão no seu código.
Próximos passos
Conecte-se ao cluster usando outras ferramentas: