본문으로 바로가기
본문으로 바로가기

JDBC 드라이버

참고

clickhouse-jdbc는 최신 Java 클라이언트를 사용하여 표준 JDBC 인터페이스를 구현합니다. 성능 또는 직접 액세스가 중요한 경우 최신 Java 클라이언트를 직접 사용하시기 바랍니다.

환경 요구 사항

설정

<!-- https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc -->
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.9.7</version>
    <classifier>all</classifier>
</dependency>

클래스패스에 JAR 파일을 추가해야 하는 애플리케이션에서 JDBC 드라이버를 사용하는 경우, 다음 위치에서 JAR 파일을 다운로드한 후 클래스패스에 추가하십시오:

구성

드라이버 클래스: com.clickhouse.jdbc.ClickHouseDriver

참고

com.clickhouse.jdbc.ClickHouseDriver는 새로운 JDBC 구현과 이전 JDBC 구현을 위한 파사드 역할을 하는 클래스입니다. 기본적으로 새로운 JDBC 구현을 사용합니다. 이전 JDBC 구현을 사용하려면 clickhouse.jdbc.v1 시스템 속성을 true로 설정해야 합니다. 이 속성은 Driver 클래스를 호출하기 전에 설정해야 합니다.

버전을 전환하는 또 다른 방법은 각 버전의 Driver 클래스를 직접 사용하는 것입니다:

  • com.clickhouse.jdbc.Driver는 새로운 JDBC 구현(V2)입니다.
  • com.clickhouse.jdbc.DriverV1은 기존 JDBC 구현(V1)입니다.

