Topik ini menjelaskan cara menggunakan Java Database Connectivity (JDBC) dalam proyek Maven untuk menghubungkan ke kluster ApsaraDB for ClickHouse.
Prasyarat
Alamat IP server tempat aplikasi Anda berada telah ditambahkan ke daftar putih kluster ApsaraDB for ClickHouse. Untuk informasi selengkapnya, lihat Setel daftar putih.
CatatanJika server aplikasi dan kluster ApsaraDB for ClickHouse Anda tidak berada dalam VPC yang sama, Anda harus menyelesaikan masalah konektivitas jaringan. Untuk informasi selengkapnya, lihat Bagaimana cara menyelesaikan masalah konektivitas jaringan antara kluster tujuan dan sumber data?. Sebagai alternatif, Anda dapat mengajukan titik akhir publik dan menggunakannya untuk terhubung. Untuk informasi selengkapnya, lihat Ajukan dan lepas titik akhir publik.
Akun database dan kata sandi telah dibuat. Untuk informasi selengkapnya, lihat Buat akun.
Prosedur
Langkah-langkah berikut menjelaskan cara menghubungkan ke kluster ApsaraDB for ClickHouse menggunakan JDBC dalam proyek Maven baru atau yang sudah ada. Anda juga dapat mengunduh contoh proyek lengkap.
Langkah 1: Buat proyek Maven
Jika Anda memiliki proyek Maven yang sudah ada, lewati langkah ini.
Gunakan Eclipse atau Integrated Development Environment (IDE) lainnya untuk membuat proyek Maven.
Langkah 2: Impor paket dependensi driver ClickHouse
Tambahkan konfigurasi berikut ke file 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>Langkah 3: Tulis kode aplikasi
Alur kode
Berikut adalah langkah-langkah utama untuk menghubungkan dan menggunakan kluster ApsaraDB for ClickHouse dengan JDBC.
Parameter tersebut mencakup informasi kluster dan pengaturan koneksi lainnya. Tabel berikut menjelaskan parameter tersebut.
Parameter | Deskripsi | Contoh |
YOUR_INSTANCE_PROTOCOL | Protokol koneksi. Nilainya tetap "http". | http |
YOUR_INSTANCE_ENDPOINT | Titik akhir. Format: |
|
DATABASE | Database yang akan dihubungkan. | testDB |
YOUR_INSTANCE_USER | Akun database. | test |
YOUR_INSTANCE_PASSWORD | Password akun database. | Password**** |
INSERT_BATCH_SIZE | Jumlah baris data yang dimasukkan dalam satu batch. Satuan: baris. | 10000 |
INSERT_BATCH_NUM | Jumlah batch yang dimasukkan oleh setiap thread. Satuan: batch. | 10 |
ENTERPRISE | Mesin tabel yang digunakan. Mesin tabel bervariasi tergantung edisi kluster.
| true |
INSERT_OPTIMIZE_LEVEL | Tingkat optimasi untuk performa insert. Nilai yang valid: 1, 2, dan 3. Kecepatan insert diurutkan sebagai berikut: 3 > 2 > 1. | 3 |
Kode contoh lengkap
Kode contoh berikut menunjukkan cara membuat tabel bernama test di database default kluster Edisi Perusahaan dan memasukkan data secara konkuren sebanyak 10 batch, dengan 10.000 baris per batch.
Sebelum menjalankan kode, ubah parameter sesuai kebutuhan skenario bisnis Anda. Untuk informasi selengkapnya tentang parameter, lihat tabel parameter di bagian Alur kode.
Logika utama dan titik masuk kode ini adalah metode main.
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"; // KONFIGURASI ANDA DI SINI
private final static String DATABASE = "default"; // KONFIGURASI ANDA DI SINI
private final static String YOUR_INSTANCE_USER = "USER"; // KONFIGURASI ANDA DI SINI
private final static String YOUR_INSTANCE_PASSWORD = "PASSWORD"; // KONFIGURASI ANDA DI SINI
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; // KONFIGURASI ANDA DI SINI
private final static Integer INSERT_OPTIMIZE_LEVEL = 3;
public static void main(String[] args) {
try {
HikariConfig conf = buildHikariDataSource();
try(HikariDataSource ds = new HikariDataSource(conf)) {
// Buat tabel.
Connection conn = ds.getConnection();
createTable(conn);
conn.close();
// Masukkan data secara konkuren.
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 mulai memasukkan\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 berhenti memasukkan\n", Thread.currentThread().getId());
countDownLatch.countDown();
}
});
}
// Tunggu hingga semua thread selesai.
countDownLatch.await();
// Hitung jumlah data di tabel.
conn = ds.getConnection();
count(conn);
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Hasilkan URL JDBC.
* @param protocol Protokol. Protokol yang didukung termasuk http, https, dan grpc.
* @param endpoint Titik akhir.
* @return URL JDBC.
*/
public static String getJdbcUrl(String protocol, String endpoint, String database) {
return String.format(JDBC_URL, protocol, endpoint, database);
}
/**
* Bangun HikariDataSource.
* @return HikariConfig.
*/
public static HikariConfig buildHikariDataSource() throws Exception {
HikariConfig conf = new HikariConfig();
// Properti
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");
/// Zona waktu
properties.setProperty("use_server_time_zone", "true");
// Konfigurasi sumber data
conf.setDataSource(new ClickHouseDataSource(getJdbcUrl(YOUR_INSTANCE_PROTOCOL, YOUR_INSTANCE_ENDPOINT, DATABASE), properties));
conf.setUsername(YOUR_INSTANCE_USER);
conf.setPassword(YOUR_INSTANCE_PASSWORD);
// Konfigurasi kolam koneksi
conf.setMaximumPoolSize(10);
conf.setMinimumIdle(5);
conf.setIdleTimeout(30000);
conf.setMaxLifetime(60000);
conf.setConnectionTimeout(30000);
conf.setPoolName("HikariPool");
return conf;
}
/**
* Buat tabel.
* @param conn Koneksi ClickHouse.
* @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 {
// Buat tabel lokal.
statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test_local` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
// Buat tabel terdistribusi.
statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = Distributed(default, default, test_local, rand());");
}
}
}
/**
* Masukkan data dalam batch.
* @param conn Koneksi ClickHouse.
* @param optimizeLevel Tingkat optimasi insert. 3 lebih cepat dari 2, dan 2 lebih cepat dari 1.<br/>
* 1: insert into `default`.`test` (id, name) values(?, ?) -- dengan kueri tambahan untuk mendapatkan struktur tabel.
* Ini portabel.<br/>
* 2: insert into `default`.`test` select id, name from input('id Int64, name String') -- secara efektif mengonversi dan memasukkan data yang dikirim ke server
* dengan struktur tertentu ke dalam tabel dengan struktur lain. Ini TIDAK portabel karena terbatas pada ClickHouse.<br/>
* 3: insert into `default`.`test` format RowBinary -- paling cepat (mendekati klien Java) dengan mode streaming tetapi memerlukan serialisasi manual.
* Ini TIDAK portabel karena terbatas pada ClickHouse.
* @throws Exception
*/
public static void batchInsert(Connection conn, int optimizeLevel) throws Exception {
PreparedStatement preparedStatement = null;
try {
// Pernyataan terpersiapkan
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 harus bernilai 1, 2, atau 3");
}
// Masukkan data.
long randBase = (long) (Math.random() * 1000000); // Bilangan acak untuk mencegah duplikasi dan kehilangan data.
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;
// Tulis id (Int64).
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putLong(id);
clickHouseOutputStream.write(buffer.array());
// Tulis name (String).
clickHouseOutputStream.writeUnicodeString("name" + id);
}
}
}
preparedStatement.setObject(1, new MyClickHouseWriter(i));
preparedStatement.executeUpdate();
break;
}
System.out.printf("[%d] optimizeLevel=%d, batch insert [%d/%d] berhasil, memakan waktu %d ms\n",
Thread.currentThread().getId(), optimizeLevel, i + 1, INSERT_BATCH_NUM, System.currentTimeMillis() - insertStartTime);
}
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
}
}
/**
* Hitung jumlah data di tabel.
* @param conn Koneksi ClickHouse.
* @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("Tabel `default`.`test` memiliki %d baris\n", resultSet.getInt("cnt"));
} else {
throw new RuntimeException("Gagal menghitung tabel `default`.`test`");
}
}
}
}Deskripsi contoh proyek lengkap
Klik awesome-clickhouse-jdbc-0.2.1.zip untuk mengunduh kode contoh.
Lingkungan proyek
Versi Maven: 3.9.6
Versi JDK: 1.8
Struktur proyek
Tabel berikut menjelaskan struktur proyek contoh ini.

Nama file | Deskripsi |
awesome-clickhouse-jdbc-0.2.1 | Nama proyek. |
mybatis-hikari-example | Nama subproyek.
|
native-example | Nama subproyek.
|
Petunjuk penggunaan
mybatis-hikari-example
Logika kode keseluruhan proyek ini konsisten dengan proyek native-example. Saat menggunakan kode, perhatikan parameter berikut dan titik masuk kodenya.
Konfigurasi parameter database:
src/main/resources/application.ymlTitik masuk kode dan konfigurasi parameter lainnya:
src/main/java/com/aliyun/Main.java
Tabel berikut menjelaskan parameter tersebut.
Lokasi modifikasi | Parameter | Deskripsi | Contoh |
| url | Titik akhir. Format: |
|
username | Akun database. | test | |
password | Password akun database. | Password**** | |
| INSERT_BATCH_SIZE | Jumlah baris data yang dimasukkan dalam satu batch. Satuan: baris. | 10000 |
INSERT_BATCH_NUM | Jumlah batch yang dimasukkan. Satuan: batch. | 10 | |
ENTERPRISE | Mesin tabel yang digunakan. `true`: Kluster Edisi Perusahaan. `false`: Kluster Edisi Komunitas. | true | |
INSERT_OPTIMIZE_LEVEL | Tingkat optimasi untuk performa insert. Nilai yang valid: 1, 2, dan 3. Kecepatan insert diurutkan sebagai berikut: 3 > 2 > 1. | 3 |
native-example
Titik masuk kode dan seluruh konfigurasi parameter untuk proyek ini berada di src/main/java/com/aliyun/Main.java. Untuk informasi selengkapnya, lihat Langkah 3: Tulis kode aplikasi.
Referensi
Untuk login ke kluster menggunakan tool lain, lihat dokumen berikut:
FAQ
Q: Setelah saya menjalankan program, muncul error connect timed out.
A: Lakukan troubleshooting sebagai berikut:
Periksa daftar putih: Pastikan alamat IP server tempat program berjalan telah ditambahkan ke daftar putih kluster tujuan. Untuk informasi selengkapnya, lihat Setel daftar putih.
Periksa jaringan:
Apakah aplikasi dan kluster tujuan berada dalam VPC yang sama?
Jika ya, gunakan jaringan internal untuk mengakses kluster. Gunakan titik akhir VPC untuk menghubungkan ke kluster.
Jika tidak, selesaikan masalah konektivitas jaringan. Untuk informasi selengkapnya, lihat Bagaimana cara menyelesaikan masalah konektivitas jaringan antara kluster tujuan dan sumber data?. Sebagai alternatif, ajukan titik akhir publik dan gunakan untuk menghubungkan ke kluster. Untuk informasi selengkapnya, lihat Ajukan dan lepas titik akhir publik.
Periksa titik akhir yang dikonfigurasi:
Pastikan titik akhir VPC atau titik akhir publik benar.
Pastikan port benar. Port default adalah 8123.
Q: Setelah saya menjalankan program, muncul error "java.sql.SQLException: Read timed out". Bagaimana cara menyelesaikannya?
A: Konfigurasikan parameter TCP keepalive pada sistem operasi dan atur properti JDBC seperti socket_keepalive=true dan http_connection_provider=APACHE_HTTP_CLIENT, seperti yang ditunjukkan dalam proyek native-example. Untuk informasi selengkapnya, lihat troubleshooting.
Q: Klien melaporkan error serupa "java.sql.SQLException: HikariPool-1 - Connection is not available". Bagaimana cara menyelesaikannya?
A: Tutup koneksi setelah selesai menggunakannya. Untuk informasi selengkapnya, lihat Contoh proyek lengkap.