From 02aca1c35c9a118fa70a43b72fe028c8760c1a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E5=B0=8F=E4=BA=91?= Date: Sat, 17 Jan 2026 17:16:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=9E=B6=E6=9E=84=E6=A0=87?= =?UTF-8?q?=E5=87=86=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE_STANDARDS.md | 66 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/ARCHITECTURE_STANDARDS.md b/ARCHITECTURE_STANDARDS.md index cd72699..ac28f86 100644 --- a/ARCHITECTURE_STANDARDS.md +++ b/ARCHITECTURE_STANDARDS.md @@ -688,7 +688,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where {entity}_id = #{{{entity}Id}} - + insert into {module}_{table} field_name, @@ -734,6 +734,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - 使用 `` 定义可复用的 SQL 片段 - 使用动态 SQL 标签:``, ``, `` - 插入操作使用 `useGeneratedKeys="true"` 返回主键 +- **重要**:插入操作必须同时指定 `keyProperty` 和 `keyColumn` 属性 - 时间字段使用 `sysdate()` 函数 --- @@ -904,7 +905,10 @@ public class {Entity}DomainImpl implements I{Entity}Domain public int insert{Entity}({Entity} {entity}) { {Entity}Entity entity = {Entity}DomainConvert.toEntity({entity}); - return {entity}Mapper.insert{Entity}(entity); + int result = {entity}Mapper.insert{Entity}(entity); + // 【重要】MyBatis 会将自增主键回填到 entity 对象,需要同步回 model 对象 + {entity}.set{Entity}Id(entity.get{Entity}Id()); + return result; } @Override @@ -933,6 +937,64 @@ public class {Entity}DomainImpl implements I{Entity}Domain - 使用**构造器注入**(Constructor Injection) - 字段声明为 `private final` - 通过 Convert 类进行对象转换 +- **【关键】insert 方法必须将 Entity 的主键回填到 Model 对象** + +**⚠️ MyBatis 主键回填注意事项:** + +MyBatis 在执行 INSERT 操作后,会将数据库自动生成的主键值回填到 `Entity` 对象中,但**不会自动同步到传入的 `Model` 对象**。 + +**错误示例(会导致主键丢失):** +```java +@Override +public int insert{Entity}({Entity} {entity}) +{ + {Entity}Entity entity = {Entity}DomainConvert.toEntity({entity}); + return {entity}Mapper.insert{Entity}(entity); + // ❌ 错误:entity 中的主键没有同步回 {entity} 对象 +} +``` + +**正确示例(主键正确回填):** +```java +@Override +public int insert{Entity}({Entity} {entity}) +{ + {Entity}Entity entity = {Entity}DomainConvert.toEntity({entity}); + int result = {entity}Mapper.insert{Entity}(entity); + // ✅ 正确:将 entity 中的主键同步回 {entity} 对象 + {entity}.set{Entity}Id(entity.get{Entity}Id()); + return result; +} +``` + +**为什么需要手动同步主键?** + +1. **对象转换导致的隔离**:`BeanUtils.copyProperties()` 只是属性拷贝,创建了新的 Entity 对象 +2. **MyBatis 只回填 Entity**:MyBatis 的 `useGeneratedKeys` 只会将主键设置到 Mapper 方法参数(Entity)中 +3. **Model 对象不会自动更新**:原始的 Model 对象与 Entity 对象是两个独立的对象,需要手动同步 + +**不同步主键的后果:** + +如果不将主键同步回 Model 对象,会导致: +- Service 层无法获取新插入记录的主键 ID +- 后续依赖主键的业务逻辑会失败(如关联表插入) +- 可能产生重复数据或数据不一致问题 + +**示例场景:** +```java +// Service 层调用 +Device device = new Device(); +device.setDeviceName("测试设备"); +deviceDomain.insertDevice(device); + +// 如果没有主键回填,device.getDeviceId() 将返回 null +Long deviceId = device.getDeviceId(); // 期望得到新插入的 ID + +// 后续业务逻辑依赖这个 ID +Dock dock = new Dock(); +dock.setDeviceId(deviceId); // 如果 deviceId 为 null,会导致关联失败 +dockDomain.insertDock(dock); +``` ### 5.4 Domain Convert 转换类规范