URL 구문: jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1&param2=value2][#tag1,tag2,...], 예를 들어:

  • jdbc:clickhouse:http://localhost:8123
  • jdbc:clickhouse:https://localhost:8443?ssl=true

URL 구문과 관련해 알아두어야 할 몇 가지 사항은 다음과 같습니다:

  • URL에는 엔드포인트를 하나만 지정할 수 있습니다
  • 프로토콜이 기본값인 'HTTP'가 아닌 경우에는 반드시 프로토콜을 명시해야 합니다
  • 기본 포트 8123이 아닌 경우 포트를 명시해야 합니다.
  • 드라이버는 포트 번호만 보고 프로토콜을 추론하지 않으므로 반드시 프로토콜을 명시적으로 지정해야 합니다
  • 프로토콜을 지정하면 ssl 매개변수를 설정할 필요가 없습니다.

연결 속성(Connection Properties)

주요 구성 매개변수는 Java 클라이언트에 정의되어 있습니다. 이러한 매개변수는 드라이버에 그대로 전달해야 합니다. 드라이버에는 클라이언트 구성에 포함되지 않는 고유한 속성도 있으며, 해당 속성은 아래에 나열되어 있습니다.

드라이버 속성:

속성기본값설명
disable_frameworks_detectiontrueUser-Agent 기반 프레임워크 감지를 비활성화합니다
jdbc_ignore_unsupported_valuesfalse드라이버 동작에 영향을 주지 않는 경우 SQLFeatureNotSupportedException 발생을 억제합니다
clickhouse.jdbc.v1false새 JDBC 구현 대신 이전 JDBC 구현을 사용합니다
default_query_settingsnull쿼리 실행 시 기본 쿼리 설정을 함께 전달할 수 있도록 합니다
jdbc_resultset_auto_closetrueStatement를 닫으면 ResultSet이 자동으로 닫힙니다
beta.row_binary_for_simple_insertfalseRowBinary writer 기반의 PreparedStatement 구현을 사용합니다. INSERT INTO ... VALUES 쿼리에만 동작합니다.
jdbc_resultset_auto_closetrueStatement를 닫으면 ResultSet이 자동으로 닫힙니다
jdbc_use_max_result_rowsfalse서버 속성 max_result_rows를 사용하여 쿼리 결과로 반환되는 행 수를 제한할 수 있도록 합니다. 활성화되면 사용자가 설정한 overflow 모드를 무시합니다. 자세한 내용은 JavaDoc을 참조하십시오.
jdbc_sql_parserJAVACC사용할 SQL 파서를 설정합니다. 선택 가능한 값은 ANTLR4, ANTLR4_PARAMS_PARSER, JAVACC입니다.
remember_last_set_rolestrue연결에 대해 마지막으로 설정된 역할을 기억합니다
서버 설정

모든 서버 설정에는 clickhouse_setting_ 접두사를 붙여야 합니다(클라이언트 구성과 동일).

Properties config = new Properties();
config.setProperty("user", "default");
config.setProperty("password", getPassword());

// set server setting
config.put(ClientConfigProperties.serverSetting("allow_experimental_time_time64_type"), "1");

Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", config);

구성 예시:

Properties properties = new Properties();
properties.setProperty("user", "default");
properties.setProperty("password", getPassword());
properties.setProperty("client_name", "my-app-01"); // when http protocol is used it will be `http_user_agent` in the query log but not `client_name`.

Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", properties);

다음 JDBC URL과 동일합니다:

jdbc:ch:http://localhost:8123/?user=default&password=password&client_name=my-app-01 
// credentials shoud be passed in `Properties`. Here it is just for example.

참고: JDBC URL이나 속성을 URL로 인코딩할 필요가 없습니다. 자동으로 인코딩됩니다.

클라이언트 식별

요청을 발생시킨 애플리케이션을 식별하는 방법은 두 가지가 있습니다: 연결 속성을 통해 com.clickhouse.client.api.ClientConfigProperties#CLIENT_NAME을 설정하거나 java.sql.Connection#setClientInfo(String name, String value) 메서드를 사용합니다.

Properties properties = new Properties();
properties.setProperty(ClientConfigProperties.CLIENT_NAME.getKey(), "my-app-01");
Connection conn = Driver.connect("jdbc:ch:http://localhost:8123/", properties);
conn.setClientInfo(com.clickhouse.jdbc.ClientInfoProperties.APPLICATION_NAME.getKey(), "my-app-01");

두 방법 모두 쿼리 로그에 다음과 같은 http_user_agent 값이 기록됩니다:

my-app-01/1.0 clickhouse-java-v2/0.9.6-SNAPSHOT (Linux; jvm:17.0.17) Apache-HttpClient/5.4.4

작업 식별

JDBC 드라이버는 각 작업마다 query_id를 생성하며, 현재 이 값은 서버 예외에 포함됩니다.

작업의 log_comment 값을 설정하려면 com.clickhouse.jdbc.StatementImpl#getLocalSettings 메서드를 사용하십시오. 이를 위해서는 Statement 또는 PreparedStatement를 먼저 com.clickhouse.jdbc.StatementImpl로 캐스팅해야 합니다.

StatementImpl stmt = (StatementImpl) conn.createStatement();
stmt.getLocalSettings().logComment("some-comment");

참고: localSettings는 스레드 간에 공유되므로 이 방식은 단일 스레드에서 statement를 사용하는 경우에만 적합합니다.

지원되는 데이터 유형

JDBC Driver는 기반 java client와 동일한 데이터 형식을 지원합니다.

JDBC 타입 매핑

다음 매핑은 다음에 적용됩니다:

  • ResultSet#getObject(columnIndex) 메서드는 해당 Java 클래스의 객체를 반환합니다. (Int8 -> java.lang.Byte, Int16 -> java.lang.Short 등)
  • ResultSetMetaData#getColumnType(columnIndex) 메서드는 해당하는 JDBC 타입을 반환합니다. (Int8 -> java.lang.Byte, Int16 -> java.lang.Short 등)

매핑을 변경할 수 있는 방법은 다음과 같습니다:

  • ResultSet#getObject(columnIndex, class) 메서드는 값을 지정된 class 타입으로 변환하려고 시도합니다. 일부 변환에는 제한 사항이 있습니다. 자세한 내용은 각 섹션을 참조하십시오.

숫자형 타입(Numeric Types)

ClickHouse 타입JDBC 타입Java 클래스
Int8TINYINTjava.lang.Byte
Int16SMALLINTjava.lang.Short
Int32INTEGERjava.lang.Integer
Int64BIGINTjava.lang.Long
Int128OTHERjava.math.BigInteger
Int256OTHERjava.math.BigInteger
UInt8OTHERjava.lang.Short
UInt16OTHERjava.lang.Integer
UInt32OTHERjava.lang.Long
UInt64OTHERjava.math.BigInteger
UInt128OTHERjava.math.BigInteger
UInt256OTHERjava.math.BigInteger
Float32REALjava.lang.Float
Float64DOUBLEjava.lang.Double
Decimal32DECIMALjava.math.BigDecimal
Decimal64DECIMALjava.math.BigDecimal
Decimal128DECIMALjava.math.BigDecimal
Decimal256DECIMALjava.math.BigDecimal
BoolBOOLEANjava.lang.Boolean
  • 숫자형 타입끼리는 상호 변환이 가능합니다. 따라서 Int8 값을 Float64로 읽을 수 있고, 그 반대도 가능합니다.:
    • rs.getObject(1, Float64.class)Int8 컬럼의 Float64 값을 반환합니다.
    • rs.getLong(1)Int8 컬럼의 Long 값을 반환합니다.
    • rs.getByte(1)Int16 컬럼의 값이 Byte 값의 범위 안에 들어가면 Byte 값으로 반환할 수 있습니다.
  • 더 넓은 범위의 타입을 더 좁은 범위의 타입으로 변환하는 것은 데이터 손상 위험 때문에 권장되지 않습니다.
  • Bool 타입은 숫자형처럼도 동작합니다.
  • 모든 숫자형 타입은 java.lang.String으로도 읽을 수 있습니다.
  • Java Float.MAX_VALUEFloat로 저장하면 문제가 있습니다(https://github.com/ClickHouse/clickhouse-java/issues/809). 같은 값을 Double로 저장하면 해당 문제가 해결됩니다.

문자열 타입(String Types)

ClickHouse 타입JDBC 타입Java 클래스
StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.String
  • Stringjava.lang.String 또는 byte[]로만 읽을 수 있습니다.
  • FixedString은 저장된 값 그대로 읽히며, 컬럼 길이에 맞게 널 바이트(\0)로 패딩됩니다. (예를 들어 FixedString(10) 컬럼에 'John'이 저장된 경우 'John\0\0\0\0\0\0\0\0\0'으로 읽힙니다.)

Enum 유형

ClickHouse 타입JDBC 타입Java 클래스
Enum8OTHERjava.lang.String
Enum16OTHERjava.lang.String
  • Enum8Enum16은 기본적으로 java.lang.String에 매핑됩니다.
  • Enum 값은 전용 getter 메서드나 getObject(columnIndex, Integer.class) 메서드를 사용하여 숫자 값으로 읽을 수 있습니다.
  • Enum16은 내부적으로 short 타입에, Enum8byte 타입에 매핑됩니다. 데이터가 손상될 위험이 있으므로 Enum16byte로 읽는 것은 피해야 합니다.
  • Enum 값은 PreparedStatement에서 문자열 값 또는 숫자 값으로 설정할 수 있습니다.

날짜/시간 타입(Date/Time Types)

ClickHouse 타입JDBC 타입Java 클래스
DateDATEjava.sql.Date
Date32DATEjava.sql.Date
DateTimeTIMESTAMPjava.sql.Timestamp
DateTime64TIMESTAMPjava.sql.Timestamp
TimeTIMEjava.sql.Time
Time64TIMEjava.sql.Time
  • 날짜/시간 타입은 JDBC와의 호환성을 높이기 위해 java.sql 타입으로 매핑됩니다. 그러나 ResultSet#getObject(columnIndex, Class<T>) 메서드를 사용하여 두 번째 인자로 해당하는 클래스를 전달하면 java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime을(를) 반환받을 수 있습니다.
    • rs.getObject(1, java.time.LocalDate.class)Date 컬럼의 java.time.LocalDate 값을 반환합니다.
    • rs.getObject(1, java.time.LocalDateTime.class)DateTime 컬럼의 java.time.LocalDateTime 값을 반환합니다.
    • rs.getObject(1, java.time.LocalTime.class)Time 컬럼의 java.time.LocalTime 값을 반환합니다.
  • Date, Date32, Time, Time64는 서버 시간대의 영향을 받지 않습니다.
  • DateTime, DateTime64는 서버 시간대나 세션 시간대의 영향을 받습니다.
  • DateTimeDateTime64 값은 getObject(colIndex, ZonedDateTime.class)를 사용하여 ZonedDateTime으로 가져올 수 있습니다.

중첩 타입(Nested Types)

ClickHouse 타입JDBC 타입Java 클래스
ArrayARRAYjava.sql.Array
TupleOTHERcom.clickhouse.data.Tuple
MapJAVA_OBJECTjava.util.Map
NestedARRAYjava.sql.Array
  • Array는 JDBC와의 호환성을 위해 기본적으로 java.sql.Array에 매핑됩니다. 이렇게 하면 반환되는 배열 값에 대한 정보를 더 자세히 제공할 수 있어 타입 추론에 유용합니다.
  • Array는 원본 배열과 동일한 내용을 담은 java.sql.ResultSet을 반환하는 getResultSet() 메서드를 구현합니다.
  • 컬렉션 타입은 데이터를 올바르게 표현하는 방식이 아니므로 java.lang.String으로 읽어서는 안 됩니다(예: 배열에서는 문자열 값이 따옴표로 둘러싸여 있지 않습니다).
  • Map 타입은 값을 getObject(columnIndex, Class<T>) 메서드를 통해서만 읽을 수 있기 때문에 JAVA_OBJECT에 매핑됩니다.
    • Map에는 이름이 붙은 컬럼이 없으므로 java.sql.Struct가 아닙니다.
  • Tuple은 서로 다른 타입을 포함할 수 있으므로 Object[]에 매핑되며, List를 사용하는 것은 지원되지 않습니다.
  • TuplegetObject(columnIndex, Array.class) 메서드를 사용하여 Array로 읽을 수 있습니다. 이 경우 Array#baseTypeNameTuple 컬럼 정의를 반환합니다.

배열 쓰기

java.sql.Connection#createArrayOf를 사용하여 java.sql.Array 객체를 생성합니다. 이 객체는 서로 다른 데이터베이스에서 배열 처리를 일관되게 수행할 수 있도록 설계되었습니다. Array 팩토리 메서드에 설정을 전달하려면 Connection이 필요합니다.

이 메서드는 두 개의 인수를 받습니다:

  • typeName - 배열 요소 타입의 이름입니다. 예를 들어 Array(Int32) -> "Int32"입니다.
  • elements - 배열의 실제 요소입니다. 예를 들어 [[1, 2, 3], [4, 5, 6]] -> new Integer[][] {{1, 2, 3}, {4, 5, 6}}.

Tuple은 Object[] 또는 java.sql.Struct로 표현할 수 있습니다(튜플 작성 방법은 아래를 참조하십시오).

예제

try (Connection conn = ...) {
    Array array = conn.createArrayOf("Int32", new Integer[][] {{1, 2, 3}, {4, 5, 6}});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (arr) VALUES (?)")) {
        ps.setArray(1, array);
        ps.executeUpdate();
    }
}

배열 읽기

Array 객체를 읽으려면 ResultSet#getArray(columnIndex)를 사용합니다. 이 객체를 사용하면 임의의 중첩 깊이를 가진 배열에도 접근할 수 있습니다. Array#getResultSet() 메서드는 배열 요소를 java.sql.ResultSet과 유사한 보다 일관된 방식으로 읽을 때 사용할 수 있습니다. 배열 요소의 정확한 타입을 알지 못할 때 유용합니다.

예제

try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Array(Int32)")) {
        ps.setArray(1, array);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Array array = rs.getArray(1);

                Object[] arr = (Object[]) array;
                Arrays.stream(arr).forEach(this::handleArrayElement);

                // or by using `ResultSet`
                ResultSet resultSet = array.getResultSet();
                while (resultSet.next()) {
                    // ...
                }
            }
        }
    } 
}

