All Products
Search
Document Center

ApsaraDB for ClickHouse:Use JDBC to connect to an ApsaraDB for ClickHouse cluster

Last Updated:Feb 17, 2025

This topic describes how to use the Java-based Java Database Connectivity (JDBC) to connect to a ApsaraDB for ClickHouse cluster in a Maven project.

Prerequisites

Procedure

The following section describes how to use JDBC to connect to an ApsaraDB for ClickHouse cluster in a new or an existing Maven project. You can also click awesome-clickhouse-jdbc-0.1.0.zip to download and review the sample code of the project. For more information about the sample code, see Sample project description.

Step 1: Create a Maven project

If a Maven project already exists, skip this step.

Create a Maven project by using Eclipse or another IDE tool.

Step 2: Add the required dependencies for the driver of ApsaraDB for ClickHouse

Add the following dependencies 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>

Step 3: Write application code

Overall process

The following flowchart shows the steps for using JDBC to connect to and perform operations on an ApsaraDB for ClickHouse cluster.

image

The related parameters include the cluster information and other parameters. The following table describes the parameters.

Parameter

Description

Example

YOUR_INSTANCE_PROTOCOL

The protocol that is used to establish network connection. The value is fixed to "http".

http

YOUR_INSTANCE_ENDPOINT

The endpoint of the cluster.

The value is in the VPC_ENDPOINT:8123 format. VPC_ENDPOINT specifies the VPC or public endpoint of the cluster.

cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123

YOUR_INSTANCE_USER

The username of the database account.

test

YOUR_INSTANCE_PASSWORD

The password of the database account.

Password****

INSERT_BATCH_SIZE

The number of rows that are inserted in a batch. Unit: rows.

10000

INSERT_BATCH_NUM

The number of batches inserted per thread. Unit: batches.

10

ENTERPRISE

The table engine. The table engine varies based on the cluster edition. Valid values:

  • true: Enterprise Edition

  • false: Community-compatible Edition

true

INSERT_OPTIMIZE_LEVEL

The level of optimization that is applied during data insertion. Valid values: 1, 2, and 3.

The insertion speeds are ranked in the following order: 3, 2, 1.

3

Complete sample code

In the default database of an Enterprise Edition cluster, create a table named test. Then, insert data into the test table in concurrent batches. Each batch contains 10,000 rows. Perform this operation for a total of 10 batches.

To run the code, you must modify the related parameters based on your business requirements. For more information about the parameters, see the Overall process section of this topic.

