This topic describes how to use Java Database Connectivity (JDBC) in a Maven project to connect to an ApsaraDB for ClickHouse cluster.
Prerequisites
The IP address of the server where your application resides has been added to the whitelist of the ApsaraDB for ClickHouse cluster. For more information, see Set a whitelist.
NoteIf your application server and the ApsaraDB for ClickHouse cluster are not in the same VPC, you must resolve the network connectivity issue. For more information, see How do I resolve network connectivity issues between a destination cluster and a data source?. Alternatively, you can apply for a public endpoint and use it to connect. For more information, see Apply for and release a public endpoint.
A database account and password have been created. For more information, see Create an account.
Procedure
The following steps describe how to connect to an ApsaraDB for ClickHouse cluster using JDBC in a new or existing Maven project. You can also download the complete project sample.
Step 1: Create a Maven project
If you have an existing Maven project, skip this step.
Use Eclipse or another Integrated Development Environment (IDE) to create a Maven project.
Step 2: Import the ClickHouse driver dependency package
Add the following configuration to the pom.xml file.
<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>Step 3: Write the application code
Code flow
The following are the main steps to connect to and use an ApsaraDB for ClickHouse cluster with JDBC.
The parameters include cluster information and other connection settings. The following table describes the parameters.
Parameter | Description | Example |
YOUR_INSTANCE_PROTOCOL | The connection protocol. The value is fixed to "http". | http |
YOUR_INSTANCE_ENDPOINT | The endpoint. Format: |
|
DATABASE | The database to connect to. | testDB |
YOUR_INSTANCE_USER | The database account. | test |
YOUR_INSTANCE_PASSWORD | The password of the database account. | Password**** |
INSERT_BATCH_SIZE | The number of data rows to insert in a batch. Unit: rows. | 10000 |
INSERT_BATCH_NUM | The number of batches to be inserted by each thread. Unit: batches. | 10 |
ENTERPRISE | The table engine to use. The table engine varies based on the cluster edition.
| true |
INSERT_OPTIMIZE_LEVEL | The optimization level for insert performance. Valid values: 1, 2, and 3. The insert speed ranks as follows: 3 > 2 > 1. | 3 |
Complete sample code
The following sample code shows how to create a table named test in the default database of an Enterprise Edition cluster and concurrently insert 10 batches of data, with 10,000 rows per batch.
Before you run the code, modify the parameters as needed for your business scenario. For more information about the parameters, see the parameter table in the Code flow section.
The main logic and entry point of this code is the main method.
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`");
}
}
}
}Complete project sample description
Click awesome-clickhouse-jdbc-0.2.1.zip to download the sample code.
Project environment
Maven version: 3.9.6
JDK version: 1.8
Project structure
The following table describes the structure of this sample project.

File name | Description |
awesome-clickhouse-jdbc-0.2.1 | Project name. |
mybatis-hikari-example | Subproject name.
|
native-example | Subproject name.
|
Usage instructions
mybatis-hikari-example
The overall code logic of this project is consistent with the native-example project. When you use the code, take note of the following parameters and the code entry point.
Database parameter configuration:
src/main/resources/application.ymlCode entry point and other parameter configurations:
src/main/java/com/aliyun/Main.java
The following table describes the parameters.
Location to modify | Parameter | Description | Example |
| url | The endpoint. Format: |
|
username | The database account. | test | |
password | The password of the database account. | Password**** | |
| INSERT_BATCH_SIZE | The number of data rows to insert in a batch. Unit: rows. | 10000 |
INSERT_BATCH_NUM | The number of batches to insert. Unit: batches. | 10 | |
ENTERPRISE | The table engine to use. `true`: Enterprise Edition cluster. `false`: Community Edition cluster. | true | |
INSERT_OPTIMIZE_LEVEL | The optimization level for insert performance. Valid values: 1, 2, and 3. The insert speed ranks as follows: 3 > 2 > 1. | 3 |
native-example
The code entry point and all parameter configurations for this project are in src/main/java/com/aliyun/Main.java. For more information, see Step 3: Write the application code.
References
To log on to the cluster using other tools, see the following documents:
FAQ
Q: After I start the program, a connect timed out error is reported.
A: Troubleshoot the issue as follows:
Check the whitelist: Ensure that the IP address of the server where the program resides is added to the whitelist of the destination cluster. For more information, see Set a whitelist.
Check the network:
Are the application and the destination cluster in the same VPC?
If so, use the internal network to access the cluster. Use the VPC endpoint to connect to the cluster.
If not, resolve the network connectivity issue. For more information, see How do I resolve network connectivity issues between a destination cluster and a data source?. Alternatively, apply for a public endpoint and use it to connect to the cluster. For more information, see Apply for and release a public endpoint.
Check the configured endpoint:
Ensure that the VPC endpoint or public endpoint is correct.
Ensure that the port is correct. The default port is 8123.
Q: After I start the program, a "java.sql.SQLException: Read timed out" error is reported. How do I resolve this issue?
A: Configure the TCP keepalive parameters for the operating system and set JDBC properties such as socket_keepalive=true and http_connection_provider=APACHE_HTTP_CLIENT, as shown in the native-example project. For more information, see Troubleshooting.
Q: The client reports an error similar to "java.sql.SQLException: HikariPool-1 - Connection is not available". How do I resolve this issue?
A: Close the connection after you are finished using it. For more information, see the Complete project sample.