튜플 쓰기

튜플은 com.clickhouse.data.Tuple 객체에 매핑되며 setObject(columnIndex, tuple) 메서드를 호출하여 이 객체로 작성해야 합니다. 이식성을 높이기 위해 java.sql.Struct 객체를 사용하여 튜플을 작성할 수도 있습니다.

예제

try (Connection conn = ...) {
    Tuple tuple = new Tuple(1, "test", LocalDate.parse("2026-03-02"));
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) {
        ps.setObject(1, tuple);
        ps.executeUpdate();
    }
}

try (Connection conn = ...) {
    Struct struct = conn.createStruct("Tuple(Int32, String, Date)", new Object[] {1, "test", LocalDate.parse("2026-03-02")});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (tuple) VALUES (?)")) {
        ps.setStruct(1, struct);
        ps.executeUpdate();
    }
}

튜플 읽기

getObject(columnIndex) 메서드는 Object[]를 반환합니다. TuplegetObject(columnIndex, Array.class) 메서드를 사용하면 java.sql.Array로 읽을 수 있습니다.

예제

try (Connection conn = ...) {
    try (PreparedStatement stmt = conn.prepareStatement("SELECT ?::Tuple(String, Int32, Date)")) {
        Array tuple = conn.createArrayOf("Tuple(String, Int32, Date)",  new Object[]{"test", 123, LocalDate.parse("2026-03-02")});
        stmt.setObject(1, tuple);
        try (ResultSet rs = stmt.executeQuery()) {
            rs.next();
            Array dbTuple = rs.getArray(1);
            Assert.assertEquals(dbTuple, tuple);
            Object arr = rs.getObject(1);
            Assert.assertEquals(arr, tuple.getArray());
        }
    }
}

맵 쓰기

맵은 key-value 쌍을 필요로 하는 타입이므로 java.collections.Map 객체로만 쓸 수 있습니다. java.sql.Struct는 key-value 쌍을 지원하지 않습니다.

예제

try (Connection conn = ...) {
    Map<String, Integer> map = new HashMap<>();
    map.put("key1", 1);
    map.put("key2", 2);
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (map) VALUES (?)")) {
        ps.setObject(1, map);
        ps.executeUpdate();
    }
}

맵 읽기

맵은 getObject(columnIndex, Map.class) 메서드를 사용하여 java.collections.Map 객체로 읽을 수 있습니다.

예제

try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Map(String, Int32)")) {
        ps.setStruct(1, struct);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Map<String, Integer> map = rs.getObject(1, Map.class);
                // ...
            }
        }
    }
}

중첩 데이터 쓰기

java.sql.Connection#createStruct를 사용하여 java.sql.Struct 객체를 인스턴스화하십시오. 이 객체는 서로 다른 데이터베이스에서 중첩 처리를 통일하기 위해 설계되었습니다. Struct 팩토리 메서드에 구성을 전달하려면 Connection이 필요합니다.

이 메서드는 두 개의 인수를 받습니다:

  • typeName - 중첩 요소의 타입 이름입니다. 예를 들어 Nested(Tuple(Int32, String)) -> "Nested(Tuple(Int32, String))"입니다.
  • elements - 실제 중첩된 요소입니다. 예를 들어 [1, 'test'] -> new Object[] {1, 'test'}입니다.

예제

try (Connection conn = ...) {
    Struct struct = conn.createStruct("Nested(Tuple(Int32, String))", new Object[] {1, 'test'});
    try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable (nested) VALUES (?)")) {
        ps.setStruct(1, struct);
        ps.executeUpdate();
    }
}

중첩 데이터 읽기

Nested 객체를 읽기 위해서는 ResultSet#getStruct(columnIndex, StructDescriptor)를 사용합니다. 이 객체를 사용하면 중첩 수준에 상관없이 임의의 깊이의 중첩 구조에 접근할 수 있습니다. Struct#getResultSet() 메서드는 java.sql.ResultSet과 유사한 보다 일관된 방식으로 중첩 요소를 읽는 데 사용할 수 있습니다. 중첩 요소의 정확한 타입을 알 수 없을 때 유용합니다.