The main logic and entry point of the code are 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.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 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";
    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 {
            // 1. Connect to the database. 
            HikariConfig conf = buildHikariDataSource();
            try(HikariDataSource ds = new HikariDataSource(conf)) {
                Connection conn = ds.getConnection();

                // 2. Create a table. 
                createTable(conn);

                // 3. Insert data concurrently. 
                int concurrentNum = 5;
                // Start five threads. 
                CountDownLatch countDownLatch = new CountDownLatch(concurrentNum);
                ExecutorService executorService = Executors.newFixedThreadPool(concurrentNum);
                for (int i = 0; i < concurrentNum; i++) {
                    executorService.submit(() -> {
                        System.out.printf("[%d] Thread start inserting\n", Thread.currentThread().getId());
                        try {
                            //Insert data. 
                            batchInsert(ds.getConnection(), INSERT_OPTIMIZE_LEVEL);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            System.out.printf("[%d] Thread stop inserting\n", Thread.currentThread().getId());
                            countDownLatch.countDown();
                        }
                    });
                }
                // Wait until each thread completes data insertion. 
                countDownLatch.await();

                // 4. View the results. 
                count(conn);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * generate JDBC URL
     * @param protocol support http, https, grpc
     * @param endpoint endpoint
     * @return JDBC URL
     */
    public static String getJdbcUrl(String protocol, String endpoint) {
        return String.format(JDBC_URL, protocol, endpoint);
    }

    /**
     * build HikariDataSource
     * @return HikariConfig
     */
    public static HikariConfig buildHikariDataSource() throws Exception {
        HikariConfig conf = new HikariConfig();

        // datasource config
        conf.setDataSource(new ClickHouseDataSource(getJdbcUrl(YOUR_INSTANCE_PROTOCOL, YOUR_INSTANCE_ENDPOINT)));
        conf.setUsername(YOUR_INSTANCE_USER);
        conf.setPassword(YOUR_INSTANCE_PASSWORD);

        // connection pool config
        conf.setMaximumPoolSize(10);
        conf.setMinimumIdle(5);
        conf.setIdleTimeout(30000);
        conf.setMaxLifetime(60000);
        conf.setConnectionTimeout(30000);
        conf.setPoolName("HikariPool");

        return conf;
    }

    /**
     * create table
     * @param conn ClickHouse connection
     * @throws Exception
     */
    public static void createTable(Connection conn) throws Exception {
        if (ENTERPRISE) {
            conn.createStatement().execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
        } else {
            // create local table
            conn.createStatement().execute("CREATE TABLE IF NOT EXISTS `default`.`test_local` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
            // create distributed table
            conn.createStatement().execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = Distributed(default, default, test_local, rand());");
        }
    }

    /**
 * batch insert
 * @param conn ClickHouse connection
 * @param optimizeLevel insert optimize level, 3 is faster than 2, 2 is faster than 1<br/>
 *                      1: insert into `default`.`test` (id, name) values(?, ?) -- with additional query for getting table structure.
 *                         It's portable.<br/>
 *                      2: insert into `default`.`test` select id, name from input('id Int64, name String') -- effectively convert and insert data sent to the server
 *                         with given structure to the table with another structure. It's NOT portable(as it's limited to ClickHouse).<br/>
 *                      3: insert into `default`.`test` format RowBinary -- fastest(close to Java client) with streaming mode but requires manual serialization and it's
 *                         NOT portable(as it's limited to ClickHouse).
 * @throws Exception
 */
    public static void batchInsert(Connection conn, int optimizeLevel) throws Exception {
        // prepared statement
        PreparedStatement preparedStatement = null;
        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); // random number, prevent data duplicate and lost
        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] success, cost %d ms\n",
                    Thread.currentThread().getId(), optimizeLevel, i + 1, INSERT_BATCH_NUM, System.currentTimeMillis() - insertStartTime);
        }
    }

    /**
     * count table
     * @param conn ClickHouse connection
     * @throws Exception
     */
    public static void count(Connection conn) throws Exception {
        ResultSet resultSet = conn.createStatement().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`");
        }
    }
}

Sample project description

Click awesome-clickhouse-jdbc-0.1.0.zip to download the sample code.

Project environment

  • Maven version: 3.9.6

  • JDK version: 1.8

Project structure

The following figure and table describe the project structure.

image

File name

Description

awesome-clickhouse-jdbc-0.1.0

The name of the project.

mybatis-hikari-example

The name of the subproject.

  • Features:

    • The project uses HikariCP as the database connection pool and MyBatis as the common object-relational mapping (ORM) framework. The project also uses JDBC to access the persistence layer of the database.

    • The project has a complete structure, including the entity layer (entity), data access layer (mapper), and business logic layer (service). The structure is aligned with actual project development.

  • Scenario: You want to use the MyBatis framework to access an ApsaraDB for ClickHouse cluster.

native-example

The name of the subproject.

  • Features:

    • The sub-project uses HikariCP as the database connection pool and JDBC to access the persistence layer of the database.

    • The sub-project has only one main class, and all logic is included in the class.

  • Scenarios: You want to learn how to connect to an ApsaraDB for ClickHouse cluster by using JDBC or run a simple performance test.

Usage notes

mybatis-hikari-example

The code logic of the project is the same as the code logic of native-example. When you read and use the code, take note of the following parameters and the code entry point:

  • Database parameter configuration: src/main/resources/application.yml

  • Entry point to read the code and configure other parameters: src/main/java/com/aliyun/Main.java

The following table describes the parameters.

Entry point

Parameter

Description

Example

src/main/resources/application.yml

url

The endpoint of the cluster.

The value is in the jdbc:clickhouse:http://VPC_ENDPOINT:8123 format. VPC_ENDPOINT specifies the VPC or public endpoint of the cluster.

jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123

username

The username of the database account.

test

password

The password of the database account.

Password****

src/main/java/com/aliyun/Main.java

INSERT_BATCH_SIZE

The amount of data that you want to insert into the table. Unit: rows.

10000

INSERT_BATCH_NUM

The number of rows inserted each time. Unit: rows.

10

ENTERPRISE

The table engine. Valid values:

true: Enterprise Edition

false: Community-compatible Edition

true

INSERT_OPTIMIZE_LEVEL

The level of optimization that is applied during data insertion. Valid values: 1, 2, and 3.

The insertion speeds are ranked in the following order: 3, 2, 1.

3

native-example

The entry point to read the code and all parameter configurations are stored in src/main/java/com/aliyun/Main.java. For more information, see Step 3: Write application code.

References

If you need a tool to log on to a cluster, see the following topics:

FAQ

What do I do when the "connect timed out" error message appears after I start the program?

To troubleshoot the issue, perform the following steps:

  1. Check the whitelist: Check whether the IP address of the program is added to the whitelist of the destination cluster. For more information, see Configure a whitelist.

  2. Check the network:

    Check whether the application and the destination cluster reside in the same VPC.

  3. Check the configured endpoint:

    • Check whether the VPC or public endpoint is valid.

    • Check whether the port is valid. The default port is 8123.