fix(manager): drop invalid affectData attribute from mapper inserts

affectData is not declared for insert by the MyBatis mapper DTD, so
the attribute only surfaces as a startup error after the affected
module deploys. Remove it from every upsertLeaseState variant and
document that the atomically advanced assignment version is re-selected
in the same transaction rather than returned.

Add ManagerMapperXmlGrammarTest to parse every mapping/*.xml against
the bundled DTD so future invalid attributes fail the build instead of
application startup.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pnoker
2026-08-27 09:40:22 +08:00
co-authored by Claude
parent e35a2a301f
commit 5bcd40b3ac
3 changed files with 59 additions and 4 deletions
@@ -79,7 +79,8 @@ public interface DriverLeaseMapper {
@Param("driverId") Long driverId);
/**
* Upserts reconciliation state and returns the atomically advanced assignment version.
* Upserts reconciliation state. The atomically advanced assignment version is not part of the return value;
* callers re-select it inside the same transaction, so the affected-row count carries no lease semantics.
*/
int upsertLeaseState(@Param("tenantId") Long tenantId,
@Param("driverId") Long driverId,
@@ -124,7 +124,7 @@
No RETURNING: the manager re-selects in the same transaction (MySQL
has no INSERT ... RETURNING).
-->
<insert id="upsertLeaseState" affectData="true" flushCache="true">
<insert id="upsertLeaseState" flushCache="true">
INSERT INTO dc3_driver_lease_state
(tenant_id, driver_id, membership_hash, device_revision)
VALUES (#{tenantId}, #{driverId}, #{membershipHash}, #{deviceRevision})
@@ -137,7 +137,7 @@
operate_time = CURRENT_TIMESTAMP
</insert>
<insert id="upsertLeaseState" databaseId="mysql" affectData="true" flushCache="true">
<insert id="upsertLeaseState" databaseId="mysql" flushCache="true">
INSERT INTO dc3_driver_lease_state
(tenant_id, driver_id, membership_hash, device_revision)
VALUES (#{tenantId}, #{driverId}, #{membershipHash}, #{deviceRevision}) AS new
@@ -149,7 +149,7 @@
<!-- MariaDB twin of the mysql fork above: identical shape, but
MariaDB never adopted the AS-new row alias: VALUES(col) instead. -->
<insert id="upsertLeaseState" databaseId="mariadb" affectData="true" flushCache="true">
<insert id="upsertLeaseState" databaseId="mariadb" flushCache="true">
INSERT INTO dc3_driver_lease_state
(tenant_id, driver_id, membership_hash, device_revision)
VALUES (#{tenantId}, #{driverId}, #{membershipHash}, #{deviceRevision})
@@ -0,0 +1,54 @@
/*
* Copyright 2016-present the IoT DC3 original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.pnoker.common.manager.scan;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import java.io.InputStream;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Grammar gate for mapper XML: every mapping file on the classpath must parse
* against the MyBatis mapper DTD. Attributes the bundled DTD does not declare
* for an element (for example {@code affectData} on {@code insert}) otherwise
* surface only at application startup, so this gate fails the build instead.
* No database is involved — parsing alone exercises the DTD validation that
* {@code sqlSessionFactory} performs.
*/
class ManagerMapperXmlGrammarTest {
@Test
void allMappingXmlParsesAgainstTheMybatisDtd() throws Exception {
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapping/*.xml");
assertThat(resources).isNotEmpty();
for (Resource resource : resources) {
Configuration configuration = new Configuration();
try (InputStream in = resource.getInputStream()) {
new XMLMapperBuilder(in, configuration, resource.getDescription(), configuration.getSqlFragments()).parse();
} catch (Exception e) {
throw new AssertionError("mapper XML rejected by MyBatis: " + resource.getDescription(), e);
}
}
}
}