예제

try (Connection conn = ...) {
    try (PreparedStatement ps = conn.prepareStatement("SELECT ?::Nested(Tuple(Int32, String))")) {
        ps.setStruct(1, struct);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                Struct struct = rs.getStruct(1);
                Object[] tuple = (Object[]) struct;
                Arrays.stream(tuple).forEach(this::handleTupleElement);

                // or by using `ResultSet`
                ResultSet resultSet = struct.getResultSet();
                while (resultSet.next()) {
                    // ...
                }
            }
        }
    }
}

지리 타입(Geo Types)

ClickHouse 타입JDBC 타입Java 클래스
PointOTHERdouble[]
RingOTHERdouble[][]
PolygonOTHERdouble[][][]
MultiPolygonOTHERdouble[][][][]

널 허용(Nullable) 및 LowCardinality 타입

  • Nullable(널 허용) 및 LowCardinality는 다른 타입을 감싸는 특수한 데이터 타입입니다.
  • Nullable(널 허용)은 ResultSetMetaData에서 타입 이름 반환 방식에 영향을 미칩니다.

특수 유형

ClickHouse 타입JDBC 타입Java 클래스
UUIDOTHERjava.util.UUID
IPv4OTHERjava.net.Inet4Address
IPv6OTHERjava.net.Inet6Address
JSONOTHERjava.lang.String
AggregateFunctionOTHER(바이너리 표현)
SimpleAggregateFunction(래핑된 타입)(래핑된 클래스)
  • UUID는 JDBC 표준 타입은 아니지만 JDK에는 포함된 타입입니다. 기본적으로 getObject() 메서드는 java.util.UUID를 반환합니다.
  • getObject(columnIndex, String.class) 메서드를 사용하면 UUIDString 형식으로 읽고 쓸 수 있습니다.
  • IPv4IPv6는 JDBC 표준 타입이 아니지만 JDK에는 포함되어 있습니다. 기본적으로 getObject() 메서드에서 java.net.Inet4Addressjava.net.Inet6Address가 반환됩니다.
  • IPv4IPv6getObject(columnIndex, String.class) 메서드를 사용하면 String 형식으로 읽고 쓸 수 있습니다.

날짜, 시간 및 시간대 처리하기

Date/Time 및 Timestamp를 처리할 때 자주 발생하는 문제와 드라이버의 동작 로직을 설명하는 Date/Time 가이드를 참조하십시오.

연결 생성하기

String url = "jdbc:ch://my-server:8123/system";

Properties properties = new Properties();
DataSource dataSource = new DataSource(url, properties);//DataSource or DriverManager are the main entry points
try (Connection conn = dataSource.getConnection()) {
... // do something with the connection

자격 증명 및 설정 제공

String url = "jdbc:ch://localhost:8123?jdbc_ignore_unsupported_values=true&socket_timeout=10";

Properties info = new Properties();
info.put("user", "default");
info.put("password", "password");
info.put("database", "some_db");

//Creating a connection with DataSource
DataSource dataSource = new DataSource(url, info);
try (Connection conn = dataSource.getConnection()) {
... // do something with the connection
}

//Alternate approach using the DriverManager
try (Connection conn = DriverManager.getConnection(url, info)) {
... // do something with the connection
}

단순 문(Simple Statement)


try (Connection conn = dataSource.getConnection(...);
    Statement stmt = conn.createStatement()) {
    ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
    while(rs.next()) {
        // ...
    }
}

삽입

try (PreparedStatement ps = conn.prepareStatement("INSERT INTO mytable VALUES (?, ?)")) {
    ps.setString(1, "test"); // id
    ps.setObject(2, LocalDateTime.now()); // timestamp
    ps.addBatch();
    ...
    ps.executeBatch(); // stream everything on-hand into ClickHouse
}

HikariCP

// connection pooling won't help much in terms of performance,
// because the underlying implementation has its own pool.
// for example: HttpURLConnection has a pool for sockets
HikariConfig poolConfig = new HikariConfig();
poolConfig.setConnectionTimeout(5000L);
poolConfig.setMaximumPoolSize(20);
poolConfig.setMaxLifetime(300_000L);
poolConfig.setDataSource(new ClickHouseDataSource(url, properties));

try (HikariDataSource ds = new HikariDataSource(poolConfig);
     Connection conn = ds.getConnection();
     Statement s = conn.createStatement();
     ResultSet rs = s.executeQuery("SELECT * FROM system.numbers LIMIT 3")) {
    while (rs.next()) {
        // handle row
        log.info("Integer: {}, String: {}", rs.getInt(1), rs.getString(1));//Same column but different types
    }
}

추가 정보

자세한 내용은 GitHub 저장소Java 클라이언트 문서를 참조하세요.

문제 해결

로깅

드라이버는 로깅을 위해 slf4j를 사용하며, classpath에서 사용 가능한 첫 번째 구현체를 사용합니다.

대용량 삽입 시 JDBC 타임아웃 해결하기

ClickHouse에서 실행 시간이 긴 대용량 삽입 작업을 수행하는 경우 다음과 같은 JDBC 타임아웃 오류가 발생할 수 있습니다:

Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]

이러한 오류는 데이터 삽입 작업을 방해하고 시스템 안정성에 영향을 줄 수 있습니다. 이 문제를 해결하려면 클라이언트 OS의 타임아웃 설정 몇 가지를 조정해야 할 수도 있습니다.

Mac OS

Mac OS에서 다음 설정을 조정하여 문제를 해결할 수 있습니다:

  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1

Linux

Linux에서는 동일한 설정만으로는 문제가 해결되지 않을 수 있습니다. Linux에서 소켓 keep-alive 설정을 처리하는 방식이 다르기 때문에 추가 단계가 필요합니다. 다음 단계를 수행하세요:

  1. /etc/sysctl.conf 또는 관련 구성 파일에서 다음 Linux 커널 파라미터를 조정하십시오:
  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1
  • net.ipv4.tcp_keepalive_intvl: 75
  • net.ipv4.tcp_keepalive_probes: 9
  • net.ipv4.tcp_keepalive_time: 60 (기본값인 300초보다 더 낮은 값으로 설정하는 것을 고려할 수 있습니다)
  1. 커널 매개변수를 변경한 후, 다음 명령을 실행하여 변경 사항을 적용하십시오:
sudo sysctl -p

해당 설정을 완료한 후, 클라이언트가 소켓에서 Keep Alive 옵션을 활성화하는지 확인하십시오:

properties.setProperty("socket_keepalive", "true");

마이그레이션 가이드

