This is an automated email from the ASF dual-hosted git repository. CritasWang pushed a commit to branch wx/iotdb-2.0.11-alignment in repository https://gitbox.apache.org/repos/asf/iotdb-extras.git
commit 339d6baa55a18c68cce48e1e49e49f932e9c7047 Author: CritasWang <[email protected]> AuthorDate: Thu Sep 17 17:50:47 2026 +0800 mybatis: drop unsupported UPDATE statements and ship the runtime jar IoTDB 2.0.11 UPDATE rejects time in its predicate and FIELD columns in SET, so MBG's key-based UPDATE statements can never run. IoTDBKeyPlugin now disables them with a generator warning instead of emitting SQL that fails at runtime, and gains unit coverage for the nullable-TAG predicate rewrite it already performed. The mybatis-support runtime jar is required by every generated mapper but was only reachable through mvn install; the new with-mybatis distribution profile packages it next to the generator plugin. --- README-zh.md | 6 + README.md | 6 + distributions/pom.xml | 70 ++++++++ .../src/assembly/mybatis-generator-plugin.xml | 8 + mybatis-generator/README-zh.md | 4 +- mybatis-generator/README.md | 4 +- .../iotdb/mybatis/plugin/IoTDBKeyPlugin.java | 28 +++- .../iotdb/mybatis/plugin/IoTDBKeyPluginTest.java | 180 +++++++++++++++++++++ mybatis-support/README.md | 2 + 9 files changed, 305 insertions(+), 3 deletions(-) diff --git a/README-zh.md b/README-zh.md index da442a5..3616075 100644 --- a/README-zh.md +++ b/README-zh.md @@ -142,6 +142,12 @@ IoTDB-Extras 使用 Maven profiles 配置不同的构建选项。您可以组合 mvn clean package -Pwith-flink -DskipTests ``` +- **with-mybatis**:将 MyBatis 生成插件和运行时适配打包为分发 zip + + ```bash + mvn clean package -Pwith-mybatis -DskipTests + ``` + - **with-grafana**:构建 Grafana 连接器和插件 ```bash diff --git a/README.md b/README.md index ee42dd1..3cc7831 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,12 @@ IoTDB-Extras uses Maven profiles to configure different build options. You can c mvn clean package -Pwith-flink -DskipTests ``` +- **with-mybatis**: Package the MyBatis generator plugin and runtime support as a distribution zip + + ```bash + mvn clean package -Pwith-mybatis -DskipTests + ``` + - **with-grafana**: Build Grafana connectors and plugins ```bash diff --git a/distributions/pom.xml b/distributions/pom.xml index cc002aa..ae4f4f0 100644 --- a/distributions/pom.xml +++ b/distributions/pom.xml @@ -119,6 +119,76 @@ </plugins> </build> </profile> + <profile> + <id>with-mybatis</id> + <dependencies> + <!-- Reactor ordering only: the assembly picks the jars up from the module target dirs. --> + <dependency> + <groupId>org.apache.iotdb</groupId> + <artifactId>mybatis-generator-plugin</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.iotdb</groupId> + <artifactId>mybatis-support</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <!-- Package binaries--> + <execution> + <id>all-bin</id> + <goals> + <goal>single</goal> + </goals> + <phase>package</phase> + <configuration> + <descriptors> + <descriptor>src/assembly/mybatis-generator-plugin.xml</descriptor> + </descriptors> + <finalName>apache-iotdb-${project.version}</finalName> + </configuration> + </execution> + </executions> + </plugin> + <!-- + Create SHA512 checksum files for the release artifacts. + --> + <plugin> + <groupId>net.nicoulaj.maven.plugins</groupId> + <artifactId>checksum-maven-plugin</artifactId> + <executions> + <execution> + <id>sign-source-release</id> + <goals> + <goal>files</goal> + </goals> + <phase>package</phase> + <configuration> + <algorithms> + <algorithm>SHA-512</algorithm> + </algorithms> + <fileSets> + <!--bin-all--> + <fileSet> + <directory>${project.build.directory}</directory> + <includes> + <include>apache-iotdb-${project.version}-mybatis-generator-plugin-bin.zip</include> + </includes> + </fileSet> + </fileSets> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> <profile> <id>with-flink</id> <build> diff --git a/distributions/src/assembly/mybatis-generator-plugin.xml b/distributions/src/assembly/mybatis-generator-plugin.xml index 271c084..dc00b42 100644 --- a/distributions/src/assembly/mybatis-generator-plugin.xml +++ b/distributions/src/assembly/mybatis-generator-plugin.xml @@ -34,6 +34,14 @@ <include>mybatis-generator-plugin-*.jar</include> </includes> </fileSet> + <!-- The generated mappers need the runtime adapters (query interceptor, DATE/BLOB handlers). --> + <fileSet> + <directory>${maven.multiModuleProjectDirectory}/mybatis-support/target/</directory> + <outputDirectory>${file.separator}</outputDirectory> + <includes> + <include>mybatis-support-*.jar</include> + </includes> + </fileSet> </fileSets> <componentDescriptors> <componentDescriptor>common-files.xml</componentDescriptor> diff --git a/mybatis-generator/README-zh.md b/mybatis-generator/README-zh.md index c18b5f6..bee354a 100644 --- a/mybatis-generator/README-zh.md +++ b/mybatis-generator/README-zh.md @@ -31,6 +31,8 @@ mvn -pl mybatis-generator,mybatis-support -am clean install ``` +`mvn package -Pwith-mybatis` 还会生成 `distributions/target/apache-iotdb-<version>-mybatis-generator-plugin-bin.zip`,其中同时打包生成插件和 `mybatis-support` 运行时 jar。 + 生成时,`mybatis-generator-maven-plugin:1.4.2` 的 `dependencies` 中必须包含: | 依赖 | 版本 | @@ -61,7 +63,7 @@ mvn mybatis-generator:generate - 行键由 **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;修改 FIELD 要按相同行键执行 INSERT。省略或为 NULL 的 FIELD 不会清除已有值。 +- 设置 `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 选择数据库。 diff --git a/mybatis-generator/README.md b/mybatis-generator/README.md index 1750e78..713164b 100644 --- a/mybatis-generator/README.md +++ b/mybatis-generator/README.md @@ -33,6 +33,8 @@ From the repository root: mvn -pl mybatis-generator,mybatis-support -am clean install ``` +`mvn package -Pwith-mybatis` additionally builds `distributions/target/apache-iotdb-<version>-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: @@ -86,7 +88,7 @@ Applications also need [mybatis-support](../mybatis-support/README.md), includin - `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. 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. +- 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. 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 index 1681ba1..2a1a0d6 100644 --- 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 @@ -24,16 +24,42 @@ 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; -/** Preserves nullable TAG components in generated TIME + TAG key predicates. */ +/** + * 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<String> warnings = new ArrayList<>(); + @Override public boolean validate(List<String> 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) { diff --git a/mybatis-generator/src/test/java/org/apache/iotdb/mybatis/plugin/IoTDBKeyPluginTest.java b/mybatis-generator/src/test/java/org/apache/iotdb/mybatis/plugin/IoTDBKeyPluginTest.java new file mode 100644 index 0000000..22356bf --- /dev/null +++ b/mybatis-generator/src/test/java/org/apache/iotdb/mybatis/plugin/IoTDBKeyPluginTest.java @@ -0,0 +1,180 @@ +/* + * 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.mapping.BoundSql; +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.DefaultXmlFormatter; +import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType; +import org.mybatis.generator.api.dom.xml.Attribute; +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.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 java.io.StringReader; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class IoTDBKeyPluginTest { + private final Context context = new Context(ModelType.FLAT); + private final List<String> 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<String, Object> 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 index f4d68ac..ef7c576 100644 --- a/mybatis-support/README.md +++ b/mybatis-support/README.md @@ -31,6 +31,8 @@ From the repository root: 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-<version>-mybatis-generator-plugin-bin.zip`. + Add this application dependency (it is separate from the build-time generator plugin): ```xml
