متطلبات البيئة
- OpenJDK الإصدار >= 8
الإعداد
{/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */}
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.9.8</version>
<classifier>all</classifier>
</dependency>// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
implementation("com.clickhouse:clickhouse-jdbc:0.9.8:all")// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
implementation 'com.clickhouse:clickhouse-jdbc:0.9.8:all'إذا كنت تستخدم JDBC driver ضمن تطبيق يستلزم إضافة ملف jar إلى classpath، فستحتاج إلى تنزيل ملف jar من:
- Maven Central ثم أضِفه إلى
classpath- بدءًا من الإصدار
0.9.4، تتوفر الحزمة https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc-all - استخدم اللاحقة
allللحصول على ملف jar يتضمن جميع التبعيات المدمجة.
- بدءًا من الإصدار
- أو من repository الرسمي هنا
الإعداد
فئة برنامج التشغيل: com.clickhouse.jdbc.ClickHouseDriver
صياغة URL: jdbc:(ch|clickhouse)[:<protocol>]://endpoint[:port][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...]، على سبيل المثال:
jdbc:clickhouse:http://localhost:8123jdbc:clickhouse:https://localhost:8443?ssl=true
ثمة بعض الأمور الجديرة بالملاحظة حول صياغة URL:
- يُسمح بوجود نقطة نهاية واحدة فقط في URL
- يجب تحديد البروتوكول إذا لم يكن البروتوكول الافتراضي، وهو 'HTTP'
- يجب تحديد المنفذ إذا لم يكن المنفذ الافتراضي '8123'
- لا يستنتج المُشغِّل البروتوكول من المنفذ، لذا يجب تحديده صراحةً
- لا تكون المعلَمة
sslمطلوبة عند تحديد البروتوكول.
خصائص الاتصال
تُعرَّف معاملات الإعداد الرئيسية في Java client، وينبغي تمريرها كما هي إلى driver. يمتلك driver بعض الخصائص الخاصة به التي لا تندرج ضمن إعداد client، وهي مُدرجة أدناه.
خصائص برنامج التشغيل:
| الخاصية | القيمة الافتراضية | الوصف |
|---|---|---|
disable_frameworks_detection |
true |
تعطيل اكتشاف أطر العمل لـ User-Agent |
jdbc_ignore_unsupported_values |
false |
يتجاهل SQLFeatureNotSupportedException عندما لا يؤثر ذلك في عمل برنامج التشغيل |
clickhouse.jdbc.v1 |
false |
استخدام تطبيق JDBC الأقدم بدلًا من JDBC الجديد |
default_query_settings |
null |
يسمح بتمرير إعدادات الاستعلام الافتراضية مع عمليات الاستعلام |
jdbc_resultset_auto_close |
true |
يُغلق ResultSet تلقائيًا عند إغلاق Statement |
beta.row_binary_for_simple_insert |
false |
استخدم تنفيذ PreparedStatement المعتمد على كاتب RowBinary. يعمل فقط مع استعلامات INSERT INTO ... VALUES. |
jdbc_resultset_auto_close |
true |
يُغلَق ResultSet تلقائيًا عند إغلاق Statement |
jdbc_use_max_result_rows |
false |
يتيح استخدام خاصية الخادم max_result_rows لتقييد عدد الصفوف التي يعيدها الاستعلام. عند تفعيله، يتجاوز وضع overflow الذي عيّنه المستخدم. راجع JavaDoc لمزيد من التفاصيل. |
jdbc_sql_parser |
JAVACC |
يحدّد محلل SQL الذي سيُستخدم. الخيارات: ANTLR4، ANTLR4_PARAMS_PARSER، JAVACC. |
remember_last_set_roles |
true |
تذكّر آخر الأدوار المعيّنة للاتصال. |
مثال على الإعداد:
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);وهو ما يعادل URL الخاص بـ JDBC التالي:
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.ملاحظة: لا حاجة إلى ترميز URL لـ JDBC URL أو الخصائص، إذ سيُجرى ترميزها تلقائيًا.
ملفات تعريف للقراءة فقط
نتجنب عمدًا إضافة الإعدادات الافتراضية إلى خصائص الاتصال تفاديًا للمشكلات المرتبطة بملفات التعريف للقراءة فقط.
غير أن بعض المستخدمين يحتاجون إلى تمرير إعدادات التنسيق (على سبيل المثال لقراءة JSON بوصفه String)، ونوصي في هذه الحالة باستخدام ملف التعريف readonly=2.
اطّلع على مزيد من المعلومات حول ملفات التعريف للقراءة فقط هنا.
تعريف العميل
ثمة طريقتان لتحديد التطبيق الذي أنشأ الطلب: ضبط 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 jdbc-v2/0.9.7 clickhouse-java-v2/0.9.6 (Linux; jvm:17.0.17) Apache-HttpClient/5.4.4ملاحظة: نوصي باستخدام تنسيق app_name/version للخاصية client_name لأنه يُساعد في التعرف على التطبيق في سجل الاستعلامات.
تحديد العملية
يُنشئ JDBC driver معرّف query_id لكل عملية (يُضمَّن حاليًا في استثناءات الخادم).
لتعيين log_comment لعملية ما، استخدم الـ method com.clickhouse.jdbc.StatementImpl#getLocalSettings. يستلزم ذلك تحويل Statement أو PreparedStatement إلى النوع com.clickhouse.jdbc.StatementImpl أولاً.
StatementImpl stmt = (StatementImpl) conn.createStatement();
stmt.getLocalSettings().logComment("some-comment");ملاحظة: يعمل هذا الأسلوب مع حالات الاستخدام أحادية الخيط للعبارة، لأن localSettings مشترك بين الخيوط.
أنواع البيانات المدعومة
يدعم 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. توجد بعض القيود على هذا التحويل. راجع كل قسم للاطلاع على التفاصيل.
الأنواع الرقمية
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| Int8 | TINYINT | java.lang.Byte |
| Int16 | SMALLINT | java.lang.Short |
| Int32 | INTEGER | java.lang.Integer |
| Int64 | BIGINT | java.lang.Long |
| Int128 | NUMERIC | java.math.BigInteger |
| Int256 | NUMERIC | java.math.BigInteger |
| UInt8 | SMALLINT | java.lang.Short |
| UInt16 | INTEGER | java.lang.Integer |
| UInt32 | BIGINT | java.lang.Long |
| UInt64 | NUMERIC | java.math.BigInteger |
| UInt128 | NUMERIC | java.math.BigInteger |
| UInt256 | NUMERIC | java.math.BigInteger |
| Float32 | FLOAT | java.lang.Float |
| Float64 | DOUBLE | java.lang.Double |
| Decimal32 | DECIMAL | java.math.BigDecimal |
| Decimal64 | DECIMAL | java.math.BigDecimal |
| Decimal128 | DECIMAL | java.math.BigDecimal |
| Decimal256 | DECIMAL | java.math.BigDecimal |
| Bool | BOOLEAN | java.lang.Boolean |
- الأنواع الرقمية قابلة للتحويل فيما بينها. لذا يمكن الحصول على
Int8بصيغةFloat64والعكس صحيح.:- سيُرجع
rs.getObject(1, Float64.class)قيمةFloat64منInt8column. - سيُرجع
rs.getLong(1)قيمةLongمنInt8column. - يمكن أن يُرجع
rs.getByte(1)قيمةByteمنInt16column إذا كانت ضمن نطاقByte.
- سيُرجع
- لا يُنصح بالتحويل من نوع أوسع إلى نوع أضيق بسبب خطر تلف البيانات.
- يتعامل النوع
Boolأيضًا على أنه عدد. - يمكن قراءة جميع الأنواع العددية على أنها
java.lang.String. - توجد مشكلة عند تخزين
Float.MAX_VALUEفي Java بوصفهFloat(https://github.com/ClickHouse/clickhouse-java/issues/809). ويؤدي حفظ القيمة نفسها بوصفهاDoubleإلى حل هذه المشكلة.
أنواع String
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| String | VARCHAR | java.lang.String |
| FixedString | VARCHAR | java.lang.String |
- لا يمكن قراءة
Stringإلا باعتبارهjava.lang.Stringأوbyte[]. - تُقرأ
FixedStringكما هي، وتُحشى بأصفار حتى يصل طولها إلى طول العمود. (على سبيل المثال، ستُقرأFixedString(10)للقيمة'John'على أنها'John\0\0\0\0\0\0\0\0\0'.)
أنواع Enum
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| Enum8 | VARCHAR | java.lang.String |
| Enum16 | VARCHAR | java.lang.String |
- يُعيَّن
Enum8وEnum16إلىjava.lang.Stringافتراضيًا. - يمكن قراءة قيم Enum كقيم رقمية باستخدام دالة getter المخصّصة أو الدالة
getObject(columnIndex, Integer.class). - يُعيَّن
Enum16داخليًا إلى short، ويُعيَّنEnum8إلى byte. ويجب تجنّب قراءةEnum16على أنه byte بسبب خطر تلف البيانات. - يمكن تعيين قيم Enum كسلسلة نصية أو كقيمة رقمية في
PreparedStatement.
أنواع التاريخ/الوقت
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| Date | DATE | java.sql.Date |
| Date32 | DATE | java.sql.Date |
| DateTime | TIMESTAMP | java.sql.Timestamp |
| DateTime64 | TIMESTAMP | java.sql.Timestamp |
| Time | TIME | java.sql.Time |
| Time64 | TIME | java.sql.Time |
- تُطابَق أنواع
Date / Timeمع أنواعjava.sqlلتحسين التوافق مع JDBC. ومع ذلك، يمكن أيضًا الحصول علىjava.time.LocalDateوjava.time.LocalDateTimeوjava.time.LocalTimeباستخدامResultSet#getObject(columnIndex, Class<T>)مع تمرير الفئة المقابلة بوصفها الوسيط الثاني.- ستُعيد
rs.getObject(1, java.time.LocalDate.class)القيمةjava.time.LocalDateمن العمودDate. - ستُعيد
rs.getObject(1, java.time.LocalDateTime.class)القيمةjava.time.LocalDateTimeمن العمودDateTime. - ستُعيد
rs.getObject(1, java.time.LocalTime.class)القيمةjava.time.LocalTimeمن العمودTime.
- ستُعيد
- لا تتأثر
DateوDate32وTimeوTime64بالمنطقة الزمنية للخادم. - يتأثر
DateTimeوDateTime64بالمنطقة الزمنية للخادم أو بالمنطقة الزمنية للجلسة. - يمكن استرجاع
DateTimeوDateTime64كـZonedDateTimeباستخدامgetObject(colIndex, ZonedDateTime.class).
الأنواع المتداخلة
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| Array | ARRAY | java.sql.Array |
| Tuple | OTHER | com.clickhouse.data.Tuple |
| Map | OTHER | java.util.Map |
| Nested | ARRAY | java.sql.Array |
- يُعيَّن
Arrayإلىjava.sql.Arrayافتراضيًا لضمان التوافق مع JDBC. ويُجرى ذلك أيضًا لتوفير مزيد من المعلومات عن قيمة المصفوفة المُعادة. وهذا مفيد في استدلال النوع. - يوفّر
ArrayالتابعgetResultSet()لإرجاعjava.sql.ResultSetبالمحتوى نفسه الموجود في المصفوفة الأصلية. - لا ينبغي قراءة أنواع المجموعات باعتبارها
java.lang.Stringلأن هذه ليست طريقة صالحة لتمثيل البيانات (على سبيل المثال: لا تُوضَع قيم السلاسل النصية بين علامتَي اقتباس في المصفوفة). Mapيُعيَّن إلىOTHERلأن القيمة لا يمكن قراءتها إلا باستخدام الطريقةgetObject(columnIndex, Class<T>).- لا يُعَدّ
Mapمن نوعjava.sql.Structلأنه لا يحتوي على أعمدة مُسمّاة.
- لا يُعَدّ
- يتم تمثيل
Tupleعلى شكلObject[]لأنه قد يحتوي على أنواع مختلفة، كما أن استخدامListغير صالح. - يمكن قراءة
TupleباعتبارهArrayباستخدام الطريقةgetObject(columnIndex, Array.class). في هذه الحالة، ستُعيدArray#baseTypeNameتعريف العمودTuple.
بيانات وصفية لنوع عنصر المصفوفة
تُعيد Array.getBaseTypeName() اسم نوع العنصر في ClickHouse؛ وتُعيد Array.getBaseType() رمز نوع JDBC.
يحتفظ JDBC V2 بتوقيعات النوع الكاملة (أنواع المُغلِّف، ومعاملات النوع) التي يحذفها V1.
قواعد التعيين العامة للمصفوفات هي:
| نوع ClickHouse | getBaseTypeName() |
getBaseType() |
|---|---|---|
Array(<نوع بدائي>) |
<نوع بدائي> |
<نوع JDBC البدائي> |
Array(<Parameterized Type>(<N>)) |
<نوع ذو معلمات>(<N>) |
<نوع JDBC للنوع الأساسي> |
Array(Nullable(<Type>)) |
Nullable(<Type>) |
<نوع JDBC الخاص بـ Type الداخلي> |
Array(LowCardinality(<Type>)) |
LowCardinality(<Type>) |
<نوع JDBC لـ Type الداخلي> |
Array(Array(...(<Type>))) |
<Type> (العنصر الداخلي الأعمق) |
<نوع JDBC للعنصر الداخلي الأعمق> |
Array(Tuple(...)) |
Tuple(...) (التعريف الكامل) |
OTHER |
Array(Enum8(...)) / Array(Enum16(...)) |
Enum8(...) / Enum16(...) (التعريف كاملاً) |
VARCHAR |
ملاحظات حول القواعد أعلاه:
- يتم الاحتفاظ بالأنواع المغلِّفة (
Nullable,LowCardinality) فيgetBaseTypeName()، لكنgetBaseType()يُرجِع رمز JDBC للنوع الداخلي. - تُسطَّح المصفوفات المتداخلة في البيانات الوصفية: يُرجِع
getBaseTypeName()نوع العنصر الأعمق غير المصفوفي، وليس العنصر الفرعي المباشر. - تحتفظ الأنواع ذات المعلمات (
FixedString(N)، والتعريفات الكاملة لـEnum/Tuple) بمعلماتها فيgetBaseTypeName().
أمثلة:
| نوع ClickHouse (مثال) | getBaseTypeName() |
getBaseType() |
|---|---|---|
| Array(Int8) | Int8 | TINYINT |
| Array(Int16) | Int16 | SMALLINT |
| Array(Int32) | Int32 | INTEGER |
| Array(Int64) | Int64 | BIGINT |
| Array(UInt8) | UInt8 | SMALLINT |
| Array(UInt16) | UInt16 | INTEGER |
| Array(UInt32) | UInt32 | BIGINT |
| Array(UInt64) | UInt64 | NUMERIC |
| Array(Float32) | Float32 | FLOAT |
| Array(Float64) | Float64 | DOUBLE |
| Array(String) | String | VARCHAR |
| Array(FixedString(8)) | FixedString(8) | VARCHAR |
| Array(Bool) | Bool | BOOLEAN |
| Array(Date) | Date | DATE |
| Array(DateTime) | DateTime | TIMESTAMP |
| Array(UUID) | UUID | OTHER |
| Array(Nullable(Int32)) | Nullable(Int32) | INTEGER |
| Array(Nullable(String)) | Nullable(String) | VARCHAR |
| Array(LowCardinality(String)) | LowCardinality(String) | VARCHAR |
| Array(LowCardinality(Nullable(String))) | LowCardinality(Nullable(String)) | VARCHAR |
| Array(Array(Int32)) | Int32 | INTEGER |
| Array(Array(Array(String))) | String | VARCHAR |
| Array(Tuple(name String, val Int32)) | Tuple(name String, val Int32) | أخرى |
| Array(Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3)) | Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3) | VARCHAR |
- في V2، تحتفظ
getBaseTypeName()بتوقيع النوع الكامل، بما في ذلك أنواع التغليف (NullableوLowCardinality) ومعلّمات النوع (FixedString(8)وتعريفاتEnumوTupleالكاملة). أما V1 فيُزيل هذه ويُرجع اسم النوع الأساسي فقط. - تستخدم مصفوفات
TupleالقيمةOTHER (1111)في V2 بدلًا منSTRUCT (2002)لأن Tuple في ClickHouse تتضمن حقولًا مسمّاة، وهو ما لا يدعمهjava.sql.Struct. - تستخدم مصفوفات
UUIDالقيمةOTHER (1111)في V2، بما يتوافق مع تعيينUUIDالقياسي. - تُعيَّن قيم
EnumإلىVARCHAR— إذ تُحدَّد عناصر enum باسمها النصي بغضّ النظر عن ترميزها الرقمي الأساسي.
كتابة المصفوفات
استخدم java.sql.Connection#createArrayOf لإنشاء كائن java.sql.Array. صُمِّم هذا الكائن لتوحيد التعامل مع المصفوفات عبر قواعد البيانات المختلفة.
يُعدّ الاتصال ضروريًا لتمرير الإعدادات إلى أسلوب المصنع الخاص بـ Array.
تقبل الطريقة وسيطَين:
typeName- اسم نوع عناصر المصفوفة. على سبيل المثالArray(Int32)->"Int32".elements- عناصر المصفوفة نفسها. على سبيل المثال[[1, 2, 3], [4, 5, 6]]->new Integer[][] {{1, 2, 3}, {4, 5, 6}}.
يمكن تمثيل Tuple على هيئة Object[] أو java.sql.Struct (راجع كيفية كتابة Tuples أدناه).
مثال
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();
}
}قراءة المصفوفات
استخدم ResultSet#getArray(columnIndex) لقراءة كائن Array. يمكن استخدام هذا الكائن للوصول إلى مصفوفة بأي عمق تداخل.
يمكن استخدام الأسلوب 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()) {
// ...
}
}
}
}
}كتابة Tuples
يتم تعيين Tuples إلى كائن com.clickhouse.data.Tuple وينبغي كتابتها بوصفها هذا الكائن عبر استدعاء الأسلوب setObject(columnIndex, tuple).
يمكن استخدام كائن java.sql.Struct لكتابة tuples لتحقيق قدر أكبر من قابلية النقل.
مثال
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();
}
}قراءة Tuples
ستُعيد الدالة getObject(columnIndex) القيمة Object[]. يمكن قراءة Tuples بوصفها java.sql.Array باستخدام الدالة getObject(columnIndex, Array.class).
مثال
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());
}
}
}كتابة الخرائط
لا يمكن كتابة Map إلا على شكل كائن java.collections.Map، إذ يستلزم هذا النوع أزواج مفتاح-قيمة (لا يدعم java.sql.Struct أزواج مفتاح-قيمة).
مثال
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();
}
}قراءة الخرائط
يمكن قراءة Map كـ java.collections.Map object باستخدام method getObject(columnIndex, Map.class).
مثال
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);
// ...
}
}
}
}كتابة Nested
استخدم java.sql.Connection#createStruct لإنشاء كائن java.sql.Struct. صُمِّم هذا الكائن لتوحيد التعامل مع البنى المتداخلة عبر قواعد البيانات المختلفة.
يلزم توفر الاتصال لتمرير الإعدادات إلى دالة المصنع الخاصة بـ Struct.
تقبل الطريقة وسيطَين:
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) لقراءة كائن Nested. يمكن استخدام الكائن للوصول إلى العناصر المتداخلة بأي مستوى من مستويات التداخل.
يمكن استخدام الدالة 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
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| Point | OTHER | double[] |
| Ring | OTHER | double[][] |
| Polygon | OTHER | double[][][] |
| MultiPolygon | OTHER | double[][][][] |
أنواع Nullable وLowCardinality
NullableوLowCardinalityنوعان خاصّان يغلّفان أنواعًا أخرى.- يؤثّر
Nullableعلى كيفية إرجاع أسماء الأنواع فيResultSetMetaData
الأنواع الخاصة
| نوع ClickHouse | نوع JDBC | فئة Java |
|---|---|---|
| UUID | OTHER | java.util.UUID |
| IPv4 | OTHER | java.net.Inet4Address |
| IPv6 | OTHER | java.net.Inet6Address |
| JSON | OTHER | java.lang.String |
| AggregateFunction | OTHER | (التمثيل الثنائي) |
| SimpleAggregateFunction | (النوع المُغلَّف) | (الفئة المُغلَّفة) |
- لا يُعد
UUIDنوعًا قياسيًا في JDBC. لكنه جزء من JDK. بشكل افتراضي، يُعادjava.util.UUIDمن الطريقةgetObject(). - يمكن قراءة
UUIDوكتابته على هيئةStringباستخدام الطريقةgetObject(columnIndex, String.class). - لا يُعد
IPv4وIPv6نوعين قياسيين في JDBC. لكنهما جزء من JDK. بشكل افتراضي، يُعادjava.net.Inet4Addressوjava.net.Inet6Addressمن الطريقةgetObject(). - يمكن قراءة
IPv4وIPv6وكتابتهما على هيئةStringباستخدام الطريقةgetObject(columnIndex, String.class).
نوع JSON
يُعيَّن JSON type إلى Map<String, Object> افتراضيًا، حيث تكون مفاتيح JSON object هي keys وقيم JSON object هي values.
على سبيل المثال:
{
"key1": "value1",
"key2": ["value2", "value3"]
"key3": {
"key4": "value4",
"key5": "value5"
}
}سيتم تعيينه إلى:
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", Arrays.asList("value2", "value3"));
map.put("key3", new HashMap<String, Object>() {{
put("key4", "value4");
put("key5", "value5");
}});ثمة خيار أكثر ملاءمةً لقراءة JSON بوصفه String عبر تمرير إعداد الخادم jdbc_read_json_as_string=true إلى خصائص الاتصال.
يجعل هذا الـ driver يُعيد قيم JSON بوصفها String، ويمكن استخدامها لتحليل البيانات باستخدام أي مكتبة JSON.
Properties properties = new Properties();
properties.setProperty(
ClientConfigProperties.serverSetting(ServerSettings.OUTPUT_FORMAT_BINARY_WRITE_JSON_AS_STRING),
"1");
try (Connection conn = DriverManager.getConnection(url, properties)) {
try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_json ORDER BY order")) {
while (rs.next()) {
String json = rs.getString("json");
// ...
}
}
}بدءًا من الإصدار 25.8 من ClickHouse، لم تعد الأرقام مُقتبسةً بشكل افتراضي. بالنسبة للإصدارات الأقدم، يمكنك تعطيل الاقتباس عبر تمرير إعدادات الخادم إلى خصائص الاتصال:
Properties properties = new Properties();
properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0");
properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0");
properties.put(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0");التعامل مع التواريخ والأوقات والمناطق الزمنية
يُرجى الاطلاع على دليل التاريخ والوقت الذي يشرح الأخطاء الشائعة ومنطق عمل الـ driver عند التعامل مع قيم التاريخ والوقت والـ timestamps.
إنشاء اتصال
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
}عبارة بسيطة
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 Client.
استكشاف الأخطاء وإصلاحها
التسجيل
يستخدم برنامج التشغيل slf4j للتسجيل، وسيستخدم أول تطبيق متاح على classpath.
حل مشكلة انتهاء مهلة JDBC عند عمليات الإدراج الكبيرة
عند تنفيذ عمليات إدراج كبيرة في ClickHouse ذات أوقات تنفيذ طويلة، قد تواجه أخطاء انتهاء مهلة JDBC مثل:
Caused by: java.sql.SQLException: Read timed out, server myHostname [uri=https://hostname.aws.clickhouse.cloud:8443]قد تُعطّل هذه الأخطاء عملية إدراج البيانات وتؤثر على استقرار النظام. لمعالجة هذه المشكلة، قد تحتاج إلى ضبط بعض إعدادات المهلة الزمنية في نظام تشغيل العميل.
Mac OS
على نظام Mac OS، يمكن تعديل الإعدادات التالية لحل المشكلة:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1
Linux
على Linux، قد لا تكفي الإعدادات المكافئة وحدها لحل المشكلة. ويعود ذلك إلى الاختلافات في طريقة تعامل Linux مع إعدادات keep-alive للمقبس، مما يستوجب اتخاذ خطوات إضافية. اتبع الخطوات التالية:
- عدّل معلمات نواة Linux التالية في
/etc/sysctl.confأو في ملف تهيئة ذي صلة:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1net.ipv4.tcp_keepalive_intvl: 75net.ipv4.tcp_keepalive_probes: 9net.ipv4.tcp_keepalive_time: 60 (قد ترغب في خفض هذه القيمة من القيمة الافتراضية البالغة 300 ثانية)
- بعد تعديل معلمات النواة، طبِّق التغييرات عن طريق تشغيل الأمر التالي:
sudo sysctl -pبعد ضبط هذه الإعدادات، تحتاج إلى التأكد من أن الـ client لديك يُفعّل خيار Keep Alive على الـ socket:
properties.setProperty("socket_keepalive", "true");دليل الترحيل
التغييرات الرئيسية
| الميزة | V1 (الإصدار القديم) | V2 (الإصدار الجديد) |
|---|---|---|
| دعم المعاملات | مدعوم جزئيًا | غير مدعوم |
| إعادة تسمية أعمدة الاستجابة | مدعوم جزئيًا | غير مدعوم |
| تعليمات SQL متعددة | غير مدعوم | غير مسموح به |
| المعاملات المُسمّاة | مدعوم | غير مدعوم (ليس ضمن مواصفات JDBC) |
البيانات المتدفقة مع PreparedStatement |
مدعوم | غير مدعوم |
- صُمم JDBC V2 ليكون أخف، وأزيلت منه بعض الميزات.
- البيانات المتدفقة غير مدعومة في JDBC V2 لأنها ليست جزءًا من مواصفة JDBC ولا من Java.
- يتطلب JDBC V2 إعدادًا صريحًا. لا توجد إعدادات افتراضية للتجاوز التلقائي.
- يجب تحديد البروتوكول في عنوان URL. لا يتم اكتشاف البروتوكول ضمنيًا بالاعتماد على أرقام المنافذ.
تغييرات الإعداد
يوجد نوعان فقط من أنواع enum:
com.clickhouse.jdbc.DriverProperties- خصائص التهيئة الخاصة ببرنامج التشغيل.com.clickhouse.client.api.ClientConfigProperties- خصائص تهيئة العميل. التغييرات على تهيئة العميل موضحة في وثائق Java Client.
يتم تحليل خصائص الاتصال على النحو التالي:
- يُحلَّل URL أولًا لاستخراج الخصائص. وتتجاوز هذه الخصائص جميع الخصائص الأخرى.
- لا تُمرَّر خصائص برنامج التشغيل إلى العميل.
- تُستخرَج نقاط النهاية (
hostوportوprotocol) من 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
}تغييرات أنواع البيانات
الأنواع الرقمية
| نوع ClickHouse | متوافق مع V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| Int8 | ✅ | TINYINT | java.lang.Byte | TINYINT | java.lang.Byte |
| Int16 | ✅ | SMALLINT | java.lang.Short | SMALLINT | java.lang.Short |
| Int32 | ✅ | INTEGER | java.lang.Integer | INTEGER | java.lang.Integer |
| Int64 | ✅ | BIGINT | java.lang.Long | BIGINT | java.lang.Long |
| Int128 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger |
| Int256 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger |
| UInt8 | ❌ | SMALLINT | java.lang.Short | SMALLINT | com.clickhouse.data.value.UnsignedByte |
| UInt16 | ❌ | INTEGER | java.lang.Integer | INTEGER | com.clickhouse.data.value.UnsignedShort |
| UInt32 | ❌ | BIGINT | java.lang.Long | BIGINT | com.clickhouse.data.value.UnsignedInteger |
| UInt64 | ❌ | NUMERIC | java.math.BigInteger | NUMERIC | com.clickhouse.data.value.UnsignedLong |
| UInt128 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger |
| UInt256 | ✅ | NUMERIC | java.math.BigInteger | NUMERIC | java.math.BigInteger |
| Float32 | ✅ | FLOAT | java.lang.Float | FLOAT | java.lang.Float |
| Float64 | ✅ | DOUBLE | java.lang.Double | DOUBLE | java.lang.Double |
| Decimal32 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Decimal64 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Decimal128 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Decimal256 | ✅ | DECIMAL | java.math.BigDecimal | DECIMAL | java.math.BigDecimal |
| Bool | ✅ | BOOLEAN | java.lang.Boolean | BOOLEAN | java.lang.Boolean |
- أكبر اختلاف هو أن الأنواع غير الموقَّعة تُحوَّل إلى أنواع Java لتحسين قابلية النقل.
أنواع String
| نوع ClickHouse | متوافق مع الإصدار V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| String | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String |
| FixedString | ✅ | VARCHAR | java.lang.String | VARCHAR | java.lang.String |
- تتم قراءة
FixedStringكما هي في كلا الإصدارين. على سبيل المثال، ستُقرأFixedString(10)للقيمة'John'على أنها'John\0\0\0\0\0\0\0\0\0'. - عند استخدام
PreparedStatement#setBytes، سيتم تحويله إلىunhex('<hex_string>')ثم تُقرأ القيمة كنوعString. - تُخزَّن السلاسل النصية بترميز UTF-8.
أنواع التاريخ/الوقت
| نوع ClickHouse | متوافق مع الإصدار V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| Date | ❌ | DATE | java.sql.Date | DATE | java.time.LocalDate |
| Date32 | ❌ | DATE | java.sql.Date | DATE | java.time.LocalDate |
| DateTime | ❌ | TIMESTAMP | java.sql.Timestamp | TIMESTAMP_WITH_TIMEZONE | java.time.OffsetDateTime |
| DateTime64 | ❌ | TIMESTAMP | java.sql.Timestamp | TIMESTAMP_WITH_TIMEZONE | java.time.OffsetDateTime |
| Time | ✅ | TIME | java.sql.Time | نوع جديد/غير مدعوم | نوع جديد/غير مدعوم |
| Time64 | ✅ | TIME | java.sql.Time | نوع جديد/غير مدعوم | نوع جديد/غير مدعوم |
- يقتصر دعم
TimeوTime64في V2 على كونهما نوعين جديدين. - يُحوَّل
DateTimeوDateTime64إلىjava.sql.Timestampلتحقيق توافق أفضل مع JDBC.
أنواع Enum
| نوع ClickHouse | متوافق مع الإصدار V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| Enum | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String |
| Enum8 | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String |
| Enum16 | ✅ | VARCHAR | java.lang.String | OTHER | java.lang.String |
الأنواع المتداخلة
| نوع ClickHouse | متوافق مع الإصدار V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| مصفوفة | ❌ | ARRAY | java.sql.Array | ARRAY | Object[] أو مصفوفة من أنواع بدائية |
| Tuple | ❌ | OTHER | Object[] | STRUCT | java.sql.Struct |
| Map | ❌ | JAVA_OBJECT | java.util.Map | STRUCT | java.util.Map |
| Nested | ❌ | ARRAY | java.sql.Array | STRUCT | java.sql.Struct |
- في V2، يُعيَّن
Arrayإلىjava.sql.Arrayافتراضيًا لضمان التوافق مع JDBC. ويُجرى ذلك أيضًا لتوفير معلومات إضافية عن قيمة المصفوفة المُعادة. وهذا مفيد في type inference. - في V2، يطبّق
ArrayالطريقةgetResultSet()لإرجاعjava.sql.ResultSetبالمحتوى نفسه الموجود في المصفوفة الأصلية. - يستخدم V1
STRUCTمعMap، لكنه يعيد دائمًا الكائنjava.util.Map. ويعالج V2 ذلك من خلال تعيينMapإلىJAVA_OBJECT. - يستخدم V1
STRUCTمعTuple، لكنه يعيد دائمًا الكائنList<Object>. ويُعيّن V2TupleإلىOTHERويعيدObject[]افتراضيًا. - يقدّم V2
com.clickhouse.data.Tuple#Tupleلكتابة قيم tuple. وهذا يسهّل تحديد ما إذا كانت القيمة tuple أم مصفوفة. - لا يمكن استخدام
PreparedStatement#setBytesوResultSet#getBytesمع أنواع المجموعات. فقد صُمّمت هاتان الطريقتان للعمل مع السلاسل الثنائية. - عادةً ما يُستخدم
java.sql.Arrayلكتابة أنواعArrayوقراءتها. ويوفّر JDBC driver دعمًا كاملًا لذلك. - في V2، يُعيَّن
NestedإلىArrayويعرضه كمصفوفة من tuples. - يوفّر V2 دعمًا جزئيًا لـ
java.sql.Structلأنه يشبه جدًا النوعArrayولا يدعم key-value pairs. ويمكن استخدامStructلكتابة قيمTuple.
أنواع Geo
| نوع ClickHouse | متوافق مع الإصدار V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| Point | ✅ | OTHER | double[] | OTHER | double[] |
| Ring | ✅ | OTHER | double[][] | OTHER | double[][] |
| Polygon | ✅ | OTHER | double[][][] | OTHER | double[][][] |
| MultiPolygon | ✅ | OTHER | double[][][][] | OTHER | double[][][][] |
أنواع Nullable وLowCardinality
NullableوLowCardinalityنوعان خاصّان يغلّفان أنواعًا أخرى.- لا تطرأ أي تغييرات على هذين النوعين في V2.
الأنواع الخاصة
| نوع ClickHouse | متوافق مع الإصدار V1 | نوع JDBC (V2) | فئة Java (V2) | نوع JDBC (V1) | فئة Java (V1) |
|---|---|---|---|---|---|
| JSON | ❌ | OTHER | java.lang.String | غير مدعوم | غير مدعوم |
| AggregateFunction | ✅ | OTHER | (التمثيل الثنائي) | OTHER | (التمثيل الثنائي) |
| SimpleAggregateFunction | ✅ | (النوع المُغلَّف) | (الفئة المُغلَّفة) | (النوع المُغلَّف) | (الفئة المُغلَّفة) |
| UUID | ✅ | OTHER | java.util.UUID | VARCHAR | java.util.UUID |
| IPv4 | ✅ | OTHER | java.net.Inet4Address | VARCHAR | java.net.Inet4Address |
| IPv6 | ✅ | OTHER | java.net.Inet6Address | VARCHAR | java.net.Inet6Address |
| Dynamic | ❌ | OTHER | java.Object | غير مدعوم | غير مدعوم |
| Variant | ❌ | OTHER | java.Object | غير مدعوم | غير مدعوم |
- يستخدم V1 النوع
VARCHARمعUUID، لكنه يعيد دائمًا الكائنjava.util.UUID. ويعالج V2 هذا الأمر من خلال تعيينUUIDإلىOTHER، ثم يعيد الكائنjava.util.UUID. - يستخدم V1 النوع
VARCHARمعIPv4وIPv6، لكنه يعيد دائمًا الكائنينjava.net.Inet4Addressوjava.net.Inet6Address. ويعالج V2 هذا الأمر من خلال تعيينIPv4وIPv6إلىOTHER، ثم يعيد الكائنينjava.net.Inet4Addressوjava.net.Inet6Address. DynamicوVariantنوعان جديدان في V2. ولا يدعمهما V1.- يعتمد
JSONعلى النوعDynamic. لذلك فهو مدعوم فقط في V2. - يمكن قراءة قيم
IPv4وIPv6على هيئةbyte[]باستخدام الدالةgetBytes(columnIndex). ومع ذلك، يُوصى باستخدام الفئات المخصّصة لهذه الأنواع. - لا يدعم V2 قراءة عنوان IP على أنه من القيم العددية، لأن التحويل مُنفَّذ بصورة أفضل في فئات
InetAddress.
تغييرات البيانات الوصفية لقاعدة البيانات
- يستخدم V2 المصطلح
Schemaفقط لتسمية قواعد البيانات. أما المصطلحCatalogفمحجوز للاستخدام المستقبلي. - يعيد V2 القيمة
falseلكل منDatabaseMetaData.supportsTransactions()وDatabaseMetaData.supportsSavepoints(). سيتغير ذلك في الإصدارات القادمة. - في
DatabaseMetaData.getTypeInfo()، يُرجِع العمودانLITERAL_PREFIXوLITERAL_SUFFIXالآن القيمةnullلأنواع البيانات التي لا يُتوقع أن تحتوي على بادئات أو لاحقات (مثل الأنواع الرقمية). في V1، كانت هذه الأعمدة تُرجِع قيماً غيرnullلهذه الأنواع. ينبغي استخدام هذه الأعمدة عند إنشاء استعلامات SQL لوضع القيم الحرفية بين علامتي اقتباس بشكل صحيح وفقاً لنوع بياناتها.
يُنفِّذ clickhouse-jdbc واجهة JDBC القياسية. فبوصفه مبنيًا فوق clickhouse-client، يوفر ميزات إضافية كتعيين الأنواع المخصصة، ودعم المعاملات، وعبارات UPDATE وDELETE المتزامنة القياسية وغيرها؛ مما يجعله سهل الاستخدام مع التطبيقات والأدوات القديمة.
واجهة برمجة تطبيقات clickhouse-jdbc متزامنة، وتنطوي بشكل عام على تكاليف إضافية أعلى (كتحليل SQL وتعيين الأنواع/تحويلها وغير ذلك). يُنصح باستخدام clickhouse-client حين يكون الأداء أولويةً قصوى، أو إذا كنت تفضّل طريقة أكثر مباشرةً للوصول إلى ClickHouse.
متطلبات البيئة
- OpenJDK الإصدار 8 أو أحدث
الإعداد
{/* https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc */}
<dependency>
<groupId>com.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.7.2</version>
{/* استخدم ملف JAR شاملًا جميع التبعيات، وغيّر classifier إلى http للحصول على ملف JAR أصغر */}
<classifier>shaded-all</classifier>
</dependency>// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
// استخدم ملف JAR شاملًا جميع التبعيات، وغيّر classifier إلى http للحصول على ملف JAR أصغر
implementation("com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all")// https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc
// استخدم ملف JAR شاملًا جميع التبعيات، وغيّر classifier إلى http للحصول على ملف JAR أصغر
implementation 'com.clickhouse:clickhouse-jdbc:0.7.2:shaded-all'منذ الإصدار 0.5.0، نستخدم Apache HTTP Client المُضمَّن مع Client. نظرًا لعدم وجود إصدار مشترك من الحزمة، تحتاج إلى إضافة logger كتبعية.
{/* https://mvnrepository.com/artifact/org.slf4j/slf4j-api */}
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
implementation("org.slf4j:slf4j-api:2.0.16")// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
implementation 'org.slf4j:slf4j-api:2.0.16'الإعداد
فئة برنامج التشغيل: com.clickhouse.jdbc.ClickHouseDriver
صياغة URL: jdbc:(ch|clickhouse)[:<protocol>]://endpoint1[,endpoint2,...][/<database>][?param1=value1¶m2=value2][#tag1,tag2,...]، على سبيل المثال:
jdbc:ch://localhostيعادلjdbc:clickhouse:http://localhost:8123jdbc:ch:https://localhostيعادلjdbc:clickhouse:http://localhost:8443?ssl=true&sslmode=STRICTjdbc:ch:grpc://localhostيعادلjdbc:clickhouse:grpc://localhost:9100
خصائص الاتصال:
| الخاصية | القيمة الافتراضية | الوصف |
|---|---|---|
continueBatchOnError |
false |
ما إذا كان ينبغي متابعة معالجة الدفعات عند حدوث خطأ |
createDatabaseIfNotExist |
false |
ما إذا كان ينبغي إنشاء قاعدة البيانات إذا لم تكن موجودة |
custom_http_headers |
ترويسات HTTP مخصّصة مفصولة بفواصل، على سبيل المثال: User-Agent=client1,X-Gateway-Id=123 |
|
custom_http_params |
معلمات استعلام HTTP مخصّصة مفصولة بفواصل، على سبيل المثال: extremes=0,max_result_rows=100 |
|
nullAsDefault |
0 |
0 - التعامل مع قيمة null كما هي، مع إطلاق استثناء عند إدراج null في عمود غير قابل للقيم null؛ 1 - التعامل مع قيمة null كما هي، مع تعطيل التحقق من null عند الإدراج؛ 2 - استبدال null بالقيمة الافتراضية لنوع البيانات المقابل لكلٍّ من query وinsert |
jdbcCompliance |
true |
ما إذا كان سيتم دعم عمليتَي UPDATE/DELETE القياسيتين المتزامنتين والمعاملة الوهمية |
typeMappings |
خصّص التعيين بين نوع بيانات ClickHouse وصنف Java، مما سيؤثر على نتيجة كلٍّ من getColumnType() وgetObject(Class<>?>). على سبيل المثال: UInt128=java.lang.String,UInt256=java.lang.String |
|
wrapperObject |
false |
ما إذا كان يجب أن تُعيد getObject() java.sql.Array / java.sql.Struct عند استخدام Array / Tuple. |
ملاحظة: يُرجى الرجوع إلى إعدادات JDBC الخاصة لمزيد من المعلومات.
أنواع البيانات المدعومة
يدعم JDBC driver نفس تنسيقات البيانات التي تدعمها مكتبة العميل.
إنشاء اتصال
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()) {
}عبارة بسيطة
try (Connection conn = dataSource.getConnection(...);
Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("select * from numbers(50000)");
while(rs.next()) {
// ...
}
}إدراج
إنها أسهل في الاستخدام، لكن أداؤها أبطأ مقارنةً بدالة 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
}- توثيق الدالة input متى أمكن
الإدراج باستخدام العناصر النائبة
يُوصى بهذا الخيار فقط للإدراجات الصغيرة، إذ يستلزم تعبير SQL طويلاً (يُحلَّل على جانب العميل ويستهلك CPU & Memory):
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.time.LocalDateTime أو java.time.OffsetDateTime بدلاً من java.sql.Timestamp، وjava.time.LocalDate بدلاً من java.sql.Date.
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
// 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
يدعم موصّل JDBC الخاص بـ ClickHouse ثلاث مكتبات HTTP: HttpClient، وHttpURLConnection، وApache HttpClient.
يستخدم JDBC driver المكتبة HttpClient افتراضيًا. يمكنك تغيير مكتبة HTTP التي يستخدمها ClickHouse JDBC connector من خلال ضبط الخاصية التالية:
properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");فيما يلي قائمة كاملة بالقيم المقابلة:
| قيمة الخاصية | مكتبة HTTP |
|---|---|
| HTTP_CLIENT | HttpClient |
| HTTP_URL_CONNECTION | HttpURLConnection |
| APACHE_HTTP_CLIENT | أباتشي HttpClient |
الاتصال بـ ClickHouse عبر SSL
لإنشاء اتصال JDBC آمن بـ ClickHouse عبر SSL، تحتاج إلى ضبط خصائص JDBC لتشمل معاملات SSL. يتضمن ذلك عادةً تحديد خصائص SSL مثل sslmode وsslrootcert في JDBC URL أو كائن الخصائص.
خصائص SSL
| الاسم | القيمة الافتراضية | القيم الاختيارية | الوصف |
|---|---|---|---|
ssl |
false | true, false | ما إذا كان سيتم تمكين SSL/TLS للاتصال |
sslmode |
strict | strict, none | ما إذا كان يجب التحقق من شهادة SSL/TLS |
sslrootcert |
مسار شهادات الجذر لـ SSL/TLS | ||
sslcert |
مسار شهادة SSL/TLS | ||
sslkey |
مفتاح RSA بتنسيق PKCS#8 | ||
key_store_type |
JKS، PKCS12 | يحدّد نوع ملف KeyStore/TrustStore أو صيغته |
|
trust_store |
مسار ملف TrustStore |
||
key_store_password |
كلمة المرور المطلوبة للوصول إلى ملف KeyStore المحدد في تهيئة 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]قد تُعطّل هذه الأخطاء عملية إدراج البيانات وتؤثر على استقرار النظام. لمعالجة هذه المشكلة، يلزمك ضبط بعض إعدادات المهلة الزمنية في نظام تشغيل العميل.
Mac OS
على نظام Mac OS، يمكن تعديل الإعدادات التالية لحل المشكلة:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1
Linux
على Linux، قد لا تكفي الإعدادات المكافئة وحدها لحل المشكلة. إذ تستلزم الاختلافات في طريقة تعامل Linux مع إعدادات keep-alive للمقبس اتخاذ خطوات إضافية. اتبع الخطوات التالية:
- عدِّل معلمات نواة Linux التالية في
/etc/sysctl.confأو في ملف تكوين ذي صلة:
net.inet.tcp.keepidle: 60000net.inet.tcp.keepintvl: 45000net.inet.tcp.keepinit: 45000net.inet.tcp.keepcnt: 8net.inet.tcp.always_keepalive: 1net.ipv4.tcp_keepalive_intvl: 75net.ipv4.tcp_keepalive_probes: 9net.ipv4.tcp_keepalive_time: 60 (قد ترغب في خفض هذه القيمة من القيمة الافتراضية البالغة 300 ثانية)
- بعد تعديل معلمات النواة، طبِّق التغييرات عن طريق تشغيل الأمر التالي:
sudo sysctl -pبعد ضبط هذه الإعدادات، تأكد من أن العميل لديك يُفعّل خيار Keep Alive على الـ socket:
properties.setProperty("socket_keepalive", "true");بدلاً من ذلك، يمكنك إضافة المعاملات المكافئة إلى JDBC URL.
المهلة الافتراضية لـ socket والاتصال في JDBC driver هي 30 ثانية. يمكن زيادة هذه المهلة لدعم عمليات إدراج البيانات الكبيرة. استخدم الأسلوب options على ClickHouseClient مع خياري SOCKET_TIMEOUT وCONNECTION_TIMEOUT كما هو محدد في ClickHouseClientOption:
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();
}