주요 변경 사항

기능V1(이전 버전)V2(새 버전)
트랜잭션 지원부분적으로 지원됨지원되지 않음
응답 컬럼 이름 변경부분적으로 지원됨지원되지 않음
다중 문장 SQL(Multi-Statement SQL)지원되지 않음허용되지 않음
명명된 매개변수지원됨지원되지 않음(JDBC 사양에 없음)
PreparedStatement를 통한 데이터 스트리밍지원됨지원되지 않음
  • JDBC V2는 보다 경량화되도록 구현되었으며, 그 과정에서 일부 기능이 제거되었습니다.
    • 스트리밍 데이터 기능은 JDBC 사양과 Java 표준의 일부가 아니기 때문에 JDBC V2에서는 지원되지 않습니다.
  • JDBC V2는 명시적인 구성 설정이 필요하며, 장애 조치(failover)를 위한 기본값은 제공되지 않습니다.
    • URL에서 프로토콜을 명시해야 합니다. 포트 번호에 따른 암시적 프로토콜 감지는 지원하지 않습니다.

구성 변경 사항

열거형(enum)은 두 개만 있습니다:

  • com.clickhouse.jdbc.DriverProperties - 드라이버 고유의 구성 속성입니다.
  • com.clickhouse.client.api.ClientConfigProperties - 클라이언트 구성 속성입니다. 클라이언트 구성 변경 사항은 Java 클라이언트 문서에 설명되어 있습니다.

연결 속성은 다음과 같이 파싱됩니다:

  • 먼저 URL에서 속성을 파싱합니다. 이렇게 파싱된 속성은 다른 모든 속성 설정보다 우선 적용됩니다.
  • 드라이버 속성은 클라이언트에 전달되지 않습니다.
  • 엔드포인트(호스트, 포트, 프로토콜)는 URL에서 추출됩니다.

Example:

String url = "jdbc:ch://my-server:8443/default?" +
            "jdbc_ignore_unsupported_values=true&" +
            "socket_rcvbuf=800000";

Properties properties = new Properties();
properties.setProperty("socket_rcvbuf", "900000");
try (Connection conn = DriverManager.getConnection(url, properties)) {
    // Connection will use socket_rcvbuf=800000 and jdbc_ignore_unsupported_values=true
    // Endpoints: my-server:8443 protocol: http (not secure)
    // Database: default
}

데이터 유형 변경 사항

숫자형 타입(Numeric Types)

ClickHouse 타입V1과 호환 여부JDBC 타입 (V2)Java 클래스 (V2)JDBC 타입 (V1)Java 클래스 (V1)
Int8TINYINTjava.lang.ByteTINYINTjava.lang.Byte
Int16SMALLINTjava.lang.ShortSMALLINTjava.lang.Short
Int32INTEGERjava.lang.IntegerINTEGERjava.lang.Integer
Int64BIGINTjava.lang.LongBIGINTjava.lang.Long
Int128OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
Int256OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
UInt8OTHERjava.lang.ShortOTHERcom.clickhouse.data.value.UnsignedByte
UInt16OTHERjava.lang.IntegerOTHERcom.clickhouse.data.value.UnsignedShort
UInt32OTHERjava.lang.LongOTHERcom.clickhouse.data.value.UnsignedInteger
UInt64OTHERjava.math.BigIntegerOTHERcom.clickhouse.data.value.UnsignedLong
UInt128OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
UInt256OTHERjava.math.BigIntegerOTHERjava.math.BigInteger
Float32REALjava.lang.FloatREALjava.lang.Float
Float64DOUBLEjava.lang.DoubleDOUBLEjava.lang.Double
Decimal32DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
Decimal64DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
Decimal128DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
Decimal256DECIMALjava.math.BigDecimalDECIMALjava.math.BigDecimal
BoolBOOLEANjava.lang.BooleanBOOLEANjava.lang.Boolean
  • 가장 큰 차이점은 부호 없는 타입이 이식성을 높이기 위해 Java 타입에 매핑된다는 점입니다.

문자열 타입(String Types)

ClickHouse 타입V1과 호환 여부JDBC 타입(V2)Java 클래스(V2)JDBC 타입(V1)Java 클래스(V1)
StringVARCHARjava.lang.StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.StringVARCHARjava.lang.String
  • FixedString은 두 버전 모두에서 값이 있는 그대로 읽힙니다. 예를 들어 'John'이 저장된 FixedString(10)'John\0\0\0\0\0\0\0\0\0'으로 읽힙니다.
  • PreparedStatement#setBytes를 사용하면 값이 unhex('<hex_string>')로 변환된 뒤 String으로 처리됩니다.
  • 문자열은 UTF-8 인코딩으로 저장됩니다.

날짜/시간 타입(Date/Time Types)

ClickHouse 타입V1과 호환 여부JDBC 타입 (V2)Java 클래스 (V2)JDBC 타입 (V1)Java 클래스 (V1)
DateDATEjava.sql.DateDATEjava.time.LocalDate
Date32DATEjava.sql.DateDATEjava.time.LocalDate
DateTimeTIMESTAMPjava.sql.TimestampTIMESTAMPjava.time.OffsetDateTime
DateTime64TIMESTAMPjava.sql.TimestampTIMESTAMPjava.time.OffsetDateTime
TimeTIMEjava.sql.Time새 타입 / 지원되지 않음새 타입 / 지원되지 않음
Time64TIMEjava.sql.Time새 타입/지원되지 않음새 타입/지원되지 않음
  • TimeTime64는 새로 도입된 타입으로 V2에서만 지원됩니다.
  • DateTimeDateTime64는 JDBC와의 호환성을 높이기 위해 java.sql.Timestamp에 매핑됩니다.

Enum 유형

ClickHouse 타입V1과 호환 여부JDBC 타입 (V2)Java 클래스 (V2)JDBC 타입 (V1)Java 클래스 (V1)
EnumVARCHARjava.lang.StringOTHERjava.lang.String
Enum8VARCHARjava.lang.StringOTHERjava.lang.String
Enum16VARCHARjava.lang.StringOTHERjava.lang.String

중첩 타입(Nested Types)

