createdDatabases = new ArrayList<>();
// Cold testcontainer first writes/reads are slower than a warm production node, so the
// per-future assertion timeout is generous.
private static final int FUTURE_TIMEOUT_SECONDS = 30;
@@ -78,7 +80,9 @@ class IoTDBTableAttributesDaoIT {
@Container
static final GenericContainer> IOTDB =
- new GenericContainer<>(DockerImageName.parse("apache/iotdb:2.0.8-standalone"))
+ new GenericContainer<>(
+ DockerImageName.parse(
+ System.getProperty("iotdb.test.image", "apache/iotdb:2.0.11-standalone")))
.withExposedPorts(6667)
// IoTDB binds its client RPC service to dn_rpc_address (default 127.0.0.1); bind to all
// interfaces so the Testcontainers port-mapped session handshake succeeds.
@@ -636,12 +640,28 @@ private TestScope scope(String databasePrefix, String entityId) {
new TestEntityId(UUID.fromString(entityId), EntityType.DEVICE));
}
+ @AfterEach
+ void dropCreatedDatabases() throws Exception {
+ // Every test provisions its own database. Drop them once the test is done: the shared
+ // container has a fixed region memory budget, and leaving dozens of databases behind makes
+ // later tests in the class fail schema-region creation ("Total allocated memory for direct
+ // buffer ... is greater than limit mem cost") and time out on their first write.
+ try (ITableSessionPool pool = newPool(null);
+ ITableSession session = pool.getSession()) {
+ for (String database : createdDatabases) {
+ session.executeNonQueryStatement("DROP DATABASE IF EXISTS " + database);
+ }
+ }
+ }
+
private String uniqueDatabase(String prefix) {
// IoTDB caps database names at 64 chars; keep the per-test prefix short and append a trimmed
// UUID so the total length stays well within the limit.
String shortPrefix = prefix.length() > 12 ? prefix.substring(0, 12) : prefix;
String shortUuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
- return "tb_at_" + shortPrefix + "_" + shortUuid;
+ String database = "tb_at_" + shortPrefix + "_" + shortUuid;
+ createdDatabases.add(database);
+ return database;
}
private TestAttributeKvEntry attr(long lastUpdateTs, String key, KvEntry value) {
diff --git a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableIngestionBenchmarkIT.java b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableIngestionBenchmarkIT.java
index 708d6b4b..e0187b00 100644
--- a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableIngestionBenchmarkIT.java
+++ b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableIngestionBenchmarkIT.java
@@ -24,6 +24,7 @@
import org.apache.iotdb.session.pool.TableSessionPoolBuilder;
import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -62,7 +63,7 @@
* TC-1 is defined with two profiles. This class is the smoke profile: a local, fast,
* JUnit-driven run that exercises the real {@link IoTDBTableTimeseriesDao#save} path (bounded queue
* → single flush worker → multi-row {@code Tablet} insert → real IoTDB) against the
- * same {@code apache/iotdb:2.0.8-standalone} Testcontainer the functional ITs use, then reports
+ * same {@code apache/iotdb:2.0.11-standalone} Testcontainer the functional ITs use, then reports
* records/sec, error rate, and writer stats.
*
*
The >10K writes/sec headline target is the FULL profile number on a dedicated host.
@@ -79,6 +80,7 @@
@Tag("integration")
@Testcontainers(disabledWithoutDocker = true)
class IoTDBTableIngestionBenchmarkIT {
+ private final List createdDatabases = new ArrayList<>();
private static final Logger LOG = LoggerFactory.getLogger(IoTDBTableIngestionBenchmarkIT.class);
// Smoke sizing: concurrency mirrors the TC-1 design (50 concurrent threads) but the total row
@@ -110,7 +112,9 @@ class IoTDBTableIngestionBenchmarkIT {
@Container
static final GenericContainer> IOTDB =
- new GenericContainer<>(DockerImageName.parse("apache/iotdb:2.0.8-standalone"))
+ new GenericContainer<>(
+ DockerImageName.parse(
+ System.getProperty("iotdb.test.image", "apache/iotdb:2.0.11-standalone")))
.withExposedPorts(6667)
// IoTDB binds its client RPC service to dn_rpc_address (default 127.0.0.1), so it would
// only listen on the container loopback and reject the Testcontainers port-mapped session
@@ -400,10 +404,26 @@ private BenchmarkScope scope() {
uniqueDatabase(), new TenantId(UUID.fromString("55555555-5555-5555-5555-555555555501")));
}
+ @AfterEach
+ void dropCreatedDatabases() throws Exception {
+ // Every test provisions its own database. Drop them once the test is done: the shared
+ // container has a fixed region memory budget, and leaving dozens of databases behind makes
+ // later tests in the class fail schema-region creation ("Total allocated memory for direct
+ // buffer ... is greater than limit mem cost") and time out on their first write.
+ try (ITableSessionPool pool = newPool(null);
+ ITableSession session = pool.getSession()) {
+ for (String database : createdDatabases) {
+ session.executeNonQueryStatement("DROP DATABASE IF EXISTS " + database);
+ }
+ }
+ }
+
private String uniqueDatabase() {
// IoTDB caps database names at 64 chars; keep the prefix short and append a trimmed UUID.
String shortUuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
- return "tb_bench_tc1_" + shortUuid;
+ String database = "tb_bench_tc1_" + shortUuid;
+ createdDatabases.add(database);
+ return database;
}
private static java.util.concurrent.ThreadFactory saverThreadFactory() {
diff --git a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableLatestDaoIT.java b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableLatestDaoIT.java
index a4c329ad..880ea2a5 100644
--- a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableLatestDaoIT.java
+++ b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableLatestDaoIT.java
@@ -24,6 +24,7 @@
import org.apache.iotdb.session.pool.TableSessionPoolBuilder;
import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
@@ -64,6 +65,7 @@
@Tag("integration")
@Testcontainers(disabledWithoutDocker = true)
class IoTDBTableLatestDaoIT {
+ private final List createdDatabases = new ArrayList<>();
// Cold testcontainer first writes/reads are slower than a warm production node, so the
// per-future assertion timeout is generous; production throughput is covered elsewhere.
private static final int FUTURE_TIMEOUT_SECONDS = 30;
@@ -73,7 +75,9 @@ class IoTDBTableLatestDaoIT {
@Container
static final GenericContainer> IOTDB =
- new GenericContainer<>(DockerImageName.parse("apache/iotdb:2.0.8-standalone"))
+ new GenericContainer<>(
+ DockerImageName.parse(
+ System.getProperty("iotdb.test.image", "apache/iotdb:2.0.11-standalone")))
.withExposedPorts(6667)
// IoTDB binds its client RPC service to dn_rpc_address (default 127.0.0.1); bind to all
// interfaces so the Testcontainers port-mapped session handshake succeeds.
@@ -1096,12 +1100,28 @@ private TestScope scope(String databasePrefix, String tenantId, String entityId)
new TestEntityId(UUID.fromString(entityId), EntityType.DEVICE));
}
+ @AfterEach
+ void dropCreatedDatabases() throws Exception {
+ // Every test provisions its own database. Drop them once the test is done: the shared
+ // container has a fixed region memory budget, and leaving dozens of databases behind makes
+ // later tests in the class fail schema-region creation ("Total allocated memory for direct
+ // buffer ... is greater than limit mem cost") and time out on their first write.
+ try (ITableSessionPool pool = newPool(null);
+ ITableSession session = pool.getSession()) {
+ for (String database : createdDatabases) {
+ session.executeNonQueryStatement("DROP DATABASE IF EXISTS " + database);
+ }
+ }
+ }
+
private String uniqueDatabase(String prefix) {
// IoTDB caps database names at 64 chars; keep the per-test prefix short and append a trimmed
// UUID so the total length stays well within the limit.
String shortPrefix = prefix.length() > 12 ? prefix.substring(0, 12) : prefix;
String shortUuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
- return "tb_lt_" + shortPrefix + "_" + shortUuid;
+ String database = "tb_lt_" + shortPrefix + "_" + shortUuid;
+ createdDatabases.add(database);
+ return database;
}
private TestTsKvEntry entry(long ts, String key, DataType dataType, Object value) {
diff --git a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesAggregationIT.java b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesAggregationIT.java
index aad140be..4bf100d4 100644
--- a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesAggregationIT.java
+++ b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesAggregationIT.java
@@ -23,6 +23,7 @@
import org.apache.iotdb.session.pool.TableSessionPoolBuilder;
import com.google.common.util.concurrent.ListenableFuture;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
@@ -55,18 +56,19 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
- * Real-Docker integration test that proves the IoTDB 2.0.8 Table Mode native three-argument {@code
+ * Real-Docker integration test that proves the IoTDB 2.0.11 Table Mode native three-argument {@code
* date_bin(ms, time, )} + {@code GROUP BY} time-bucketed aggregation path
* matches ThingsBoard 4.3.1.2's contract: buckets anchored at {@code startTs} (not epoch 1970),
* entries stamped at the bucket midpoint, every non-empty bucket returned ascending regardless of
* query order/limit, and typed COUNT semantics -- all against hand-computed expected values. Reuses
* the testcontainer harness from {@link IoTDBTableTimeseriesDaoIT}: {@code
- * apache/iotdb:2.0.8-standalone}, {@code dn_rpc_address=0.0.0.0}, exposed port 6667, short-prefix
+ * apache/iotdb:2.0.11-standalone}, {@code dn_rpc_address=0.0.0.0}, exposed port 6667, short-prefix
* unique database, schema bootstrap from {@code schema-iotdb-table.sql}.
*/
@Tag("integration")
@Testcontainers(disabledWithoutDocker = true)
class IoTDBTableTimeseriesAggregationIT {
+ private final List createdDatabases = new ArrayList<>();
private static final int FUTURE_TIMEOUT_SECONDS = 30;
private static final Duration IOTDB_STARTUP_TIMEOUT = Duration.ofMinutes(3);
private static final Duration IOTDB_READY_TIMEOUT = Duration.ofSeconds(60);
@@ -75,7 +77,9 @@ class IoTDBTableTimeseriesAggregationIT {
@Container
static final GenericContainer> IOTDB =
- new GenericContainer<>(DockerImageName.parse("apache/iotdb:2.0.8-standalone"))
+ new GenericContainer<>(
+ DockerImageName.parse(
+ System.getProperty("iotdb.test.image", "apache/iotdb:2.0.11-standalone")))
.withExposedPorts(6667)
.withEnv("dn_rpc_address", "0.0.0.0")
.waitingFor(Wait.forListeningPort().withStartupTimeout(IOTDB_STARTUP_TIMEOUT));
@@ -1109,10 +1113,26 @@ private TestScope scope(String databasePrefix, String tenantId, String entityId)
new TestEntityId(UUID.fromString(entityId), EntityType.DEVICE));
}
+ @AfterEach
+ void dropCreatedDatabases() throws Exception {
+ // Every test provisions its own database. Drop them once the test is done: the shared
+ // container has a fixed region memory budget, and leaving dozens of databases behind makes
+ // later tests in the class fail schema-region creation ("Total allocated memory for direct
+ // buffer ... is greater than limit mem cost") and time out on their first write.
+ try (ITableSessionPool pool = newPool(null);
+ ITableSession session = pool.getSession()) {
+ for (String database : createdDatabases) {
+ session.executeNonQueryStatement("DROP DATABASE IF EXISTS " + database);
+ }
+ }
+ }
+
private String uniqueDatabase(String prefix) {
String shortPrefix = prefix.length() > 12 ? prefix.substring(0, 12) : prefix;
String shortUuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
- return "tb_it_" + shortPrefix + "_" + shortUuid;
+ String database = "tb_it_" + shortPrefix + "_" + shortUuid;
+ createdDatabases.add(database);
+ return database;
}
private TestTsKvEntry entry(long ts, String key, DataType dataType, Object value) {
diff --git a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoIT.java b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoIT.java
index 46e0aad8..cc9bf778 100644
--- a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoIT.java
+++ b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoIT.java
@@ -25,6 +25,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -59,7 +60,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
- * Integration tests for the IoTDB Table Mode timeseries DAO against a real IoTDB 2.0.8 container:
+ * Integration tests for the IoTDB Table Mode timeseries DAO against a real IoTDB 2.0.11 container:
* the WRITE path (verified by reading the telemetry table back through raw table-session SQL) plus
* the RAW (non-aggregated) READ path, a millisecond time-bucketed aggregation smoke read and the
* DELETE path exercised through the DAO.
@@ -67,6 +68,7 @@
@Tag("integration")
@Testcontainers(disabledWithoutDocker = true)
class IoTDBTableTimeseriesDaoIT {
+ private final List createdDatabases = new ArrayList<>();
// Cold testcontainer first writes are slower than a warm production node, so the per-future
// assertion timeout is generous; production throughput is covered elsewhere, not here.
private static final int FUTURE_TIMEOUT_SECONDS = 30;
@@ -78,7 +80,9 @@ class IoTDBTableTimeseriesDaoIT {
@Container
static final GenericContainer> IOTDB =
- new GenericContainer<>(DockerImageName.parse("apache/iotdb:2.0.8-standalone"))
+ new GenericContainer<>(
+ DockerImageName.parse(
+ System.getProperty("iotdb.test.image", "apache/iotdb:2.0.11-standalone")))
.withExposedPorts(6667)
// IoTDB binds its client RPC service to dn_rpc_address (default 127.0.0.1), so it would
// only listen on the container loopback and reject the Testcontainers port-mapped session
@@ -101,7 +105,7 @@ static void restoreRawOnlyBackendProperties() {
}
/**
- * Pins the IoTDB 2.0.8 engine behavior that a database-bound table-session pool can bootstrap a
+ * Pins the IoTDB 2.0.11 engine behavior that a database-bound table-session pool can bootstrap a
* not-yet-existing database. If a future IoTDB image changes that behavior, this test fails
* before first boot breaks in production.
*/
@@ -628,12 +632,28 @@ private TestScope scope(String databasePrefix, String tenantId, String entityId)
new TestEntityId(UUID.fromString(entityId), EntityType.DEVICE));
}
+ @AfterEach
+ void dropCreatedDatabases() throws Exception {
+ // Every test provisions its own database. Drop them once the test is done: the shared
+ // container has a fixed region memory budget, and leaving dozens of databases behind makes
+ // later tests in the class fail schema-region creation ("Total allocated memory for direct
+ // buffer ... is greater than limit mem cost") and time out on their first write.
+ try (ITableSessionPool pool = newPool(null);
+ ITableSession session = pool.getSession()) {
+ for (String database : createdDatabases) {
+ session.executeNonQueryStatement("DROP DATABASE IF EXISTS " + database);
+ }
+ }
+ }
+
private String uniqueDatabase(String prefix) {
// IoTDB caps database names at 64 chars; keep the per-test prefix short and
// append a trimmed UUID so the total length stays well within the limit.
String shortPrefix = prefix.length() > 12 ? prefix.substring(0, 12) : prefix;
String shortUuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
- return "tb_it_" + shortPrefix + "_" + shortUuid;
+ String database = "tb_it_" + shortPrefix + "_" + shortUuid;
+ createdDatabases.add(database);
+ return database;
}
private int assertTelemetryRows(
diff --git a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoTest.java b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoTest.java
index 66fa857f..b3dc70a5 100644
--- a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoTest.java
+++ b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTimeseriesDaoTest.java
@@ -2746,9 +2746,9 @@ private boolean isNull(String column) {
case "sum_double" -> sumDouble == null;
case "min_long" -> minLong == null;
case "max_long" -> maxLong == null;
- // MAX(time) is NULL iff the bounded window matched zero rows (time is never null). A real
- // empty calendar bucket (emptyAggRow) returns one row with MAX(time) NULL; every other
- // bucket has matching data, so its max_ts is non-null.
+ // MAX(time) is NULL iff the bounded window matched zero rows (time is never null). A real
+ // empty calendar bucket (emptyAggRow) returns one row with MAX(time) NULL; every other
+ // bucket has matching data, so its max_ts is non-null.
case "max_ts" -> emptyAgg;
default -> true;
};
diff --git a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTtlIT.java b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTtlIT.java
index bd6cf4f0..fde0ac74 100644
--- a/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTtlIT.java
+++ b/iotdb-thingsboard-table/src/test/java/org/apache/iotdb/extras/thingsboard/table/IoTDBTableTtlIT.java
@@ -23,6 +23,7 @@
import org.apache.iotdb.isession.pool.ITableSessionPool;
import org.apache.iotdb.session.pool.TableSessionPoolBuilder;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
@@ -34,6 +35,7 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -42,8 +44,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
- * Validates the table-level TTL mechanism against real IoTDB 2.0.8. IoTDB Table Mode TTL is a table
- * property expressed in milliseconds; this IT proves the two operator-facing paths the README
+ * Validates the table-level TTL mechanism against real IoTDB 2.0.11. IoTDB Table Mode TTL is a
+ * table property expressed in milliseconds; this IT proves the two operator-facing paths the README
* documents work as described:
*
*
@@ -61,6 +63,7 @@
@Tag("integration")
@Testcontainers(disabledWithoutDocker = true)
class IoTDBTableTtlIT {
+ private final List createdDatabases = new ArrayList<>();
private static final Duration IOTDB_STARTUP_TIMEOUT = Duration.ofMinutes(3);
private static final Duration IOTDB_READY_TIMEOUT = Duration.ofSeconds(60);
private static final Duration IOTDB_READY_POLL_INTERVAL = Duration.ofMillis(500);
@@ -72,7 +75,9 @@ class IoTDBTableTtlIT {
@Container
static final GenericContainer> IOTDB =
- new GenericContainer<>(DockerImageName.parse("apache/iotdb:2.0.8-standalone"))
+ new GenericContainer<>(
+ DockerImageName.parse(
+ System.getProperty("iotdb.test.image", "apache/iotdb:2.0.11-standalone")))
.withExposedPorts(6667)
// IoTDB binds its client RPC service to dn_rpc_address (default 127.0.0.1); bind to all
// interfaces so the Testcontainers port-mapped session handshake succeeds.
@@ -208,9 +213,25 @@ private void awaitIoTDBReady(String database) throws Exception {
"IoTDB did not accept table-session statements within " + IOTDB_READY_TIMEOUT, lastFailure);
}
+ @AfterEach
+ void dropCreatedDatabases() throws Exception {
+ // Every test provisions its own database. Drop them once the test is done: the shared
+ // container has a fixed region memory budget, and leaving dozens of databases behind makes
+ // later tests in the class fail schema-region creation ("Total allocated memory for direct
+ // buffer ... is greater than limit mem cost") and time out on their first write.
+ try (ITableSessionPool pool = newPool(null);
+ ITableSession session = pool.getSession()) {
+ for (String database : createdDatabases) {
+ session.executeNonQueryStatement("DROP DATABASE IF EXISTS " + database);
+ }
+ }
+ }
+
private String uniqueDatabase(String prefix) {
String shortPrefix = prefix.length() > 12 ? prefix.substring(0, 12) : prefix;
String shortUuid = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
- return "tb_it_" + shortPrefix + "_" + shortUuid;
+ String database = "tb_it_" + shortPrefix + "_" + shortUuid;
+ createdDatabases.add(database);
+ return database;
}
}
diff --git a/mybatis-generator/README-zh.md b/mybatis-generator/README-zh.md
index c91e773a..bee354a6 100644
--- a/mybatis-generator/README-zh.md
+++ b/mybatis-generator/README-zh.md
@@ -19,38 +19,76 @@
-->
-# mybatis-generator-plugin
+# IoTDB MyBatis Generator 插件
-- 把该项目 `clone` 下来之后,在本地执行 `mvn clean install` 或者 `mvn clean deploy` (`deploy` 需要修改 `pom` 中的 `distributionManagement`)【已经上传 `Maven` 中央仓库,所以此步骤不在需要】
+为 IoTDB **表模型**生成 Model、Mapper 和 XML,提供批量插入、Lombok、序列化、Swagger 注释和 JDBC 类型映射。要求 **JDK 17+、IoTDB/JDBC 2.0.11、MBG 1.4.2**。插件版本属于 Extras,本仓库仍为 `2.0.4-SNAPSHOT`。
-- 在要生成的项目的 `pom` 文件中添加如下配置:
+## 安装与配置
+
+在仓库根目录安装生成插件和运行时适配:
+
+```sh
+mvn -pl mybatis-generator,mybatis-support -am clean install
+```
+
+`mvn package -Pwith-mybatis` 还会生成 `distributions/target/apache-iotdb--mybatis-generator-plugin-bin.zip`,其中同时打包生成插件和 `mybatis-support` 运行时 jar。
+
+生成时,`mybatis-generator-maven-plugin:1.4.2` 的 `dependencies` 中必须包含:
+
+| 依赖 | 版本 |
+|---|---|
+| `org.apache.iotdb:mybatis-generator-plugin` | `2.0.4-SNAPSHOT` |
+| `org.apache.iotdb:iotdb-jdbc` | `2.0.11` |
+
+无需配置机器相关的 `classPathEntry`。应用运行时还要依赖 `org.apache.iotdb:mybatis-support:2.0.4-SNAPSHOT` 并[注册查询拦截器](../mybatis-support/README.md)。2.0.11 表模型驱动的 prepared query 路径需要这一适配。
+
+参考[完整生成配置](../examples/mybatis-generator/src/main/resources/generatorConfig.xml),设置 JDBC URL、凭据、表名及输出路径。先创建表,再从应用项目目录执行:
+
+```sh
+mvn mybatis-generator:generate
+```
+
+生成会读取真实元数据并可能覆盖已有文件;检查差异后再使用。
+
+## 键、类型与 SQL 语义
```xml
-
-
-
- org.mybatis.generator
- mybatis-generator-maven-plugin
- 1.4.2
-
-
- org.apache.iotdb
- mybatis-generator-plugin
- 2.0.3
-
-
-
- true
- true
- src/main/resources/generatorConfig.xml
-
-
-
-
+
+
+
+
+
```
-- `configurationFile` 配置 `generatorConfig.xml` 文件的位置,其内容在本项目的 `src/main/resources` 有一个模板供参考,`copy` 其内容放到相应的位置
+- 行键由 **TIME 与全部 TAG** 组成,`virtualKeyColumns` 必须与表结构一致。`IoTDBKeyPlugin` 在查询、删除的键谓词中为 NULL TAG 生成 `IS NULL`。
+- `IoTDBJavaTypeResolver` 将 TIMESTAMP 映射为 **Long**;数值单位跟随服务端 ms/us/ns,适配层不转换单位。FLOAT 配置为 `java.lang.Float`。不要用 `Date` 承接高精度时间戳。
+- DATE 使用 `LocalDate + IoTDBLocalDateTypeHandler`,BLOB 使用 `byte[] + IoTDBBlobTypeHandler`。通过 `columnOverride` 同时生成参数及结果映射,详见[运行时适配文档](../mybatis-support/README.md)。
+- 设置 `enableUpdateByPrimaryKey=false` 和 `enableUpdateByExample=false`。2.0.11 的 UPDATE 只支持 ATTRIBUTE,且谓词中不能出现 `time`,MBG 按主键生成的 UPDATE 无法执行;若仍开启,`IoTDBKeyPlugin` 会跳过这些语句并输出生成警告。修改 FIELD 要按相同行键执行 INSERT。省略或为 NULL 的 FIELD 不会清除已有值。
+- ATTRIBUTE 属于设备,更新会影响该设备所有时间点。普通关系数据库的通用 UPDATE 不能直接套用。
+- 批量 SQL 保留 MBG 的标识符转义和 TypeHandler。保留字使用 `delimitIdentifiers` / `delimitAllColumns`。
+- 示例使用 `ignoreQualifiersAtRuntime=true`:生成读取配置中的 schema,运行时由 JDBC URL 选择数据库。
+- Lombok/Swagger 注解依赖需要放在应用 classpath 中;Lombok 同时覆盖主键类、普通模型和 BLOB 子类。
+
+## 批量行为与迁移
+
+调用 `mapper.batchInsert(records)`。默认方法先校验整份列表,再按 **500 行**拆分 SQL;插件属性 `batchSize` 必须为正整数。宽表或大 BLOB 应减小批次,它限制行数,不限制字节数。
+
+| 输入/配置 | 行为 |
+|---|---|
+| 空列表 | 返回 0,不访问数据库 |
+| NULL 列表或 NULL 元素 | 写入前抛出 `IllegalArgumentException` |
+| identity/autoincrement/generated-always 列 | 不参与批量插入 |
+| `incrementField` | 保持旧版排除列兼容 |
+| 关闭 INSERT | 不生成批量方法/XML |
+| 所有列都被排除 | 不生成批量方法/XML,并报告警告 |
+| BLOB 分层模型 | 使用包含全部字段的模型类型 |
+
+`batchInsertRows(@Param("records") List)` 是内部单批映射方法,直接调用会绕过校验和拆批。**从旧版升级时,必须同时重新生成 Mapper 接口和 XML**,不能只替换其中一个。
+
+示例启用 `UnmergeableXmlMappersPlugin`,在关闭注释时也直接替换生成 XML,避免重复生成累积同名语句。手写 SQL 应单独保存,或在重新生成前检查并保留。
+
+2.0.11 JDBC 的影响行数为 -1(未知)。任一批次返回未知时,总返回值保持 -1,否则累加已知数量;不要用 `result > 0` 判断成功。SQL 失败会抛异常,但之前的批次可能已写入。IoTDB JDBC 不提供事务回滚,`@Transactional` 不能把多次写入变成原子操作。
-- 修改 `generatorConfig.xml` 中 想用的内容,主要是:`jdbcConnection`、`javaModelGenerator`、`sqlMapGenerator`、`javaClientGenerator`、`table`
+实际查询应限制时间/设备范围和返回条数。检入的简单示例将 `selectAll` 限制为 1000 条;MBG 标准模板不会自动添加该限制,重新生成后应检查查询范围。
-- 在项目的 `pom` 所在的地方执行命令:`mvn mybatis-generator:generate` 生成相应的 `Java` 类和 `mapper` 文件
+集成测试和已知服务端限制参见[可运行示例](../examples/mybatis-generator/README.md)和[MyBatis-Plus 示例](../examples/mybatisplus-generator/README.md)。本插件与 MyBatis-Plus 的生成器是两套独立集成。
diff --git a/mybatis-generator/README.md b/mybatis-generator/README.md
index 4f60b9c2..713164bd 100644
--- a/mybatis-generator/README.md
+++ b/mybatis-generator/README.md
@@ -19,38 +19,98 @@
-->
-# mybatis-generator-plugin
+# IoTDB MyBatis Generator plugin
-- After 'clone' the project, execute 'mvn clean install' or 'mvn clean deploy' locally ('deploy' needs to modify 'distributionManagement' in 'pom'). This step is not necessary as it has already been uploaded to the Maven central repository
+Generates MyBatis models, mapper interfaces and XML for the IoTDB **table model**. Features include `batchInsert`, Lombok models, serializable models, Swagger comments and configurable JDBC-to-Java type mapping.
-- Add the following configuration to the 'pom' file of the project to be generated:
+## Prerequisites
+
+Use JDK 17+, IoTDB/JDBC 2.0.11, and MyBatis Generator 1.4.2. The plugin artifact belongs to Extras (`2.0.4-SNAPSHOT` in this checkout); its version does not track the IoTDB driver version.
+
+From the repository root:
+
+```sh
+mvn -pl mybatis-generator,mybatis-support -am clean install
+```
+
+`mvn package -Pwith-mybatis` additionally builds `distributions/target/apache-iotdb--mybatis-generator-plugin-bin.zip`, which bundles the generator plugin jar together with the `mybatis-support` runtime jar.
+
+## Configure the generator
+
+Both the plugin and JDBC driver must be in the generator's plugin classloader. No absolute `classPathEntry` or manually copied JDBC jar is needed:
+
+```xml
+
+ org.mybatis.generator
+ mybatis-generator-maven-plugin
+ 1.4.2
+
+
+ org.apache.iotdb
+ mybatis-generator-plugin
+ 2.0.4-SNAPSHOT
+
+
+ org.apache.iotdb
+ iotdb-jdbc
+ 2.0.11
+
+
+
+ src/main/resources/generatorConfig.xml
+ true
+ true
+
+
+```
+
+Use the [example configuration](../examples/mybatis-generator/src/main/resources/generatorConfig.xml) as a starting point. Set the JDBC URL to `jdbc:iotdb://127.0.0.1:6667/test?sql_dialect=table`, update credentials, output packages and table names, and create the target database/table before generation.
+
+Run from the consuming project's directory:
+
+```sh
+mvn mybatis-generator:generate
+```
+
+Generation reads database metadata and can overwrite generated sources. Review the diff before incorporating output into application code.
+
+## IoTDB-specific mapping
+
+Applications also need [mybatis-support](../mybatis-support/README.md), including its query interceptor and DATE/BLOB handlers. The query interceptor is required for MyBatis's prepared query path with the 2.0.11 table driver.
```xml
-
-
-
- org.mybatis.generator
- mybatis-generator-maven-plugin
- 1.4.2
-
-
- org.apache.iotdb
- mybatis-generator-plugin
- 2.0.3
-
-
-
- true
- true
- src/main/resources/generatorConfig.xml
-
-
-
-
+
+
+
+
+
```
-- The location of the `configurationFile` configuration `generatorConfig. xml` file can be found in the `src/main/resources` template of this project for reference` Copy its content and place it in the corresponding location
+- `IoTDBJavaTypeResolver` maps TIMESTAMP to **Long**. Values use the server's configured ms/us/ns precision without conversion. Set `jdbcType.FLOAT=java.lang.Float`; do not map high-precision timestamps to `Date`.
+- The logical row identity is **TIME plus every TAG**. Keep `virtualKeyColumns` synchronized with the real schema; `IoTDBKeyPlugin` emits `IS NULL` for nullable TAG components in generated SELECT/DELETE predicates. ATTRIBUTE and FIELD columns are not keys.
+- Disable `enableUpdateByPrimaryKey` and `enableUpdateByExample`. IoTDB 2.0.11 UPDATE changes ATTRIBUTE columns only and rejects `time` in its predicate, so MBG's key-based UPDATE statements cannot run; `IoTDBKeyPlugin` drops them and reports a generator warning if they are left enabled. To change FIELD values, INSERT the same key and the desired fields; omitted/null fields do not erase existing values. ATTRIBUTE updates affect the device across timestamps.
+- Add DATE/BLOB `columnOverride` entries from [runtime support](../mybatis-support/README.md), so the same handlers apply to inserts, batch parameters and result maps.
+- Use `delimitIdentifiers` / `delimitAllColumns` for SQL identifiers requiring quotes. Batch SQL uses MBG's formatting helpers and preserves configured handlers and escaping.
+- Lombok/Swagger plugins require the corresponding annotation dependencies in the consuming application. Lombok is applied to primary-key and BLOB model classes as well as base records.
+
+The examples use `ignoreQualifiersAtRuntime=true`: generation reads the configured schema, while runtime SQL uses the database selected by the JDBC URL.
+
+## Batch behavior and migration
+
+Call `mapper.batchInsert(records)`. The generated default method validates the entire list before writing and splits it into at most **500 rows** per SQL statement. `batchSize` must be a positive integer; lower it for wide rows, large BLOBs or server request limits. This is a row limit, not a byte-size limit.
+
+- Empty list: returns 0 and issues no SQL.
+- Null list or null element: throws `IllegalArgumentException` before any chunk is sent.
+- Identity, autoincrement, generated-always and legacy `incrementField` columns are excluded.
+- Disabled inserts or a table with no insertable columns produce no batch method/statement; the latter reports a generation warning.
+- Models with a separate BLOB subclass use the all-fields type.
+
+`batchInsertRows(@Param("records") List)` is the internal mapped statement. **Regenerate the mapper interface and XML together** when migrating from the previous mapped `batchInsert` method. Calling the internal helper directly bypasses validation and chunking.
+
+The sample configurations enable `UnmergeableXmlMappersPlugin` so reruns replace generated XML even when comments are suppressed, instead of accumulating duplicate statements. Preserve handwritten SQL separately or review it before regeneration.
+
+The driver reports affected-row count as -1 (unknown); the public batch method preserves -1 if any chunk has an unknown count, otherwise it sums known counts. SQL failures propagate as exceptions. Earlier chunks may already have been written when a later chunk fails: IoTDB JDBC 2.0.11 does not implement rollback, and MyBatis/Spring transaction annotations cannot make the batch atomic.
-- Modify the content you want to use in 'generatorConfig. xml', mainly by:` jdbcConnection`、`javaModelGenerator`、`sqlMapGenerator`、`javaClientGenerator`、`table`
+Bound generated `selectAll`/Example queries in application code with time/device predicates and a limit. The checked-in simple example caps `selectAll` at 1000; MBG's standard templates do not add this cap automatically.
-- Execute the command at the location of the 'pom' in the project:` Mvn mybatis generator: generate generates corresponding Java classes and mapper files
+See the [runnable MyBatis example](../examples/mybatis-generator/README.md) and [MyBatis-Plus example](../examples/mybatisplus-generator/README.md) for integration tests and known server limitations. This plugin integrates with MyBatis Generator; the MyBatis-Plus example uses its own generator.
diff --git a/mybatis-generator/pom.xml b/mybatis-generator/pom.xml
index 26a108e7..7df748dd 100644
--- a/mybatis-generator/pom.xml
+++ b/mybatis-generator/pom.xml
@@ -19,10 +19,21 @@
+
+ org.mybatis
+ mybatis
+ 3.5.19
+ test
+
org.mybatis.generator
mybatis-generator-core
1.4.2
+
+ junit
+ junit
+ test
+
diff --git a/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/BatchInsertPlugin.java b/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/BatchInsertPlugin.java
index cb24f3cd..f3cf80ca 100644
--- a/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/BatchInsertPlugin.java
+++ b/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/BatchInsertPlugin.java
@@ -29,39 +29,88 @@
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
+import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
+import java.util.stream.Collectors;
public class BatchInsertPlugin extends PluginAdapter {
+ private int batchSize = 500;
+ private List warnings;
@Override
public boolean clientGenerated(Interface interfaze, IntrospectedTable introspectedTable) {
- batchInsertMethod(interfaze, introspectedTable);
+ if (canInsert(introspectedTable)) {
+ batchInsertMethod(interfaze, introspectedTable);
+ }
return super.clientGenerated(interfaze, introspectedTable);
}
@Override
public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
- addBatchInsertXml(document, introspectedTable);
+ if (canInsert(introspectedTable)) {
+ addBatchInsertXml(document, introspectedTable);
+ }
return super.sqlMapDocumentGenerated(document, introspectedTable);
}
@Override
- public boolean validate(List list) {
- return true;
+ public boolean validate(List warnings) {
+ this.warnings = warnings;
+ try {
+ batchSize = Integer.parseInt(properties.getProperty("batchSize", "500"));
+ if (batchSize > 0) {
+ return true;
+ }
+ } catch (NumberFormatException ignored) {
+ // Report the invalid plugin setting through MBG's configuration warnings.
+ }
+ warnings.add("BatchInsertPlugin: batchSize must be a positive integer");
+ return false;
+ }
+
+ @Override
+ public void initialized(IntrospectedTable table) {
+ if (table.getTableConfiguration().isInsertStatementEnabled()
+ && insertColumns(table).isEmpty()) {
+ warnings.add("BatchInsertPlugin: no insertable columns in " + table.getFullyQualifiedTable());
+ }
+ }
+
+ private boolean canInsert(IntrospectedTable table) {
+ return table.getTableConfiguration().isInsertStatementEnabled()
+ && !insertColumns(table).isEmpty();
+ }
+
+ private List insertColumns(IntrospectedTable table) {
+ String incrementField = table.getTableConfigurationProperty("incrementField");
+ return table.getAllColumns().stream()
+ .filter(
+ column ->
+ !column.isIdentity()
+ && !column.isAutoIncrement()
+ && !column.isGeneratedAlways()
+ && !column.isGeneratedColumn())
+ .filter(
+ column ->
+ incrementField == null
+ || !column.getActualColumnName().equalsIgnoreCase(incrementField.trim()))
+ .collect(Collectors.toList());
}
private void batchInsertMethod(Interface interfaze, IntrospectedTable introspectedTable) {
Set importedTypes = new TreeSet<>();
importedTypes.add(FullyQualifiedJavaType.getNewListInstance());
- importedTypes.add(new FullyQualifiedJavaType(introspectedTable.getBaseRecordType()));
+ importedTypes.add(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Param"));
+ FullyQualifiedJavaType recordType = introspectedTable.getRules().calculateAllFieldsClass();
+ importedTypes.add(recordType);
Method ibsmethod = new Method("batchInsert");
ibsmethod.setVisibility(JavaVisibility.PUBLIC);
- ibsmethod.setAbstract(true);
+ ibsmethod.setDefault(true);
FullyQualifiedJavaType ibsReturnType = FullyQualifiedJavaType.getIntInstance();
@@ -70,41 +119,47 @@ private void batchInsertMethod(Interface interfaze, IntrospectedTable introspect
ibsmethod.setName("batchInsert");
FullyQualifiedJavaType paramType = FullyQualifiedJavaType.getNewListInstance();
- FullyQualifiedJavaType paramListType;
- paramListType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
- paramType.addTypeArgument(paramListType);
- ibsmethod.addParameter(new Parameter(paramType, "records", "@Param(\"records\")"));
+ paramType.addTypeArgument(recordType);
+ ibsmethod.addParameter(new Parameter(paramType, "records"));
+ ibsmethod.addBodyLine(
+ "if (records == null || records.stream().anyMatch(java.util.Objects::isNull)) {");
+ ibsmethod.addBodyLine(
+ "throw new IllegalArgumentException(\"records and its elements must not be null\");");
+ ibsmethod.addBodyLine("}");
+ ibsmethod.addBodyLine("int result = 0;");
+ ibsmethod.addBodyLine("for (int start = 0; start < records.size();) {");
+ ibsmethod.addBodyLine("int end = start + Math.min(" + batchSize + ", records.size() - start);");
+ ibsmethod.addBodyLine("int count = batchInsertRows(records.subList(start, end));");
+ ibsmethod.addBodyLine("result = count < 0 || result < 0 ? -1 : result + count;");
+ ibsmethod.addBodyLine("start = end;");
+ ibsmethod.addBodyLine("}");
+ ibsmethod.addBodyLine("return result;");
interfaze.addImportedTypes(importedTypes);
interfaze.addMethod(ibsmethod);
+ Method rowsMethod = new Method("batchInsertRows");
+ rowsMethod.setVisibility(JavaVisibility.PUBLIC);
+ rowsMethod.setAbstract(true);
+ rowsMethod.setReturnType(FullyQualifiedJavaType.getIntInstance());
+ rowsMethod.addParameter(new Parameter(paramType, "records", "@Param(\"records\")"));
+ rowsMethod.addJavaDocLine("/** Internal single-batch statement; call batchInsert instead. */");
+ interfaze.addMethod(rowsMethod);
}
private void addBatchInsertXml(Document document, IntrospectedTable introspectedTable) {
- List columns = introspectedTable.getAllColumns();
- String incrementField =
- introspectedTable.getTableConfiguration().getProperties().getProperty("incrementField");
- if (incrementField != null) {
- incrementField = incrementField.toUpperCase();
- }
+ List columns = insertColumns(introspectedTable);
XmlElement insertBatchElement = new XmlElement("insert");
- insertBatchElement.addAttribute(new Attribute("id", "batchInsert"));
- insertBatchElement.addAttribute(new Attribute("parameterType", "java.util.List"));
-
- StringBuilder sqlElement = new StringBuilder();
- StringBuilder javaPropertyAndDbType = new StringBuilder("(");
- for (IntrospectedColumn introspectedColumn : columns) {
- String columnName = introspectedColumn.getActualColumnName();
- if (!columnName.toUpperCase().equals(incrementField)) {
- sqlElement.append(columnName + ",\n ");
- javaPropertyAndDbType.append(
- "\n #{item."
- + introspectedColumn.getJavaProperty()
- + ",jdbcType="
- + introspectedColumn.getJdbcTypeName()
- + "},");
- }
- }
+ insertBatchElement.addAttribute(new Attribute("id", "batchInsertRows"));
+ context.getCommentGenerator().addComment(insertBatchElement);
+ String columnNames =
+ columns.stream()
+ .map(MyBatis3FormattingUtilities::getEscapedColumnName)
+ .collect(Collectors.joining(", "));
+ String parameters =
+ columns.stream()
+ .map(column -> MyBatis3FormattingUtilities.getParameterClause(column, "item."))
+ .collect(Collectors.joining(", "));
XmlElement foreachElement = new XmlElement("foreach");
foreachElement.addAttribute(new Attribute("collection", "records"));
@@ -114,18 +169,11 @@ private void addBatchInsertXml(Document document, IntrospectedTable introspected
insertBatchElement.addElement(
new TextElement(
"insert into "
- + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()
- + " ("));
- insertBatchElement.addElement(
- new TextElement(
- " " + sqlElement.delete(sqlElement.lastIndexOf(","), sqlElement.length()).toString()));
- insertBatchElement.addElement(new TextElement(") values "));
- foreachElement.addElement(
- new TextElement(
- javaPropertyAndDbType
- .delete(javaPropertyAndDbType.length() - 1, javaPropertyAndDbType.length())
- .append("\n )")
- .toString()));
+ + introspectedTable.getFullyQualifiedTableNameAtRuntime()
+ + " ("
+ + columnNames
+ + ") values"));
+ foreachElement.addElement(new TextElement("(" + parameters + ")"));
insertBatchElement.addElement(foreachElement);
document.getRootElement().addElement(insertBatchElement);
diff --git a/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/IoTDBKeyPlugin.java b/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/IoTDBKeyPlugin.java
new file mode 100644
index 00000000..2a1a0d64
--- /dev/null
+++ b/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/IoTDBKeyPlugin.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis.plugin;
+
+import org.mybatis.generator.api.IntrospectedColumn;
+import org.mybatis.generator.api.IntrospectedTable;
+import org.mybatis.generator.api.PluginAdapter;
+import org.mybatis.generator.api.dom.xml.Attribute;
+import org.mybatis.generator.api.dom.xml.TextElement;
+import org.mybatis.generator.api.dom.xml.XmlElement;
+import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
+import org.mybatis.generator.config.TableConfiguration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapts MBG's key-based statements to the IoTDB table model: nullable TAG components of the TIME +
+ * TAG key become {@code IS NULL} predicates in SELECT/DELETE, and the UPDATE statements are dropped
+ * because IoTDB UPDATE accepts neither {@code time} in the predicate nor FIELD columns in SET.
+ */
+public class IoTDBKeyPlugin extends PluginAdapter {
+ // MBG hands the shared warnings list to validate() before any table callback.
+ private List warnings = new ArrayList<>();
+
+ @Override
+ public boolean validate(List warnings) {
+ this.warnings = warnings;
+ return true;
+ }
+
+ @Override
+ public void initialized(IntrospectedTable table) {
+ TableConfiguration configuration = table.getTableConfiguration();
+ if (configuration.isUpdateByPrimaryKeyStatementEnabled()
+ || configuration.isUpdateByExampleStatementEnabled()) {
+ warnings.add(
+ "IoTDBKeyPlugin: not generating UPDATE statements for "
+ + table.getFullyQualifiedTable()
+ + "; IoTDB UPDATE cannot use time in the predicate or FIELD columns in SET. INSERT"
+ + " the same key to change FIELD values and set enableUpdateByPrimaryKey and"
+ + " enableUpdateByExample to false.");
+ configuration.setUpdateByPrimaryKeyStatementEnabled(false);
+ configuration.setUpdateByExampleStatementEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean sqlMapSelectByPrimaryKeyElementGenerated(
+ XmlElement element, IntrospectedTable table) {
+ replaceKeyPredicate(element, table);
+ return true;
+ }
+
+ @Override
+ public boolean sqlMapDeleteByPrimaryKeyElementGenerated(
+ XmlElement element, IntrospectedTable table) {
+ replaceKeyPredicate(element, table);
+ return true;
+ }
+
+ private void replaceKeyPredicate(XmlElement element, IntrospectedTable table) {
+ // MBG emits the key WHERE clause as trailing text elements.
+ int where = -1;
+ for (int i = 0; i < element.getElements().size(); i++) {
+ if (element.getElements().get(i) instanceof TextElement
+ && ((TextElement) element.getElements().get(i))
+ .getContent()
+ .trim()
+ .startsWith("where ")) {
+ where = i;
+ break;
+ }
+ }
+ if (where < 0) return;
+ element.getElements().subList(where, element.getElements().size()).clear();
+ XmlElement predicate = new XmlElement("where");
+ for (IntrospectedColumn column : table.getPrimaryKeyColumns()) {
+ String name = MyBatis3FormattingUtilities.getEscapedColumnName(column);
+ String parameter = MyBatis3FormattingUtilities.getParameterClause(column);
+ if ("time".equalsIgnoreCase(column.getActualColumnName())) {
+ predicate.addElement(new TextElement("AND " + name + " = " + parameter));
+ } else {
+ XmlElement choose = new XmlElement("choose");
+ XmlElement when = new XmlElement("when");
+ when.addAttribute(new Attribute("test", column.getJavaProperty() + " != null"));
+ when.addElement(new TextElement("AND " + name + " = " + parameter));
+ XmlElement otherwise = new XmlElement("otherwise");
+ otherwise.addElement(new TextElement("AND " + name + " IS NULL"));
+ choose.addElement(when);
+ choose.addElement(otherwise);
+ predicate.addElement(choose);
+ }
+ }
+ element.addElement(predicate);
+ }
+}
diff --git a/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/LombokPlugin.java b/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/LombokPlugin.java
index 650d4ec6..9e434430 100644
--- a/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/LombokPlugin.java
+++ b/mybatis-generator/src/main/java/org/apache/iotdb/mybatis/plugin/LombokPlugin.java
@@ -42,6 +42,18 @@ public boolean modelBaseRecordClassGenerated(
return true;
}
+ @Override
+ public boolean modelPrimaryKeyClassGenerated(
+ TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
+ return modelBaseRecordClassGenerated(topLevelClass, introspectedTable);
+ }
+
+ @Override
+ public boolean modelRecordWithBLOBsClassGenerated(
+ TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
+ return modelBaseRecordClassGenerated(topLevelClass, introspectedTable);
+ }
+
@Override
public boolean modelSetterMethodGenerated(
Method method,
diff --git a/mybatis-generator/src/main/resources/generatorConfig.xml b/mybatis-generator/src/main/resources/generatorConfig.xml
index 78b9d73c..5dd578a8 100644
--- a/mybatis-generator/src/main/resources/generatorConfig.xml
+++ b/mybatis-generator/src/main/resources/generatorConfig.xml
@@ -18,7 +18,7 @@
-->
-
+
@@ -29,6 +29,8 @@
value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>-->
+
+
@@ -53,7 +55,7 @@
-
+
diff --git a/mybatis-generator/src/test/java/org/apache/iotdb/mybatis/plugin/BatchInsertPluginTest.java b/mybatis-generator/src/test/java/org/apache/iotdb/mybatis/plugin/BatchInsertPluginTest.java
new file mode 100644
index 00000000..42e27e43
--- /dev/null
+++ b/mybatis-generator/src/test/java/org/apache/iotdb/mybatis/plugin/BatchInsertPluginTest.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis.plugin;
+
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.session.Configuration;
+import org.junit.Test;
+import org.mybatis.generator.api.IntrospectedColumn;
+import org.mybatis.generator.api.IntrospectedTable;
+import org.mybatis.generator.api.dom.DefaultJavaFormatter;
+import org.mybatis.generator.api.dom.DefaultXmlFormatter;
+import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
+import org.mybatis.generator.api.dom.java.Interface;
+import org.mybatis.generator.api.dom.java.JavaVisibility;
+import org.mybatis.generator.api.dom.xml.Attribute;
+import org.mybatis.generator.api.dom.xml.Document;
+import org.mybatis.generator.api.dom.xml.XmlElement;
+import org.mybatis.generator.codegen.mybatis3.IntrospectedTableMyBatis3Impl;
+import org.mybatis.generator.config.Context;
+import org.mybatis.generator.config.ModelType;
+import org.mybatis.generator.config.TableConfiguration;
+import org.mybatis.generator.internal.rules.FlatModelRules;
+import org.mybatis.generator.internal.rules.HierarchicalModelRules;
+
+import javax.tools.ToolProvider;
+
+import java.io.StringReader;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class BatchInsertPluginTest {
+ private final Context context = new Context(ModelType.FLAT);
+ private final List warnings = new ArrayList<>();
+ private final BatchInsertPlugin plugin = new BatchInsertPlugin();
+
+ private IntrospectedTable table() {
+ context.addProperty("beginningDelimiter", "\"");
+ context.addProperty("endingDelimiter", "\"");
+ plugin.setContext(context);
+ assertTrue(plugin.validate(warnings));
+ IntrospectedTable table =
+ new IntrospectedTableMyBatis3Impl() {
+ @Override
+ public String getFullyQualifiedTableNameAtRuntime() {
+ return "measurements";
+ }
+
+ @Override
+ public String getAliasedFullyQualifiedTableNameAtRuntime() {
+ return "measurements m";
+ }
+ };
+ TableConfiguration configuration = new TableConfiguration(context);
+ configuration.setTableName("measurements");
+ table.setTableConfiguration(configuration);
+ table.setContext(context);
+ table.setBaseRecordType("example.Measurement");
+ table.setRecordWithBLOBsType("example.MeasurementWithBLOBs");
+ table.setRules(new FlatModelRules(table));
+ column(table, "time", Types.TIMESTAMP, "TIMESTAMP", "java.lang.Long");
+ return table;
+ }
+
+ private IntrospectedColumn column(
+ IntrospectedTable table, String name, int type, String jdbc, String javaType) {
+ IntrospectedColumn column = new IntrospectedColumn();
+ column.setContext(context);
+ column.setActualColumnName(name);
+ column.setJavaProperty(name);
+ column.setJdbcType(type);
+ column.setJdbcTypeName(jdbc);
+ column.setFullyQualifiedJavaType(new FullyQualifiedJavaType(javaType));
+ table.addColumn(column);
+ return column;
+ }
+
+ private Interface mapper(IntrospectedTable table) {
+ Interface mapper = new Interface("example.MeasurementMapper");
+ mapper.setVisibility(JavaVisibility.PUBLIC);
+ plugin.clientGenerated(mapper, table);
+ return mapper;
+ }
+
+ private String xml(IntrospectedTable table) {
+ Document document =
+ new Document(
+ "-//mybatis.org//DTD Mapper 3.0//EN", "https://mybatis.org/dtd/mybatis-3-mapper.dtd");
+ XmlElement root = new XmlElement("mapper");
+ root.addAttribute(new Attribute("namespace", "example.MeasurementMapper"));
+ document.setRootElement(root);
+ plugin.sqlMapDocumentGenerated(document, table);
+ return new DefaultXmlFormatter().getFormattedContent(document);
+ }
+
+ @Test
+ public void generatedMapperCompilesAndGuardsAndSplitsBatches() throws Exception {
+ IntrospectedTable table = table();
+ Properties properties = new Properties();
+ properties.setProperty("batchSize", "2");
+ plugin.setProperties(properties);
+ assertTrue(plugin.validate(warnings));
+ Path dir = Files.createTempDirectory("iotdb-mbg-test-");
+ try {
+ Path source = Files.createDirectories(dir.resolve("example"));
+ Files.writeString(
+ source.resolve("Measurement.java"), "package example; public class Measurement {}");
+ Files.writeString(
+ source.resolve("MeasurementMapper.java"),
+ new DefaultJavaFormatter().getFormattedContent(mapper(table)));
+ assertEquals(
+ 0,
+ ToolProvider.getSystemJavaCompiler()
+ .run(
+ null,
+ null,
+ null,
+ "-proc:none",
+ "-classpath",
+ System.getProperty("java.class.path"),
+ "-d",
+ dir.toString(),
+ source.resolve("Measurement.java").toString(),
+ source.resolve("MeasurementMapper.java").toString()));
+ try (URLClassLoader loader =
+ new URLClassLoader(
+ new java.net.URL[] {dir.toUri().toURL()}, getClass().getClassLoader())) {
+ Class> mapper = loader.loadClass("example.MeasurementMapper");
+ Object row = loader.loadClass("example.Measurement").getConstructor().newInstance();
+ List chunks = new ArrayList<>();
+ java.util.concurrent.atomic.AtomicBoolean unknownCount =
+ new java.util.concurrent.atomic.AtomicBoolean();
+ Object instance =
+ Proxy.newProxyInstance(
+ loader,
+ new Class>[] {mapper},
+ (proxy, method, args) -> {
+ if (method.isDefault())
+ return InvocationHandler.invokeDefault(proxy, method, args);
+ chunks.add(((List>) args[0]).size());
+ return unknownCount.get() ? -1 : ((List>) args[0]).size();
+ });
+ java.lang.reflect.Method insert = mapper.getMethod("batchInsert", List.class);
+ assertEquals(0, insert.invoke(instance, List.of()));
+ assertTrue(chunks.isEmpty());
+ assertEquals(5, insert.invoke(instance, List.of(row, row, row, row, row)));
+ assertEquals(List.of(2, 2, 1), chunks);
+ unknownCount.set(true);
+ assertEquals(-1, insert.invoke(instance, List.of(row, row, row)));
+ chunks.clear();
+ InvocationTargetException failure =
+ assertThrows(
+ InvocationTargetException.class,
+ () -> insert.invoke(instance, Arrays.asList(row, row, row, null)));
+ assertTrue(failure.getCause() instanceof IllegalArgumentException);
+ assertTrue(chunks.isEmpty());
+ assertThrows(
+ InvocationTargetException.class, () -> insert.invoke(instance, new Object[] {null}));
+ }
+ } finally {
+ try (java.util.stream.Stream files = Files.walk(dir)) {
+ for (Path path :
+ files
+ .sorted(java.util.Comparator.reverseOrder())
+ .collect(java.util.stream.Collectors.toList())) {
+ Files.delete(path);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void preservesEscapingHandlersAndParameterCounts() {
+ IntrospectedTable table = table();
+ IntrospectedColumn order = column(table, "order", Types.BIGINT, "BIGINT", "java.lang.Long");
+ order.setColumnNameDelimited(true);
+ order.setTypeHandler("org.apache.ibatis.type.LongTypeHandler");
+ String xml = xml(table);
+ assertTrue(xml.contains("\"order\""));
+ assertTrue(xml.contains("typeHandler=org.apache.ibatis.type.LongTypeHandler"));
+ assertFalse(xml.contains("measurements m"));
+ Configuration configuration = new Configuration();
+ new XMLMapperBuilder(
+ new StringReader(xml), configuration, "mapper.xml", configuration.getSqlFragments())
+ .parse();
+ org.apache.ibatis.mapping.BoundSql sql =
+ configuration
+ .getMappedStatement("example.MeasurementMapper.batchInsertRows")
+ .getBoundSql(
+ Map.of(
+ "records",
+ List.of(Map.of("time", 1L, "order", 2L), Map.of("time", 3L, "order", 4L))));
+ assertEquals(4, sql.getParameterMappings().size());
+ assertTrue(sql.getSql().replaceAll("\\s+", " ").contains("(?, ?)"));
+ }
+
+ @Test
+ public void excludesGeneratedColumnsAndSkipsTablesWithoutInsertableColumns() {
+ IntrospectedTable table = table();
+ column(table, "identity", Types.BIGINT, "BIGINT", "java.lang.Long").setIdentity(true);
+ column(table, "generated", Types.BIGINT, "BIGINT", "java.lang.Long").setGeneratedAlways(true);
+ column(table, "automatic", Types.BIGINT, "BIGINT", "java.lang.Long").setAutoIncrement(true);
+ assertFalse(xml(table).contains("identity"));
+ assertFalse(xml(table).contains("item.generated"));
+ assertFalse(xml(table).contains("item.automatic"));
+ table.getTableConfiguration().addProperty("incrementField", "TIME");
+ plugin.initialized(table);
+ assertTrue(warnings.get(0).contains("no insertable columns"));
+ assertTrue(mapper(table).getMethods().isEmpty());
+ assertFalse(xml(table).contains(" warnings = new ArrayList<>();
+ private final IoTDBKeyPlugin plugin = new IoTDBKeyPlugin();
+
+ private IntrospectedTable table() {
+ context.addProperty("beginningDelimiter", "\"");
+ context.addProperty("endingDelimiter", "\"");
+ plugin.setContext(context);
+ assertTrue(plugin.validate(warnings));
+ IntrospectedTable table =
+ new IntrospectedTableMyBatis3Impl() {
+ @Override
+ public String getFullyQualifiedTableNameAtRuntime() {
+ return "mix";
+ }
+ };
+ TableConfiguration configuration = new TableConfiguration(context);
+ configuration.setTableName("mix");
+ table.setTableConfiguration(configuration);
+ table.setContext(context);
+ table.setBaseRecordType("example.Mix");
+ table.setRules(new FlatModelRules(table));
+ column(table, "time", Types.TIMESTAMP, "TIMESTAMP", "java.lang.Long");
+ column(table, "device_id", Types.VARCHAR, "VARCHAR", "java.lang.String");
+ column(table, "temperature", Types.FLOAT, "FLOAT", "java.lang.Float");
+ table.addPrimaryKeyColumn("time");
+ table.addPrimaryKeyColumn("device_id");
+ return table;
+ }
+
+ private void column(
+ IntrospectedTable table, String name, int type, String jdbc, String javaType) {
+ IntrospectedColumn column = new IntrospectedColumn();
+ column.setContext(context);
+ column.setActualColumnName(name);
+ column.setJavaProperty(
+ name.equals("device_id") ? "deviceId" : name); // MBG camel-cases the property name
+ column.setColumnNameDelimited(true); // matches delimitAllColumns in the example configs
+ column.setJdbcType(type);
+ column.setJdbcTypeName(jdbc);
+ column.setFullyQualifiedJavaType(new FullyQualifiedJavaType(javaType));
+ table.addColumn(column);
+ }
+
+ /** Mirrors the trailing "where ... and ..." text elements MBG emits for the key predicate. */
+ private XmlElement keyedStatement(String tag, String id, String prefix) {
+ XmlElement element = new XmlElement(tag);
+ element.addAttribute(new Attribute("id", id));
+ if (tag.equals("select")) {
+ element.addAttribute(new Attribute("resultType", "map"));
+ }
+ element.addElement(new TextElement(prefix + " \"mix\""));
+ element.addElement(new TextElement("where \"time\" = #{time,jdbcType=TIMESTAMP}"));
+ element.addElement(new TextElement(" and \"device_id\" = #{deviceId,jdbcType=VARCHAR}"));
+ return element;
+ }
+
+ private Configuration parse(XmlElement... statements) {
+ Document document =
+ new Document(
+ "-//mybatis.org//DTD Mapper 3.0//EN", "https://mybatis.org/dtd/mybatis-3-mapper.dtd");
+ XmlElement root = new XmlElement("mapper");
+ root.addAttribute(new Attribute("namespace", "example.MixMapper"));
+ for (XmlElement statement : statements) {
+ root.addElement(statement);
+ }
+ document.setRootElement(root);
+ Configuration configuration = new Configuration();
+ new XMLMapperBuilder(
+ new StringReader(new DefaultXmlFormatter().getFormattedContent(document)),
+ configuration,
+ "mapper.xml",
+ configuration.getSqlFragments())
+ .parse();
+ return configuration;
+ }
+
+ @Test
+ public void rewritesKeyPredicatesSoNullTagsMatchWithIsNull() {
+ IntrospectedTable table = table();
+ XmlElement select = keyedStatement("select", "selectByPrimaryKey", "select * from");
+ XmlElement delete = keyedStatement("delete", "deleteByPrimaryKey", "delete from");
+ assertTrue(plugin.sqlMapSelectByPrimaryKeyElementGenerated(select, table));
+ assertTrue(plugin.sqlMapDeleteByPrimaryKeyElementGenerated(delete, table));
+ Configuration configuration = parse(select, delete);
+ for (String statement : new String[] {"selectByPrimaryKey", "deleteByPrimaryKey"}) {
+ Map key = new HashMap<>();
+ key.put("time", 1700000000123L);
+ key.put("deviceId", "d1");
+ BoundSql bound =
+ configuration.getMappedStatement("example.MixMapper." + statement).getBoundSql(key);
+ String sql = bound.getSql().replaceAll("\\s+", " ");
+ assertTrue(sql, sql.contains("WHERE \"time\" = ? AND \"device_id\" = ?"));
+ assertEquals(2, bound.getParameterMappings().size());
+ key.put("deviceId", null);
+ bound = configuration.getMappedStatement("example.MixMapper." + statement).getBoundSql(key);
+ sql = bound.getSql().replaceAll("\\s+", " ");
+ assertTrue(sql, sql.contains("WHERE \"time\" = ? AND \"device_id\" IS NULL"));
+ assertEquals(1, bound.getParameterMappings().size());
+ }
+ }
+
+ @Test
+ public void leavesStatementsWithoutAKeyPredicateAlone() {
+ IntrospectedTable table = table();
+ XmlElement select = new XmlElement("select");
+ select.addAttribute(new Attribute("id", "selectAll"));
+ select.addElement(new TextElement("select * from \"mix\""));
+ assertTrue(plugin.sqlMapSelectByPrimaryKeyElementGenerated(select, table));
+ assertEquals(1, select.getElements().size());
+ }
+
+ @Test
+ public void disablesUpdateStatementsWithAWarning() {
+ IntrospectedTable table = table();
+ TableConfiguration configuration = table.getTableConfiguration();
+ configuration.setUpdateByPrimaryKeyStatementEnabled(true);
+ configuration.setUpdateByExampleStatementEnabled(true);
+ plugin.initialized(table);
+ assertFalse(configuration.isUpdateByPrimaryKeyStatementEnabled());
+ assertFalse(configuration.isUpdateByExampleStatementEnabled());
+ assertFalse(table.getRules().generateUpdateByPrimaryKeyWithoutBLOBs());
+ assertFalse(table.getRules().generateUpdateByPrimaryKeySelective());
+ assertFalse(table.getRules().generateUpdateByExampleWithoutBLOBs());
+ assertEquals(1, warnings.size());
+ assertTrue(warnings.get(0), warnings.get(0).contains("not generating UPDATE statements"));
+
+ plugin.initialized(table);
+ assertEquals("already disabled tables do not warn again", 1, warnings.size());
+ }
+}
diff --git a/mybatis-support/README.md b/mybatis-support/README.md
new file mode 100644
index 00000000..ef7c5763
--- /dev/null
+++ b/mybatis-support/README.md
@@ -0,0 +1,102 @@
+
+
+# IoTDB MyBatis runtime support
+
+Small runtime adapters for MyBatis and MyBatis-Plus with **JDK 17+ and IoTDB JDBC 2.0.11**. MyBatis is a provided dependency; the application selects its MyBatis/Boot version.
+
+## Install and register
+
+From the repository root:
+
+```sh
+mvn -pl mybatis-support -am install
+```
+
+The `with-mybatis` distribution profile (`mvn package -Pwith-mybatis`) also ships this jar next to the generator plugin in `apache-iotdb--mybatis-generator-plugin-bin.zip`.
+
+Add this application dependency (it is separate from the build-time generator plugin):
+
+```xml
+
+ org.apache.iotdb
+ mybatis-support
+ 2.0.4-SNAPSHOT
+
+```
+
+Register the query interceptor on the IoTDB MyBatis configuration:
+
+```xml
+
+
+
+```
+
+For MyBatis-Plus, expose `new IoTDBQueryInterceptor()` as a Spring `@Bean`. The [example](../examples/mybatisplus-generator/src/main/java/org/apache/iotdb/config/IoTDBMybatisConfiguration.java) registers it. For applications with several databases, register it only on the IoTDB `SqlSessionFactory`.
+
+In JDBC 2.0.11, a table-model prepared SELECT executed with `execute()` returns true without populating `getResultSet()`. MyBatis normally uses these two calls and can return an empty result. The interceptor routes prepared queries and cursors through **one** `executeQuery()` call and exposes that result to MyBatis. It preserves statement/result ownership and propagates errors; it does not retry or re-execute the SQL.
+
+## Type mapping
+
+| IoTDB type | Java type | Read/write mapping |
+|---|---|---|
+| TIME / TIMESTAMP | `Long` | Built-in `LongTypeHandler`, raw server-precision ticks |
+| FLOAT | `Float` | Built-in `FloatTypeHandler` |
+| DATE | `LocalDate` | `IoTDBLocalDateTypeHandler` |
+| BLOB | `byte[]` | `IoTDBBlobTypeHandler` |
+
+DATE uses JDBC `setDate/getDate` and checks `wasNull()`. JDBC 2.0.11 does not implement the typed `getObject(..., LocalDate.class)` path and can return a non-null date object for SQL NULL.
+
+BLOB uses the supported `setBinaryStream(int, InputStream, int)` overload and `getBytes`. The 2.0.11 driver's `setBytes` treats bytes as text, and `setBlob` is unsupported. Arbitrary bytes, including zero and non-UTF-8 bytes, must survive unchanged.
+
+Configure generator column overrides:
+
+```xml
+
+
+```
+
+The handler must appear in both parameter mappings and result mappings. In MyBatis-Plus use `@TableName(autoResultMap = true)` and `@TableField(typeHandler = ...)`; generated XML also includes explicit mappings. Package scanning is available through `mybatis-plus.type-handlers-package=org.apache.iotdb.mybatis.type`.
+
+## Time precision and write results
+
+A timestamp such as `1700000000123456789L` is interpreted in the server's configured precision. Use matching values for all TIME and TIMESTAMP columns:
+
+| Server precision | Example raw value |
+|---|---|
+| ms | `1700000000123L` |
+| us | `1700000000123456L` |
+| ns | `1700000000123456789L` |
+
+There is no automatic unit conversion. `java.util.Date` and the driver's `setTimestamp` path cannot preserve arbitrary microsecond/nanosecond ticks; do not substitute them for Long without a separately verified conversion policy.
+
+JDBC 2.0.11 does not report reliable affected-row counts through `getUpdateCount()`; MyBatis write methods can return **-1 (unknown)** after successful execution. Do not interpret `result > 0` as the success criterion. Failures raise exceptions. A multi-row insert, several chunks, or a Spring transaction annotation does not provide transactional rollback.
+
+## Tests
+
+```sh
+mvn -pl mybatis-support -am test
+```
+
+Unit tests cover byte preservation, dates/NULLs, raw ms/us/ns values, and single execution of prepared queries. The [MyBatis](../examples/mybatis-generator/README.md) and [MyBatis-Plus](../examples/mybatisplus-generator/README.md) examples provide opt-in real-server tests that generate, compile and execute mappers.
diff --git a/mybatis-support/pom.xml b/mybatis-support/pom.xml
new file mode 100644
index 00000000..41063f99
--- /dev/null
+++ b/mybatis-support/pom.xml
@@ -0,0 +1,40 @@
+
+
+
+ 4.0.0
+
+ org.apache.iotdb
+ iotdb-extras-parent
+ 2.0.4-SNAPSHOT
+
+ mybatis-support
+ IoTDB Extras: MyBatis Runtime Support
+
+
+ org.mybatis
+ mybatis
+ 3.5.19
+ provided
+
+
+ junit
+ junit
+ test
+
+
+
diff --git a/mybatis-support/src/main/java/org/apache/iotdb/mybatis/IoTDBQueryInterceptor.java b/mybatis-support/src/main/java/org/apache/iotdb/mybatis/IoTDBQueryInterceptor.java
new file mode 100644
index 00000000..f9724c36
--- /dev/null
+++ b/mybatis-support/src/main/java/org/apache/iotdb/mybatis/IoTDBQueryInterceptor.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis;
+
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.apache.ibatis.session.ResultHandler;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+/**
+ * Uses executeQuery for MyBatis prepared queries. IoTDB JDBC 2.0.11 table statements return true
+ * from execute() without assigning their ResultSet. Register on the IoTDB SqlSessionFactory.
+ */
+@Intercepts({
+ @Signature(
+ type = StatementHandler.class,
+ method = "query",
+ args = {Statement.class, ResultHandler.class}),
+ @Signature(
+ type = StatementHandler.class,
+ method = "queryCursor",
+ args = {Statement.class})
+})
+public class IoTDBQueryInterceptor implements Interceptor {
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ Object statement = invocation.getArgs()[0];
+ if (!(statement instanceof PreparedStatement)) return invocation.proceed();
+ invocation.getArgs()[0] = queryStatement((PreparedStatement) statement);
+ try {
+ return invocation.proceed();
+ } finally {
+ invocation.getArgs()[0] = statement;
+ }
+ }
+
+ static PreparedStatement queryStatement(PreparedStatement delegate) {
+ return (PreparedStatement)
+ Proxy.newProxyInstance(
+ PreparedStatement.class.getClassLoader(),
+ new Class>[] {PreparedStatement.class},
+ new java.lang.reflect.InvocationHandler() {
+ private ResultSet resultSet;
+
+ @Override
+ public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args)
+ throws Throwable {
+ if ("execute".equals(method.getName()) && (args == null || args.length == 0)) {
+ resultSet = delegate.executeQuery();
+ return resultSet != null;
+ }
+ if ("getResultSet".equals(method.getName())) return resultSet;
+ try {
+ return method.invoke(delegate, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+ });
+ }
+}
diff --git a/mybatis-support/src/main/java/org/apache/iotdb/mybatis/type/IoTDBBlobTypeHandler.java b/mybatis-support/src/main/java/org/apache/iotdb/mybatis/type/IoTDBBlobTypeHandler.java
new file mode 100644
index 00000000..e7878229
--- /dev/null
+++ b/mybatis-support/src/main/java/org/apache/iotdb/mybatis/type/IoTDBBlobTypeHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis.type;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.io.ByteArrayInputStream;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@MappedTypes(byte[].class)
+@MappedJdbcTypes(
+ value = {JdbcType.BLOB, JdbcType.BINARY, JdbcType.VARBINARY},
+ includeNullJdbcType = true)
+public class IoTDBBlobTypeHandler extends BaseTypeHandler {
+ @Override
+ public void setNonNullParameter(
+ PreparedStatement statement, int index, byte[] value, JdbcType type) throws SQLException {
+ // IoTDB 2.0.11 encodes this overload as a hexadecimal BLOB literal. setBytes decodes text.
+ statement.setBinaryStream(index, new ByteArrayInputStream(value), value.length);
+ }
+
+ @Override
+ public byte[] getNullableResult(ResultSet resultSet, String column) throws SQLException {
+ return resultSet.getBytes(column);
+ }
+
+ @Override
+ public byte[] getNullableResult(ResultSet resultSet, int column) throws SQLException {
+ return resultSet.getBytes(column);
+ }
+
+ @Override
+ public byte[] getNullableResult(CallableStatement statement, int column) throws SQLException {
+ return statement.getBytes(column);
+ }
+}
diff --git a/mybatis-support/src/main/java/org/apache/iotdb/mybatis/type/IoTDBLocalDateTypeHandler.java b/mybatis-support/src/main/java/org/apache/iotdb/mybatis/type/IoTDBLocalDateTypeHandler.java
new file mode 100644
index 00000000..a516c661
--- /dev/null
+++ b/mybatis-support/src/main/java/org/apache/iotdb/mybatis/type/IoTDBLocalDateTypeHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis.type;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import java.sql.CallableStatement;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDate;
+
+@MappedTypes(LocalDate.class)
+@MappedJdbcTypes(value = JdbcType.DATE, includeNullJdbcType = true)
+public class IoTDBLocalDateTypeHandler extends BaseTypeHandler {
+ @Override
+ public void setNonNullParameter(
+ PreparedStatement statement, int index, LocalDate value, JdbcType type) throws SQLException {
+ statement.setDate(index, Date.valueOf(value));
+ }
+
+ @Override
+ public LocalDate getNullableResult(ResultSet resultSet, String column) throws SQLException {
+ Date value = resultSet.getDate(column);
+ return resultSet.wasNull() ? null : toLocalDate(value);
+ }
+
+ @Override
+ public LocalDate getNullableResult(ResultSet resultSet, int column) throws SQLException {
+ Date value = resultSet.getDate(column);
+ return resultSet.wasNull() ? null : toLocalDate(value);
+ }
+
+ @Override
+ public LocalDate getNullableResult(CallableStatement statement, int column) throws SQLException {
+ Date value = statement.getDate(column);
+ return statement.wasNull() ? null : toLocalDate(value);
+ }
+
+ private LocalDate toLocalDate(Date value) {
+ return value == null ? null : value.toLocalDate();
+ }
+}
diff --git a/mybatis-support/src/test/java/org/apache/iotdb/mybatis/IoTDBQueryInterceptorTest.java b/mybatis-support/src/test/java/org/apache/iotdb/mybatis/IoTDBQueryInterceptorTest.java
new file mode 100644
index 00000000..7f128014
--- /dev/null
+++ b/mybatis-support/src/test/java/org/apache/iotdb/mybatis/IoTDBQueryInterceptorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis;
+
+import org.junit.Test;
+
+import java.lang.reflect.Proxy;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class IoTDBQueryInterceptorTest {
+ @Test
+ public void executesQueryOnceAndRetainsResultUntilMyBatisClosesStatement() throws Exception {
+ AtomicInteger queries = new AtomicInteger();
+ AtomicInteger closes = new AtomicInteger();
+ ResultSet result =
+ (ResultSet)
+ Proxy.newProxyInstance(
+ getClass().getClassLoader(), new Class>[] {ResultSet.class}, (p, m, a) -> null);
+ PreparedStatement driver =
+ (PreparedStatement)
+ Proxy.newProxyInstance(
+ getClass().getClassLoader(),
+ new Class>[] {PreparedStatement.class},
+ (p, m, a) -> {
+ if ("executeQuery".equals(m.getName())) {
+ queries.incrementAndGet();
+ return result;
+ }
+ if ("close".equals(m.getName())) {
+ closes.incrementAndGet();
+ return null;
+ }
+ if ("getResultSet".equals(m.getName())) return null;
+ throw new AssertionError("Unexpected JDBC call: " + m.getName());
+ });
+ PreparedStatement statement = IoTDBQueryInterceptor.queryStatement(driver);
+ assertTrue(statement.execute());
+ assertSame(result, statement.getResultSet());
+ assertSame(result, statement.getResultSet());
+ assertEquals(1, queries.get());
+ assertEquals(0, closes.get());
+ statement.close();
+ assertEquals(1, closes.get());
+ }
+
+ @Test
+ public void preservesSqlExceptionsWithoutRetrying() {
+ SQLException expected = new SQLException("query rejected");
+ AtomicInteger queries = new AtomicInteger();
+ PreparedStatement driver =
+ (PreparedStatement)
+ Proxy.newProxyInstance(
+ getClass().getClassLoader(),
+ new Class>[] {PreparedStatement.class},
+ (p, m, a) -> {
+ queries.incrementAndGet();
+ throw expected;
+ });
+ PreparedStatement statement = IoTDBQueryInterceptor.queryStatement(driver);
+ assertSame(expected, assertThrows(SQLException.class, statement::execute));
+ assertEquals(1, queries.get());
+ }
+}
diff --git a/mybatis-support/src/test/java/org/apache/iotdb/mybatis/type/IoTDBTypeHandlerTest.java b/mybatis-support/src/test/java/org/apache/iotdb/mybatis/type/IoTDBTypeHandlerTest.java
new file mode 100644
index 00000000..c6df49f4
--- /dev/null
+++ b/mybatis-support/src/test/java/org/apache/iotdb/mybatis/type/IoTDBTypeHandlerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.iotdb.mybatis.type;
+
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.LongTypeHandler;
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.lang.reflect.Proxy;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.time.LocalDate;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class IoTDBTypeHandlerTest {
+ @Test
+ public void preservesBinaryBytesAndUsesTheSupportedStreamOverload() throws Exception {
+ byte[] bytes = {(byte) 0xff, 0, 39, (byte) 0x80};
+ AtomicReference bound = new AtomicReference<>();
+ PreparedStatement statement =
+ proxy(
+ PreparedStatement.class,
+ (method, args) -> {
+ assertEquals("setBinaryStream", method);
+ assertEquals(Integer.valueOf(bytes.length), args[2]);
+ bound.set(((InputStream) args[1]).readAllBytes());
+ return null;
+ });
+ IoTDBBlobTypeHandler handler = new IoTDBBlobTypeHandler();
+ handler.setParameter(statement, 1, bytes, JdbcType.BLOB);
+ assertArrayEquals(bytes, bound.get());
+ ResultSet results =
+ proxy(
+ ResultSet.class,
+ (method, args) -> {
+ assertEquals("getBytes", method);
+ return bytes;
+ });
+ assertArrayEquals(bytes, handler.getResult(results, "payload"));
+ assertArrayEquals(bytes, handler.getResult(results, 1));
+ }
+
+ @Test
+ public void usesJdbcDateWithoutJdbc42GetObject() throws Exception {
+ LocalDate value = LocalDate.of(2024, 2, 29);
+ PreparedStatement statement =
+ proxy(
+ PreparedStatement.class,
+ (method, args) -> {
+ assertEquals("setDate", method);
+ assertEquals(Date.valueOf(value), args[1]);
+ return null;
+ });
+ IoTDBLocalDateTypeHandler handler = new IoTDBLocalDateTypeHandler();
+ handler.setParameter(statement, 1, value, JdbcType.DATE);
+ ResultSet results =
+ proxy(
+ ResultSet.class,
+ (method, args) -> {
+ if (method.equals("wasNull")) return false;
+ assertEquals("getDate", method);
+ return Date.valueOf(value);
+ });
+ assertEquals(value, handler.getResult(results, "reading_date"));
+ assertEquals(value, handler.getResult(results, 1));
+ }
+
+ @Test
+ public void preservesNullDatesAndBlobs() throws Exception {
+ ResultSet results =
+ proxy(
+ ResultSet.class,
+ (method, args) -> {
+ if (method.equals("wasNull")) return true;
+ if (method.equals("getDate")) return Date.valueOf("0002-11-30");
+ return null;
+ });
+ assertNull(new IoTDBLocalDateTypeHandler().getResult(results, "reading_date"));
+ assertNull(new IoTDBBlobTypeHandler().getResult(results, "payload"));
+ for (JdbcType type : new JdbcType[] {JdbcType.DATE, JdbcType.BLOB}) {
+ AtomicReference boundType = new AtomicReference<>();
+ PreparedStatement statement =
+ proxy(
+ PreparedStatement.class,
+ (method, args) -> {
+ assertEquals("setNull", method);
+ boundType.set((Integer) args[1]);
+ return null;
+ });
+ if (type == JdbcType.DATE) {
+ new IoTDBLocalDateTypeHandler().setParameter(statement, 1, null, type);
+ } else {
+ new IoTDBBlobTypeHandler().setParameter(statement, 1, null, type);
+ }
+ assertEquals(Integer.valueOf(type.TYPE_CODE), boundType.get());
+ }
+ }
+
+ @Test
+ public void longHandlerPreservesRawMillisecondsMicrosecondsAndNanoseconds() throws Exception {
+ for (long ticks : new long[] {1700000000123L, 1700000000123456L, 1700000000123456789L}) {
+ PreparedStatement statement =
+ proxy(
+ PreparedStatement.class,
+ (method, args) -> {
+ assertEquals("setLong", method);
+ assertEquals(ticks, args[1]);
+ return null;
+ });
+ ResultSet results =
+ proxy(
+ ResultSet.class,
+ (method, args) -> {
+ if (method.equals("wasNull")) return false;
+ assertEquals("getLong", method);
+ return ticks;
+ });
+ LongTypeHandler handler = new LongTypeHandler();
+ handler.setParameter(statement, 1, ticks, JdbcType.TIMESTAMP);
+ assertEquals(Long.valueOf(ticks), handler.getResult(results, "time"));
+ }
+ }
+
+ interface Call {
+ Object invoke(String method, Object[] args) throws Throwable;
+ }
+
+ private static T proxy(Class type, Call call) {
+ return type.cast(
+ Proxy.newProxyInstance(
+ type.getClassLoader(),
+ new Class>[] {type},
+ (proxy, method, args) -> call.invoke(method.getName(), args)));
+ }
+}
diff --git a/pom.xml b/pom.xml
index b155418b..6704b40b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,32 +38,30 @@
iotdb-collector
metric-scrape
mybatis-generator
+ mybatis-support
-
1.7
206
0.9
-
- 4.9.3
+ 4.13.2
4.2.0
-
2.9.3
3.3.0
3.38.0
Release
1.5.0
- 1.16.0
+ 1.16.1
1.10.0
2.14.0
3.3
- 3.13.0
+ 3.18.0
3.6.1
2.11.1
4.4
@@ -79,8 +77,8 @@
3.8.1-17.0
1.17.1
1.16
- 1.22.0
- 2.10.1
+ 1.28.0
+ 2.13.1
32.1.2-jre
3.3.6
3.1.3
@@ -90,9 +88,9 @@
This is the version of the thrift binary, that we release separately from here:
https://github.com/apache/iotdb-bin-resources/tree/main/iotdb-tools-thrift
-->
- 0.14.1.0
- 2.0.5
- 2.16.2
+ 0.23.0.0
+ 2.0.11
+ 2.18.6
4.0.4
@@ -103,48 +101,39 @@
1.0-1
2.40
-
9.4.57.v20241219
0.11.5
- 3.23.0
+ 3.26.2
5.14.0
2.5.0
3.1
4.13.2
2.8.2
-
- 1.3.15
- 1.8.0
+ 1.5.34
+ 1.10.1
3.6.0
- 1.8
- 1.8
+ 17
+ 17
+ 17
1.11.4
- 0.6.11
+ 0.6.14
2.23.4
-
- 0.17
- 4.1.97.Final
+ 0.18.0
+ 4.1.134.Final
9.37.2
10.15
-
- 6.6.0
+ 7.10.0
1.9.1
7.0.0
1.5.6
2.0.9
- 1.0.5
-
- 3.1.0-a6a21d5-SNAPSHOT
+ 1.1.0
+ 3.3.0
1.0.4
- 1.1.13
- 3.5.10
+ 1.2.9
+ 3.7.9
0.10.2
5.1.3
2.12.19
@@ -152,7 +141,7 @@
3.0.9
2.0.9
- 1.1.10.4
+ 1.1.10.5
target/jacoco-merged-reports/jacoco.xml
**/generated-sources
@@ -165,29 +154,22 @@
3.5.0
false
- 2.43.0
-
+ 2.44.5
+
2.7.18
5.3.39
3.49.1.0
-
1.6.14
chmod
-
- 0.14.1
-
+
+ 0.23.0
9.0.86
- 2.1.1
+ 2.4.0
1.9
0.11.1
- 1.5.5-5
+ 1.5.6-3
+
+
+ io.netty
+ netty-bom
+ ${netty.version}
+ pom
+ import
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ ${jackson.version}
+ pom
+ import
+
+
+ at.yawk.lz4
+ lz4-java
+ ${lz4-java.version}
+
+
+ org.xerial.snappy
+ snappy-java
+ ${snappy-java.version}
+
+
+ org.apache.iotdb
+ iotdb-jdbc
+ ${iotdb.version}
+
+
+ org.apache.iotdb
+ iotdb-subscription
+ ${iotdb.version}
+
+
+ org.apache.iotdb
+ isession
+ ${iotdb.version}
+
org.springframework.boot
spring-boot
@@ -251,15 +273,38 @@
libthrift
${thrift.version}
-
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+
- org.apache.tomcat.embed
- tomcat-embed-core
+ org.apache.httpcomponents.core5
+ httpcore5
-
- javax.annotation
- javax.annotation-api
+ org.apache.httpcomponents.core5
+ httpcore5-h2
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+
+ org.apache.commons
+ commons-lang3
@@ -1100,7 +1145,7 @@
com.fasterxml.jackson.module
jackson-module-jaxb-annotations
- 2.15.2
+ ${jackson.version}
@@ -1693,6 +1738,23 @@
+
+
+ enforce-java-baseline
+
+ enforce
+
+ validate
+
+ false
+
+
+ [17,)
+ IoTDB Extras with IoTDB 2.0.11 requires JDK 17 or newer.
+
+
+
+
enforce-version-convergence
@@ -1741,7 +1803,7 @@
true
- 1.8.0
+ [17,)
@@ -2025,43 +2087,8 @@
-
- .java-9-and-above
-
- [9,)
-
-
- 8
-
-
-
-
- .java-11-below
-
- (,11]
-
-
-
- 2.27.1
-
- true
-
-
-
-
- .java-16
-
- 16
-
-
- --illegal-access=permit --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
-
-
@@ -2079,8 +2106,7 @@
opt-in convention (cf. the with-springboot profile / iotdb-spring-boot-starter), it is wired
as an explicit, named profile rather than JDK-auto-activated, so a plain `mvn clean verify`
never pulls it into the reactor implicitly. CI builds and tests it on the JDK 17+ jobs by
- passing `-P with-thingsboard` (see .github/workflows/compile-check.yml); the JDK 8/11 jobs
- omit the flag and skip it.
+ passing `-P with-thingsboard` (see .github/workflows/compile-check.yml).
-->
with-thingsboard