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

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.6</version>
    <classifier>all</classifier>
</dependency>

구성

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

참고

com.clickhouse.jdbc.ClickHouseDriver는 새로운 JDBC 구현과 이전 JDBC 구현을 위한 파사드 클래스입니다. 기본적으로 새로운 JDBC 구현을 사용합니다. 연결 속성에서 clickhouse.jdbc.v1 속성을 true로 설정하면 이전 JDBC 구현을 사용할 수 있습니다.

com.clickhouse.jdbc.Driver는 새로운 JDBC 구현입니다. com.clickhouse.jdbc.DriverV1은 기존 JDBC 구현입니다.

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입니다.
서버 설정

모든 서버 설정에는 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 인코딩할 필요가 없습니다. 자동으로 인코딩됩니다.

지원되는 데이터 유형

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으로 읽을 수 있습니다.

문자열 타입(String Types)

ClickHouse 타입JDBC 타입Java 클래스
StringVARCHARjava.lang.String
FixedStringVARCHARjava.lang.String
  • Stringjava.lang.String 또는 byte[] 타입으로만 읽을 수 있습니다.
  • FixedString은 값이 있는 그대로 읽히되, 컬럼 길이에 맞도록 0으로 채워집니다. (예를 들어 'John'에 대한 FixedString(10)'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으로 가져올 수 있습니다.

수집 유형

ClickHouse 타입JDBC 타입Java 클래스
ArrayARRAYjava.sql.Array
TupleOTHERObject[]
JAVA_OBJECTjava.util.Map
  • 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 컬럼 정의를 반환합니다.

지리 타입(Geo Types)

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

널 허용 및 LowCardinality 타입

  • NullableLowCardinality는 다른 타입을 감싸는 특수 타입입니다.
  • 널 허용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으로 읽거나 쓸 수 있습니다.

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

java.sql.Date, java.sql.Time, java.sql.Timestamp는 시간대(Timezone) 계산을 복잡하게 만들 수 있습니다. 물론 지원되기는 하지만, java.time 패키지 사용을 고려하십시오. ZonedDateTimeOffsetDateTime은 java.sql.Timestamp, java.sql.Date, java.sql.Time을 대체하기에 적합합니다.

Date vs DateTime

Date는 타임존 없이 저장되며, DateTime은 타임존과 함께 저장됩니다. 주의하지 않으면 예상치 못한 결과가 발생할 수 있습니다.

연결 생성하기

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의 타임아웃 설정을 조정해야 할 수 있습니다.

macOS

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는 보다 가볍게 구현되었으며, 그 과정에서 일부 기능이 제거되었습니다.
    • Streaming Data는 JDBC 사양과 Java의 일부가 아니므로 JDBC V2에서는 지원되지 않습니다.
  • JDBC V2는 명시적인 설정이 필요합니다. 장애 조치(failover)에 대한 기본 설정은 제공되지 않습니다.
    • URL에 프로토콜을 명시해야 합니다. 포트 번호 기반의 암시적 프로토콜 감지는 허용되지 않습니다.

구성 변경 사항

열거형(enum)은 두 가지만 존재합니다:

  • com.clickhouse.jdbc.DriverProperties - 드라이버 자체의 구성 속성입니다.
  • com.clickhouse.client.api.ClientConfigProperties - 클라이언트 구성 속성을 나타냅니다. 클라이언트 구성 변경 방법은 Java 클라이언트 문서에 설명되어 있습니다.

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

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

예제:

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.BigInteger기타java.math.BigInteger
UInt8기타java.lang.Short기타com.clickhouse.data.value.UnsignedByte
UInt16기타java.lang.Integer기타com.clickhouse.data.value.UnsignedShort
UInt32기타java.lang.Long기타com.clickhouse.data.value.UnsignedInteger
UInt64기타java.math.BigInteger기타com.clickhouse.data.value.UnsignedLong
UInt128기타java.math.BigInteger기타java.math.BigInteger
UInt256기타java.math.BigInteger기타java.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으로 읽힙니다.

날짜/시간 타입(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

수집 유형

ClickHouse 타입V1과 호환JDBC 타입(V2)Java 클래스(V2)JDBC 타입(V1)Java 클래스(V1)
배열ARRAYjava.sql.ArrayARRAYObject[] 또는 기본 타입 배열
튜플OTHERObject[]STRUCTjava.sql.Struct
JAVA_OBJECTjava.util.MapSTRUCTjava.util.Map
  • V2에서는 JDBC와의 호환성을 위해 Array가 기본적으로 java.sql.Array에 매핑됩니다. 이는 반환된 배열 값에 대한 정보를 더 많이 제공하여 타입 추론에 도움이 되도록 하기 위한 것입니다.
  • V2에서는 ArraygetResultSet() 메서드를 구현하여 원래 배열과 동일한 내용을 담은 java.sql.ResultSet을 반환합니다.
  • V1은 MapSTRUCT를 사용하지만 항상 java.util.Map 객체를 반환합니다. V2에서는 MapJAVA_OBJECT로 매핑하여 이 문제를 해결합니다. 또한 STRUCT는 컬럼 이름이 없기 때문에 Map에 대해 유효한 타입이 아닙니다.
  • V1은 Tuple에 대해 STRUCT를 사용하지만, 항상 List<Object> 객체를 반환합니다. V2는 TupleOTHER에 매핑하고 Object[]를 반환하도록 하여 이를 수정합니다. 서로 다른 타입이 함께 존재할 수 있으므로 List를 사용하는 것은 적절하지 않습니다.
  • PreparedStatement#setBytesResultSet#getBytes는 컬렉션 타입에는 사용할 수 없습니다. 이 메서드들은 바이너리 문자열을 처리하도록 설계되었습니다.
  • 일반적으로 java.sql.Array는 Array 타입을 기록하고 읽는 데 사용합니다. JDBC 드라이버는 이를 완전히 지원합니다.

지리 타입(Geo Types)

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

널 허용 및 LowCardinality 타입

  • NullableLowCardinality는 다른 데이터 타입을 감싸는 특수 데이터 타입입니다.
  • 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은 UUIDVARCHAR를 사용하지만, 항상 java.util.UUID 객체를 반환합니다. V2에서는 UUIDOTHER로 매핑함으로써 이를 개선하고, java.util.UUID 객체를 반환합니다.
  • V1은 IPv4IPv6VARCHAR를 사용하지만, 항상 java.net.Inet4Addressjava.net.Inet6Address 객체를 반환합니다. V2에서는 이를 수정하여 IPv4IPv6OTHER로 매핑하고 java.net.Inet4Addressjava.net.Inet6Address 객체를 반환합니다.
  • DynamicVariant는 V2의 새로운 타입입니다. V1에서는 지원되지 않습니다.
  • JSONDynamic 타입을 기반으로 합니다. 따라서 V2에서만 지원됩니다.
  • IPv4 및 IPv6 값은 getBytes(columnIndex) 메서드를 사용하여 byte[]로 읽을 수 있습니다. 하지만 이러한 타입에는 전용 클래스를 사용하는 것이 권장됩니다.
  • V2에서는 IP 주소를 숫자 값으로 읽는 기능을 지원하지 않습니다. IP 주소를 숫자 값으로 변환하는 작업은 InetAddress 클래스에서 구현하는 것이 더 적절하기 때문입니다.

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

  • V2에서는 데이터베이스 이름을 지정할 때 Schema라는 용어만 사용합니다. Catalog라는 용어는 향후 사용을 위해 예약되어 있습니다.
  • V2는 DatabaseMetaData.supportsTransactions()DatabaseMetaData.supportsSavepoints()에 대해 false를 반환합니다. 이는 향후 버전에서 변경될 예정입니다.

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

참고

최신 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 체크를 비활성화합니다; 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()) {
        // ...
    }
}

Insert

참고
  • PreparedStatementStatement 대신 사용하십시오

사용하기는 더 쉽지만 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의 타임아웃 설정 몇 가지를 조정하십시오.

macOS

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();
}