ClickHouse 타입V1과 호환 여부JDBC 타입(V2)Java 클래스(V2)JDBC 타입(V1)Java 클래스(V1)
ArrayARRAYjava.sql.ArrayARRAYObject[] 또는 기본 타입 배열
TupleOTHERObject[]STRUCTjava.sql.Struct
MapJAVA_OBJECTjava.util.MapSTRUCTjava.util.Map
NestedARRAYjava.sql.ArraySTRUCTjava.sql.Struct
  • In V2에서는 JDBC와의 호환성을 위해 Array가 기본적으로 java.sql.Array에 매핑됩니다. 이는 반환되는 배열 값에 대한 정보를 더 자세히 제공하기 위한 것으로, 타입 추론에 유용합니다.
  • V2에서는 ArraygetResultSet() 메서드를 구현하여 원본 배열과 동일한 내용을 가진 java.sql.ResultSet을 반환합니다.
  • V1은 Map에 대해 STRUCT를 사용하지만 항상 java.util.Map 객체를 반환합니다. V2에서는 MapJAVA_OBJECT에 매핑하여 이 문제를 해결합니다.
  • V1은 Tuple에 대해 STRUCT를 사용하지만 항상 List<Object> 객체를 반환합니다. V2는 TupleOTHER에 매핑하여 기본적으로 Object[]를 반환합니다.
  • V2에서는 튜플을 쓰기 위해 com.clickhouse.data.Tuple#Tuple을 도입합니다. 이를 통해 값이 튜플인지 배열인지 여부를 더 쉽게 판별할 수 있습니다.
  • PreparedStatement#setBytesResultSet#getBytes는 컬렉션 타입에는 사용할 수 없습니다. 이 메서드는 바이너리 문자열을 처리하도록 설계되었습니다.
  • 일반적으로 Array 타입을 읽고 쓸 때에는 java.sql.Array를 사용합니다. JDBC 드라이버에서 이에 대한 지원을 완비하고 있습니다.
  • V2에서 NestedArray에 매핑되며 튜플 배열 형태로 표현됩니다.
  • V2에서는 java.sql.Struct가 Array 타입과 매우 유사하고 key-value 쌍을 지원하지 않기 때문에 부분적으로만 지원합니다. StructTuple 값을 쓰는 데 사용할 수 있습니다.

지리 타입(Geo Types)

ClickHouse 타입V1과 호환JDBC 타입 (V2)Java 클래스 (V2)JDBC 타입 (V1)Java 클래스 (V1)
PointOTHERdouble[]OTHERdouble[]
RingOTHERdouble[][]OTHERdouble[][]
PolygonOTHERdouble[][][]OTHERdouble[][][]
MultiPolygonOTHERdouble[][][][]OTHERdouble[][][][]

널 허용 및 LowCardinality 타입

  • Nullable(널 허용) 및 LowCardinality는 다른 타입을 감싸서 사용하는 특수한 데이터 타입입니다.
  • V2에서는 이러한 타입에 대한 변경 사항이 없습니다.

특수 유형

ClickHouse 타입V1 호환 여부JDBC 타입 (V2)Java 클래스 (V2)JDBC 타입 (V1)Java 클래스 (V1)
JSONOTHERjava.lang.String지원되지 않음지원되지 않음
AggregateFunctionOTHER(이진 표현)OTHER(이진 표현)
SimpleAggregateFunction(래핑된 타입)(래핑된 클래스)(래핑된 타입)(래핑된 클래스)
UUIDOTHERjava.util.UUIDVARCHARjava.util.UUID
IPv4OTHERjava.net.Inet4AddressVARCHARjava.net.Inet4Address
IPv6OTHERjava.net.Inet6AddressVARCHARjava.net.Inet6Address
DynamicOTHERjava.Object지원되지 않음지원되지 않음
VariantOTHERjava.Object지원되지 않음지원되지 않음
  • V1은 UUID 타입에 대해 JDBC 타입으로 VARCHAR를 사용하지만 항상 java.util.UUID 객체를 반환합니다. V2에서는 이를 수정하여 UUIDOTHER에 매핑하고 java.util.UUID 객체를 반환합니다.
  • V1에서는 IPv4IPv6에 대해 JDBC 타입으로 VARCHAR를 사용하지만, 항상 java.net.Inet4Addressjava.net.Inet6Address 객체를 반환합니다. V2에서는 이를 수정하여 IPv4IPv6을 JDBC 타입 OTHER로 매핑하여 java.net.Inet4Addressjava.net.Inet6Address 객체를 반환합니다.
  • DynamicVariant는 V2에서 새로 추가된 타입입니다. V1에서는 지원되지 않습니다.
  • JSONDynamic 타입을 기반으로 하므로 V2에서만 지원됩니다.
  • IPv4 및 IPv6 값은 getBytes(columnIndex) 메서드를 사용하여 byte[]로 읽을 수 있습니다. 그러나 이러한 타입에는 지정된 클래스를 사용하는 것이 권장됩니다.
  • V2에서는 IP 주소를 숫자 형태로 직접 읽는 기능을 지원하지 않습니다. 숫자 값으로의 변환은 구현 측면에서 InetAddress 클래스에서 처리하는 것이 더 적절하기 때문입니다.

데이터베이스 메타데이터 변경 사항

  • V2에서는 데이터베이스를 나타내는 용어로 Schema만 사용합니다. Catalog라는 용어는 향후 사용을 위해 예약되어 있습니다.
  • V2는 DatabaseMetaData.supportsTransactions()DatabaseMetaData.supportsSavepoints()에 대해 false를 반환합니다. 이는 향후 개발에서 변경될 예정입니다.

clickhouse-jdbc는 표준 JDBC 인터페이스를 구현합니다. clickhouse-client 위에 구축되어 사용자 정의 타입 매핑, 트랜잭션 지원, 표준 동기식 UPDATEDELETE 문 등과 같은 추가 기능을 제공하므로 레거시 애플리케이션 및 도구에서 쉽게 사용할 수 있습니다.

참고

최신 JDBC(0.7.2) 버전은 Client-V1을 사용합니다.

clickhouse-jdbc API는 동기식이며, 일반적으로 더 많은 오버헤드(예: SQL 파싱 및 타입 매핑/변환 등)가 발생합니다. 성능이 매우 중요하거나 ClickHouse에 보다 직접적으로 액세스하기를 원하는 경우 clickhouse-client를 사용하는 방안을 고려하십시오.

환경 요구 사항

설정

<!-- https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc -->
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.7.2</version>
    <!-- 모든 의존성이 포함된 uber JAR를 사용하고, 더 작은 JAR가 필요하면 classifier를 http로 변경하십시오 -->
    <classifier>shaded-all</classifier>
</dependency>

버전 0.5.0부터 클라이언트에 포함된 Apache HTTP Client를 사용합니다. 패키지의 공유 버전이 없기 때문에 로거를 의존성으로 추가해야 합니다.

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.16</version>
</dependency>

구성

드라이버 클래스: com.clickhouse.jdbc.ClickHouseDriver

URL 구문: jdbc:(ch|clickhouse)[:<protocol>]://endpoint1[,endpoint2,...][/<database>][?param1=value1&param2=value2][#tag1,tag2,...], 예를 들면:

  • jdbc:ch://localhostjdbc:clickhouse:http://localhost:8123와 동일합니다.
  • jdbc:ch:https://localhostjdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICT와 동일합니다.
  • jdbc:ch:grpc://localhostjdbc:clickhouse:grpc://localhost:9100와 동일합니다.

연결 속성(Connection Properties):

속성기본값설명
continueBatchOnErrorfalse오류가 발생했을 때 배치 처리를 계속할지 여부
createDatabaseIfNotExistfalse데이터베이스가 존재하지 않을 경우 생성할지 여부
custom_http_headers쉼표로 구분된 사용자 정의 HTTP 헤더(예: User-Agent=client1,X-Gateway-Id=123)
custom_http_params쉼표로 구분된 사용자 정의 HTTP 쿼리 매개변수. 예: extremes=0,max_result_rows=100
nullAsDefault00 - null 값을 그대로 처리하며, 널 허용이 아닌 컬럼에 null을 삽입하려 할 경우 예외를 발생시킵니다; 1 - null 값을 그대로 처리하며, 삽입 시 null 검사(null-check)를 비활성화합니다; 2 - 쿼리와 삽입 모두에서 null 값을 해당 데이터 타입의 기본값으로 대체합니다
jdbcCompliancetrue표준 동기식 UPDATE/DELETE 및 가상 트랜잭션(fake transaction)을 지원할지 여부
typeMappingsClickHouse 데이터 타입과 Java 클래스 간의 매핑을 사용자 정의하며, 이 설정은 getColumnType()getObject(Class<>?>)의 결과 모두에 영향을 미칩니다. 예를 들어, UInt128=java.lang.String,UInt256=java.lang.String처럼 설정할 수 있습니다.
wrapperObjectfalsegetObject()가 Array / Tuple 타입에 대해 java.sql.Array / java.sql.Struct를 반환해야 하는지 여부.

참고: 자세한 내용은 JDBC 관련 설정을 참조하세요.

지원되는 데이터 유형

JDBC 드라이버는 클라이언트 라이브러리와 동일한 데이터 형식을 지원합니다.

참고
  • AggregatedFunction - ⚠️ SELECT * FROM table ... 구문을 지원하지 않습니다
  • Decimal - 일관성을 위해 21.9+에서는 SET output_format_decimal_trailing_zeros=1을 설정합니다
  • Enum - 문자열 및 정수형 값으로 모두 취급할 수 있습니다
  • UInt64 - client-v1에서는 long 타입으로 매핑됩니다

연결 생성하기

String url = "jdbc:ch://my-server/system"; // use http protocol and port 8123 by default

Properties properties = new Properties();

ClickHouseDataSource dataSource = new ClickHouseDataSource(url, properties);
try (Connection conn = dataSource.getConnection("default", "password");
    Statement stmt = conn.createStatement()) {
}

단순 문(Simple Statement)


try (Connection conn = dataSource.getConnection(...);
    Statement stmt = conn.createStatement()) {
    ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
    while(rs.next()) {
        // ...
    }
}

삽입

참고
  • Statement 대신 PreparedStatement를 사용하십시오

사용하기는 더 쉽지만 아래에 설명된 input 함수에 비해 성능이 떨어집니다:

try (PreparedStatement ps = conn.prepareStatement("insert into mytable(* except (description))")) {
    ps.setString(1, "test"); // id
    ps.setObject(2, LocalDateTime.now()); // timestamp
    ps.addBatch(); // parameters will be write into buffered stream immediately in binary format
    ...
    ps.executeBatch(); // stream everything on-hand into ClickHouse
}

input 테이블 함수 사용

성능이 우수한 옵션:

try (PreparedStatement ps = conn.prepareStatement(
    "insert into mytable select col1, col2 from input('col1 String, col2 DateTime64(3), col3 Int32')")) {
    // The column definition will be parsed so the driver knows there are 3 parameters: col1, col2 and col3
    ps.setString(1, "test"); // col1
    ps.setObject(2, LocalDateTime.now()); // col2, setTimestamp is slow and not recommended
    ps.setInt(3, 123); // col3
    ps.addBatch(); // parameters will be write into buffered stream immediately in binary format
    ...
    ps.executeBatch(); // stream everything on-hand into ClickHouse
}

플레이스홀더를 사용한 삽입

이 옵션은 긴 SQL 표현식을 사용해야 하고 해당 표현식이 클라이언트 측에서 파싱되어 CPU와 메모리를 많이 소모하므로 소규모 삽입 작업에만 권장됩니다:

try (PreparedStatement ps = conn.prepareStatement("insert into mytable values(trim(?),?,?)")) {
    ps.setString(1, "test"); // id
    ps.setObject(2, LocalDateTime.now()); // timestamp
    ps.setString(3, null); // description
    ps.addBatch(); // append parameters to the query
    ...
    ps.executeBatch(); // issue the composed query: insert into mytable values(...)(...)...(...)
}

DateTime 및 시간대 처리

java.sql.Timestamp 대신 java.time.LocalDateTime 또는 java.time.OffsetDateTime을 사용하고, java.sql.Date 대신 java.time.LocalDate를 사용하세요.

try (PreparedStatement ps = conn.prepareStatement("select date_time from mytable where date_time > ?")) {
    ps.setObject(2, LocalDateTime.now());
    ResultSet rs = ps.executeQuery();
    while(rs.next()) {
        LocalDateTime dateTime = (LocalDateTime) rs.getObject(1);
    }
    ...
}

AggregateFunction 처리

참고

현재 groupBitmap만 지원됩니다.

// batch insert using input function
try (ClickHouseConnection conn = newConnection(props);
        Statement s = conn.createStatement();
        PreparedStatement stmt = conn.prepareStatement(
                "insert into test_batch_input select id, name, value from input('id Int32, name Nullable(String), desc Nullable(String), value AggregateFunction(groupBitmap, UInt32)')")) {
    s.execute("drop table if exists test_batch_input;"
            + "create table test_batch_input(id Int32, name Nullable(String), value AggregateFunction(groupBitmap, UInt32))engine=Memory");
    Object[][] objs = new Object[][] {
            new Object[] { 1, "a", "aaaaa", ClickHouseBitmap.wrap(1, 2, 3, 4, 5) },
            new Object[] { 2, "b", null, ClickHouseBitmap.wrap(6, 7, 8, 9, 10) },
            new Object[] { 3, null, "33333", ClickHouseBitmap.wrap(11, 12, 13) }
    };
    for (Object[] v : objs) {
        stmt.setInt(1, (int) v[0]);
        stmt.setString(2, (String) v[1]);
        stmt.setString(3, (String) v[2]);
        stmt.setObject(4, v[3]);
        stmt.addBatch();
    }
    int[] results = stmt.executeBatch();
    ...
}

// use bitmap as query parameter
try (PreparedStatement stmt = conn.prepareStatement(
    "SELECT bitmapContains(my_bitmap, toUInt32(1)) as v1, bitmapContains(my_bitmap, toUInt32(2)) as v2 from {tt 'ext_table'}")) {
    stmt.setObject(1, ClickHouseExternalTable.builder().name("ext_table")
            .columns("my_bitmap AggregateFunction(groupBitmap,UInt32)").format(ClickHouseFormat.RowBinary)
            .content(new ByteArrayInputStream(ClickHouseBitmap.wrap(1, 3, 5).toBytes()))
            .asTempTable()
            .build());
    ResultSet rs = stmt.executeQuery();
    Assert.assertTrue(rs.next());
    Assert.assertEquals(rs.getInt(1), 1);
    Assert.assertEquals(rs.getInt(2), 0);
    Assert.assertFalse(rs.next());
}

HTTP 라이브러리 구성하기

ClickHouse JDBC 커넥터는 세 가지 HTTP 라이브러리를 지원합니다: HttpClient, HttpURLConnection, 그리고 Apache HttpClient.

참고

HttpClient는 JDK 11 이상에서만 지원됩니다.

JDBC 드라이버는 기본적으로 HttpClient를 사용합니다. 다음 속성을 설정하면 ClickHouse JDBC 커넥터에서 사용하는 HTTP 라이브러리를 변경할 수 있습니다:

properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");

해당 값들의 전체 목록은 다음과 같습니다:

속성 값HTTP 라이브러리
HTTP_CLIENTHttpClient
HTTP_URL_CONNECTIONHttpURLConnection
APACHE_HTTP_CLIENTApache HttpClient

SSL을 사용하여 ClickHouse에 연결하기

SSL을 사용해 ClickHouse에 보안 JDBC 연결을 설정하려면 JDBC 속성에 SSL 매개변수가 포함되도록 구성해야 합니다. 일반적으로 JDBC URL 또는 Properties 객체에 sslmodesslrootcert와 같은 SSL 속성을 지정합니다.

SSL 속성

이름기본값선택 가능한 값설명
sslfalsetrue, false연결에 SSL/TLS를 활성화할지 여부
sslmodestrictstrict, noneSSL/TLS 인증서를 검증할지 여부
sslrootcertSSL/TLS 루트 인증서 경로
sslcertSSL/TLS 인증서 경로
sslkeyPKCS#8 형식의 RSA 키
key_store_typeJKS, PKCS12KeyStore/TrustStore 파일의 유형 또는 형식을 지정합니다.
trust_storeTrustStore 파일 경로
key_store_passwordKeyStore 구성에서 지정된 KeyStore 파일에 액세스하는 데 필요한 암호

이러한 속성은 Java 애플리케이션이 암호화된 연결을 통해 ClickHouse 서버와 통신하도록 보장하여, 데이터 전송 과정에서의 보안을 강화합니다.

  String url = "jdbc:ch://your-server:8443/system";

  Properties properties = new Properties();
  properties.setProperty("ssl", "true");
  properties.setProperty("sslmode", "strict"); // NONE to trust all servers; STRICT for trusted only
  properties.setProperty("sslrootcert", "/mine.crt");
  try (Connection con = DriverManager
          .getConnection(url, properties)) {

      try (PreparedStatement stmt = con.prepareStatement(

          // place your code here

      }
  }

대용량 삽입 작업에서 JDBC 타임아웃 문제 해결

ClickHouse에서 실행 시간이 긴 대용량 삽입 작업을 수행하는 경우 다음과 같은 JDBC 타임아웃 오류가 발생할 수 있습니다:

Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]

이러한 오류는 데이터 삽입 프로세스를 중단시키고 시스템 안정성에 영향을 줄 수 있습니다. 이 문제를 해결하려면 클라이언트 OS의 타임아웃 설정 몇 가지를 조정하십시오.

Mac OS

Mac OS에서는 다음 설정을 조정하여 문제를 해결할 수 있습니다:

  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1

Linux

Linux에서는 동일한 설정만으로는 문제가 해결되지 않을 수 있습니다. Linux에서 소켓 keep-alive 설정을 처리하는 방식이 다르기 때문에 추가 단계가 필요합니다. 다음 단계를 수행하세요:

  1. /etc/sysctl.conf 또는 관련 구성 파일에서 다음 Linux 커널 파라미터를 조정하십시오:
  • net.inet.tcp.keepidle: 60000
  • net.inet.tcp.keepintvl: 45000
  • net.inet.tcp.keepinit: 45000
  • net.inet.tcp.keepcnt: 8
  • net.inet.tcp.always_keepalive: 1
  • net.ipv4.tcp_keepalive_intvl: 75
  • net.ipv4.tcp_keepalive_probes: 9
  • net.ipv4.tcp_keepalive_time: 60 (기본값인 300초보다 더 낮은 값으로 설정하는 것을 고려할 수 있습니다)
  1. 커널 매개변수를 수정한 후, 다음 명령을 실행하여 변경 사항을 적용하십시오:
sudo sysctl -p

해당 설정을 완료한 후에는 클라이언트가 소켓에서 Keep-Alive 옵션을 활성화했는지 확인해야 합니다:

properties.setProperty("socket_keepalive", "true");
참고

현재 소켓 keep-alive를 설정하려면 Apache HTTP Client 라이브러리를 사용해야 합니다. clickhouse-java가 지원하는 다른 두 HTTP 클라이언트 라이브러리는 소켓 옵션 설정을 허용하지 않습니다. 자세한 내용은 HTTP 라이브러리 구성하기를 참조하세요.

또는 동일한 매개변수를 JDBC URL에 추가할 수 있습니다.

JDBC 드라이버의 기본 소켓 및 연결 타임아웃은 30초입니다. 대용량 데이터 삽입 작업을 지원하려면 타임아웃을 늘릴 수 있습니다. ClickHouseClientOption에 정의된 SOCKET_TIMEOUTCONNECTION_TIMEOUT 옵션과 함께 ClickHouseClientoptions 메서드를 사용하세요:

final int MS_12H = 12 * 60 * 60 * 1000; // 12 h in ms
final String sql = "insert into table_a (c1, c2, c3) select c1, c2, c3 from table_b;";

try (ClickHouseClient client = ClickHouseClient.newInstance(ClickHouseProtocol.HTTP)) {
    client.read(servers).write()
        .option(ClickHouseClientOption.SOCKET_TIMEOUT, MS_12H)
        .option(ClickHouseClientOption.CONNECTION_TIMEOUT, MS_12H)
        .query(sql)
        .executeAndWait();
}