资讯详情

Spring Boot 3 + MyBatis Plus 插入数据实战:主键策略、字段填充与踩坑记录

📅 2026/10/11 14:25:03 | 华诺云谱 👁 阅读
Spring Boot 3 + MyBatis Plus 插入数据实战:主键策略、字段填充与踩坑记录
去年开始我陆续把一些老项目从 Spring Boot 2.x 迁到 Spring Boot 3还在网上连载了这套 Spring Boot 3 MyBatis Plus 的实操笔记这次正好写到添加数据这节。先说结论用 MyBatis Plus 做添加表面上看就一个insert()方法的事但你要是没搞明白 Mapper 层背后那些注解、主键策略、字段填充的逻辑后面做更新、删除、联表查询时会被各种诡异的坑绊住。这篇笔记就是系列第 05 篇核心围绕“添加”这个动作在 Mapper 层的实现从环境版本、实体类设计、三种写入方式到审计字段自动填充和实战踩坑记录内容偏入门但尽可能把底层逻辑讲透。适合刚接触 MyBatis Plus 的新手也适合那些用 CRUD 生成器写代码却从没想过 Mapper 为什么能直接调insert的人。记住一句话MyBatis Plus 的添加操作难点不在insert()那行代码而在你如何看待实体和表之间的关系。1. 添加操作的底层思路先理解 BaseMapper 在背后做了什么1.1 为什么写一个继承接口的 Mapper 就能插入数据用 MyBatis Plus 写了这么久我发现很多同学在第一步就被“魔法”迷惑了。定义一个interface UserMapper extends BaseMapperUser什么都不用写就能直接userMapper.insert(user)总觉得是框架施了什么法术。其实拆开看事情没那么玄。BaseMapperT是 MyBatis Plus 提供的一个泛型接口它在编译阶段通过泛型T锁定实体类型User然后框架内置了一个针对User的 Mapper 代理实现。你调用的insert(entity)最终会被解析成一条 SQLINSERT INTO user (name, age, email) VALUES (?, ?, ?)每个字段、每个问号全都来自实体类上的注解和类属性定义。如果我们画一条调用链就是这样的Controller 调用 Service - Service 调用 Mapper.insert() - MyBatis Plus 根据实体类元数据生成 INSERT SQL - 交给 MyBatis 的 SqlSession 执行 - 返回受影响行数这个机制的优势很明显不写 XML、不写 SQL、不写 DAO 实现类一张表的常规增删改查就都有了。代价是你必须在实体类上把表和字段的关系声明清楚否则框架猜错了映射数据就写错地方。从实际项目出发我建议新手先把 BaseMapper 提供的 CRUD 方法逐个摸一遍尤其是关注它们的返回值。比如insert()返回的是int代表受影响行数正常插入成功就是 1。这个细节很多人忽略但它可以成为判断数据库写入是否成功的直接依据。1.2 添加操作在整套 CRUD 中的位置与设计顺序很多教程上来就讲代码我反而想先讲设计顺序。做添加之前你得先回答几个问题主键是数据库自增还是由程序生成创建时间、更新时间要不要自动维护逻辑删除字段在插入时如何处理哪些字段需要默认值哪些必须由前端传入这些问题看似零散其实都指向一个共同的设计原则插入数据的职责边界。数据库的自增、默认值属于数据库侧能力而雪花 ID、自动填充时间属于应用侧能力理清这条边界你的代码才不会出现所有逻辑都堆在 Controller 里的局面。我在实际项目里的做法是实体类只负责描述“对象形态”插入时状态由应用统一管理而不是依赖前端传什么就存什么。举个例子createTime这个字段如果让前端传就会出现各种时间格式不统一、空值漏传的问题让后端通过字段填充统一处理一行注解就能解决后面我会专门展示。1.3 环境版本Spring Boot 3 和 MyBatis Plus 的搭配技巧先说个最容易踩的地雷MyBatis Plus 分两个 starter老的是mybatis-plus-boot-starter新的是mybatis-plus-spring-boot3-starter。Spring Boot 3 项目如果用老的 starter启动时大概率直接报错因为 Spring Boot 3 基于 Jakarta EE 规范包名从javax.*换成了jakarta.*老 starter 没适配。我当前项目使用的稳定组合是这样parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version /parent dependencies !-- Web 依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MySQL 驱动Spring Boot 3 默认使用 com.mysql.cj.jdbc.Driver -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 注意Spring Boot 3 必须引入这个专门适配的 Starter -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency /dependencies要说一下版本建议MyBatis Plus 这边我用的 3.5.7实际生产如果要更保守一点3.5.5 以上都行但低于 3.5.3 的话部分安全和功能更新是缺失的尤其是针对 SQL 注入的一些拦截能力建议不要用太老的版本。# application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: assign_id logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这个配置文件里的log-impl非常有用它会打印 MyBatis 执行的 SQL 和参数调试添加功能时能直接看到框架生成的 INSERT 语句是什么样强烈建议初学阶段开着。等到项目上线再换成org.apache.ibatis.logging.slf4j.Slf4jImpl配合日志级别控制。2. 实体类与表结构设计添加数据的质量源头2.1 从一张用户表开始建表与字段映射用一个示例来走全程。先看表结构CREATE TABLE user ( id BIGINT NOT NULL COMMENT 主键ID, name VARCHAR(30) NOT NULL COMMENT 姓名, age INT DEFAULT NULL COMMENT 年龄, email VARCHAR(50) DEFAULT NULL COMMENT 邮箱, create_time DATETIME DEFAULT NULL COMMENT 创建时间, update_time DATETIME DEFAULT NULL COMMENT 更新时间, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除标记0未删除 1已删除, PRIMARY KEY (id) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4 COMMENT 用户表;注意我特意把主键设计成了BIGINT而不是AUTO_INCREMENT的自增整型这是为后面演示雪花 ID 做准备的。项目里的真实情况如果你追求绝对简单用自增主键也行但一旦涉及分布式、分库分表自增主键就会成为瓶颈所以 MyBatis Plus 默认的ASSIGN_ID雪花算法是更通用的方案。对应实体类这样写Data TableName(user) public class User { TableId(type IdType.ASSIGN_ID) private Long id; private String name; private Integer age; private String email; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableLogic TableField(fill FieldFill.INSERT) private Integer deleted; }几个关键注解我逐个说。TableName(user)指定实体对应的表。因为 User 类名翻译成表名默认就是user所以这个注解看起来是多余的但如果你的表名和实体名对不上比如实体叫Member表叫t_member没有这个注解框架就找不到表。TableId(type IdType.ASSIGN_ID)是主键策略。ASSIGN_ID表示主键由 MyBatis Plus 在插入时生成生成算法是雪花算法生成的是Long类型。后面会单独展开主键策略的选择问题。TableField(fill ...)和TableLogic这两个比较特殊一个是字段填充一个是逻辑删除它们直接影响 insert 时字段的处理我放在后面详细讲。2.2 主键生成策略自增、雪花还是 UUID主键策略是添加操作里最值得讲清楚的环节因为很多人只知道自己配了AUTO或ASSIGN_ID但不知道各自适用什么场景。MyBatis Plus 的IdType枚举里实际开发中会碰到的有这么几种策略值主键谁来生成适用场景注意事项AUTO数据库自增数据库单库单表、数据量可控插入后需要回填主键MyBatis Plus 会自动把自增 ID 写回实体ASSIGN_IDMyBatis Plus 生成雪花算法应用分布式环境、微服务生成的 ID 是 Long 类型数据库字段必须够大ASSIGN_UUIDMyBatis Plus 生成UUID应用需要全局唯一字符串主键默认生成不含横线的 32 位字符串INPUT使用者手动赋值应用特殊业务主键如果实体 id 为 null会直接报错或插入 null很多初学的人会问ASSIGN_ID 和 AUTO 到底选哪个我的建议是只要你的系统未来可能拆分成多个服务或者数据库会分表就选ASSIGN_ID。雪花算法的好处不只是唯一它生成的 ID 是有序的对数据库索引友好不像 UUID 那样随机散列导致频繁页分裂。而且有个细节很关键MyBatis Plus 在执行 insert 之后会把生成的主键回填到实体对象上。也就是说不管用哪种策略只要插入成功user.getId()就有值了。这个特性在“先插入子表、再引用主表 ID 关联”的场景里特别有用不用再手动查一次。userMapper.insert(user); // 插入成功后user.getId() 已经是数据库里的真实主键了 Long newId user.getId();2.3 字段忽略、驼峰转换与保留字陷阱MyBatis Plus 默认开启驼峰映射createTime字段自动对应数据库的create_time列所以大多数情况下你不需要额外声明。但有两个坑我提醒一下。第一个是TableField(exist false)。实体类里如果有一些不是数据库字段的属性比如confirmPassword、或者传参用的临时对象必须加这个注解否则 MyBatis Plus 会认为它是表字段insert 时生成一条不存在的列直接 SQL 报错。TableField(exist false) private String confirmPassword;第二个坑是数据库保留字。name、desc、order这些词在某些数据库里是保留字。实体字段叫desc时MyBatis Plus 生成的 SQL 会写成INSERT INTO table (desc) VALUES (?)数据库直接报语法错误。解决办法有两个要么建表时避开保留字列名要么在字段上指定别名TableField(desc) private String desc;用反引号包住列名告诉数据库这是一个普通列名。这个问题在字段设计阶段就可以规避所以我一直强调建表时就要有“未来会被 ORM 框架映射”的意识字段命名尽量回避保留字。3. Mapper 层添加数据的三种实现方式3.1 方式一直接使用 BaseMapper 内置的 insert 方法这是日常开发最常用的方式代码量最少Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public boolean addUser(User user) { int rows userMapper.insert(user); return rows 0; } }这里有个选择是直接用userMapper还是用IServiceMyBatis Plus 提供了一套 Service 层的 CRUD 封装ServiceImpl里已经有save()、saveBatch()这些方法。如果你的项目追求代码规范用 Service 层更合适如果简单 Demo 或者内部工具Mapper 直接用也没什么问题。但要注意我见过不少人绕过 Service 直接userMapper.insert()这不算错只是后续如果要在插入前后做数据校验、字段装配、敏感信息加密Service 层是合适的位置。所以我个人的习惯是 Controller 不直接碰 Mapper中间留 Service 一层。insert()方法执行的 SQL 经过日志打印大概是这个样子 Preparing: INSERT INTO user (id, name, age, email, create_time, update_time, deleted) VALUES (?, ?, ?, ?, ?, ?, ?) Parameters: 1843671872339849214(Long), 张三(String), 25(Integer), zhangsanexample.com(String), 2025-01-10T10:30:00(LocalDateTime), 2025-01-10T10:30:00(LocalDateTime), 0(Integer) Updates: 1注意观察即使你没有给createTime、updateTime显式赋值因为字段填充生效了SQL 里依然有值。这个能力独立于 insert 方法本身属于 AOP 拦截逻辑下一节单独展开。3.2 方式二自定义 SQL 实现有条件的插入内置 insert 虽方便但遇到“带条件插入”“插入忽略重复数据”这类需求就得上自定义 SQL 了。比如 MySQL 的INSERT IGNORE或者ON DUPLICATE KEY UPDATEBaseMapper 方法没法直接表达需要自己在接口里新增方法再配 XML。先在 Mapper 接口里声明方法public interface UserMapper extends BaseMapperUser { /** * 如果用户名已存在则忽略插入 */ int insertIgnoreDuplicate(User user); }再写 XML 文件放在 resources 目录下的mapper/UserMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper insert idinsertIgnoreDuplicate parameterTypecom.example.demo.entity.User INSERT IGNORE INTO user (name, age, email, create_time, update_time, deleted) VALUES (#{name}, #{age}, #{email}, #{createTime}, #{updateTime}, #{deleted}) /insert /mapper别忘在配置里指定 XML 扫描路径mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml自定义 SQL 有个额外好处你可以精确控制插入哪些列。比如某个表有 30 个字段但业务上只允许插入其中 5 个比如带了默认值或触发器字段就不应该出现在 INSERT 里。内置 insert 是“非 null 字段才插入”的策略但如果实体属性被无意的 setter 赋值了它照样插进去无法做列级管控。自定义 SQL 的价值就在这种精确性上。3.3 方式三批量插入怎么选更稳说句实话MyBatis Plus 内置的saveBatch()在 Service 层用的场景比较多但它是通过循环调用insert来实现的本质上是单条 SQL 的批量发送并没有真正拼成一条多 VALUES 的 INSERT。分批大小默认 1000性能在中小型数据量下完全够用但如果你想追求极致的批量插入性能可以用自定义 SQL 拼多值插入。insert idinsertBatch INSERT INTO user (name, age, email) VALUES foreach collectionlist itemitem separator, (#{item.name}, #{item.age}, #{item.email}) /foreach /insert不过我得泼一盆冷水批量拼 SQL 在数据量大时有 SQL 长度限制问题MySQL 默认max_allowed_packet是 4MB上万条记录拼接可能超限。所以实际项目里我走的是折中方案——用saveBatch的默认分片逻辑不做手写多值 INSERT除非线上有明显性能报告说瓶颈在这里。批量插入时还要考虑主键策略。如果是ASSIGN_IDMyBatis Plus 会在循环里为每条记录生成雪花 ID如果是自增主键批量插入后的主键回填逻辑要小心部分数据库驱动对批量生成键的支持不完整回填可能拿不到值。这一点在数据迁移、批量初始化场景中要提前测一下。4. 核心细节插入时的字段自动填充与逻辑删除4.1 MetaObjectHandler 与审计字段的自动写入回看表结构create_time、update_time和deleted这三个字段理论上每次插入都应该由系统设置而不是依赖前端传入。MyBatis Plus 专门为这种场景提供了字段填充机制。首先在实体类字段上标记填充时机TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableLogic TableField(fill FieldFill.INSERT) private Integer deleted;FieldFill.INSERT表示插入时自动填充FieldFill.INSERT_UPDATE表示插入和更新时都填充。然后实现一个处理器Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { LocalDateTime now LocalDateTime.now(); this.strictInsertFill(metaObject, createTime, LocalDateTime.class, now); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, now); this.strictInsertFill(metaObject, deleted, Integer.class, 0); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }这里头的strictInsertFill有个细节只在实体字段为 null 时才填充。如果你的业务代码手动给createTime赋了值这个填充就会被跳过。这个设计有利有弊好处是灵活你想覆盖也能覆盖坏处是可能你明明写了填充器却发现某些记录时间不对排查半天才发现是代码里手动赋了值。经验之谈规范代码里不要让业务层去碰审计字段统一交给填充器。4.2 逻辑删除字段在插入时扮演的角色TableLogic注解标记的是逻辑删除字段很多人以为它只在删除和查询时生效其实插入时也有影响。MyBatis Plus 在执行 insert 时如果实体里逻辑删除字段为 null即使有填充器也可能会出现意外。所以最好的方式就是在填充器里给一个默认值 0保证新增记录始终是“未删除”状态。还有一个很有趣的点逻辑删除字段一旦标记了TableLogic所有内置的 select 语句都会自动追加WHERE deleted 0条件但这不会影响 insert。插入语义是“创建新数据”不存在已删除的说法所以插入不关心这个字段的值是 0 还是 1。但如果你手写自定义 SQL 插入而且没有显式带上 deleted 字段的默认值数据库表结构里就该给 deleted 列设计DEFAULT 0双保险。提示TableLogic一定要和实体字段的填充器配合好否则一旦插入后查询发现查不到自己刚插入的数据先检查 deleted 字段是不是被填成了 1这是我见过最经典的低级错误之一。4.3 插入返回结果与主键回填的区分再来强调一下返回值。userMapper.insert(user)返回int它表示 SQL 执行后受影响的行数通常是 1。很多人误以为拿到了主键但主键并不在返回值里而在实体对象的 id 属性上。这两者的区别建议烂熟于心int rows userMapper.insert(user); if (rows 1) { // 主键已经被 MyBatis Plus 回填到 user 对象里了 Long generatedId user.getId(); log.info(新增用户成功主键{}, generatedId); }这个回填机制依赖于数据库驱动提供的getGeneratedKeys能力。自增主键时回填的是数据库生成的 IDASSIGN_ID 时回填的是应用生成的雪花 ID。如果遇到返回 1 但getId()还是 null 的情况多半是自增主键配置和驱动不匹配或者实体主键字段命名不是 id需要检查TableId是否标注清楚。5. 实操走一遍完整新增流程与代码串联5.1 从 Controller 到 Mapper 的调用链设计新建一条用户记录的完整链路我习惯这样组织RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping public ResultUser addUser(RequestBody User user) { boolean success userService.createUser(user); return success ? Result.ok(user) : Result.fail(新增失败); } }Service 层承担校验和补充逻辑Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override Transactional(rollbackFor Exception.class) public boolean createUser(User user) { // 基础校验姓名不能为空、年龄合理范围 if (user.getName() null || user.getName().trim().isEmpty()) { throw new BusinessException(姓名不能为空); } // 邮箱重复性校验可以调用已经存在的查询方法 // 执行插入 return userMapper.insert(user) 0; } }Controller 不要直接依赖 Mapper这不仅是规范问题也是可测试性考虑。Service 层用了Transactional插入失败就整体回滚这在多表写入时尤其重要。要知道之前有个项目就是因为没加事务注解子表插入失败后主表数据已经落库排查了很久才定位到是事务边界的问题。5.2 XML 扫描与 Mapper 注册的三种姿势自定义 SQL 的 XML 文件写好了如果没被扫描到运行时会报Invalid bound statement (not found)这种错误特别容易让人一头雾水。我整理一下 MyBatis Plus 项目里 Mapper 的三种注册姿势第一种启动类上使用MapperScanSpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }第二种在每个 Mapper 接口上加MapperMapper public interface UserMapper extends BaseMapperUser { }第三种两者都不用在配置类里通过MapperScan指定多个包路径。规模大的项目建议用第一种一个注解覆盖全部 Mapper 接口少了很多重复标注。至于 XML 文件要让配置里的mapper-locations指向正确路径同时 XML 文件的 namespace 必须和 Mapper 接口的全限定名一致少一个都不行。5.3 单元测试验证插入是否真的成功依赖注入都设置好之后写个简单的单元测试是最有成就感的环节SpringBootTest public class UserMapperTest { Autowired private UserMapper userMapper; Test void testInsert() { User user new User(); user.setName(王五); user.setAge(30); user.setEmail(wangwuexample.com); int rows userMapper.insert(user); Assertions.assertEquals(1, rows); Assertions.assertNotNull(user.getId()); System.out.println(生成主键 user.getId()); } }启动测试后控制台如果开了 SQL 日志你能直接看到准备的 SQL、传入的参数和最终受影响行数。这一步建议每一个学习 MyBatis Plus 的新手都做一遍因为“看见”框架生成 SQL 之后你对 ORM 的恐惧会消除一半对字段映射的直觉也会建立起来。如果测试报错不用急着怀疑框架先按下面这个顺序排查检查数据库连接是否通检查表是否存在检查实体类字段复杂度最后看日志里的 SQL 长什么样。6. 插入场景的常见问题与排查思路6.1 问题一insert 返回 0但数据库表里没有数据返回 0 说明 MyBatis 认为没有行受到影响但如果你确认 SQL 执行成功了实际可能得看是不是字段默认值的锅。一个容易被忽视的情况是MySQL 在某些严格模式下比如给非空字段插入 null 且没有默认值会直接报错而不是返回 0。另一种常见原因是拦截器做了操作限制比如 MyBatis Plus 3.5.3.1 之后引入了block attack插件如果你开启了BlockAttackInnerInterceptor某些包含条件的插入或者恶意的 SQL 片段会被直接拦截表现为 insert 调用时抛异常或者返回受影响行数为 0。排查思路很朴实把log-impl打开看它到底执行的什么 SQL、传的什么参数比起猜要高效得多。6.2 问题二字段明明有值插入后数据库却是 null这是实体映射问题最典型的表现。出现这种情况时优先检查字段上有没有写错注解。比如把一个非数据库字段标记成了TableField(xxx)或者漏了TableField(exist false)导致框架把临时属性也当成表字段处理。另一种情况是数据库列名和实体属性名对不上驼峰转换也不是万能的比如数据库列叫userName实体属性叫name中间隔了一层框架不可能猜出来必须用TableField(user_name)显式指定。我踩过的坑是实体类里定义了静态属性MyBatis Plus 也会尝试把它当作字段映射后来给那个字段加了TableField(exist false)才消停。所以实体类里不必要的静态字段和常量尽量用内部类或者额外对象承载别堆在实体里。6.3 问题三插入数据时提示字段不存在或 SQL 语法错误这种情况大多是数据库列名写错或者实体类里出现了保留字。比如字段名order、desc之类插入 SQL 会在这些词周围丢一个语法错误。如果检查下来字段确实没问题再看是不是表名没有加 schema 前缀多租户场景下偶发user表找成了别的库。这里有个经验数据库连接 URL 里的database和你实际建表所在的库要一致多数据源项目里不同数据源的 Mapper 要分开包管理否则走错库查不到表报错信息却指不到点子上只会反复说“表不存在”。6.4 快速排查清单为了让大家少走弯路我把插入场景的常见问题汇总成一张速查表现象可能原因排查动作insert 返回 0拦截器限制、SQL 未真正执行开启 SQL 日志确认执行内容插入后主键为 null主键策略配置不正确或实体主键字段未用 TableId检查 IdType 和表主键类型数据库报列不存在实体属性多了非表字段检查是否需要 TableField(exist false)插入时时间字段为空填充器未生效检查 MetaObjectHandler 是否注册、fill 枚举是否写对自定义 SQL 报 not foundXML 未被扫描或 namespace 不匹配检查 mapper-locations 与接口路径查询不到刚插入的数据逻辑删除字段被填充成 1检查 deleted 字段填充逻辑这张表我自己在带新人时打印过基本能覆盖 80% 的插入场景问题。剩余的 20%大概率出在业务代码里对对象做了二次修改或者对 MyBatis Plus 的缓存特性理解有偏差这时候就只能靠日志一步步追了。7. 扩展思路插入之后还能往哪走这个系列到了第 05 篇增删改查里“增”已经讲透。接下来的方向无非是两个分支一个是往查询深入怎么在已添加的数据里玩出花样比如条件构造器、分页插件、多表关联另一个是往事务和性能深入比如批量插入的优化、分布式事务中的一致性问题。我个人更推荐先吃透“查”。因为插入只是数据生命周期的入口而查询才是暴露字段设计、索引设计问题的照妖镜。当你从一张表开始做关联查询时回来再看插入的设计会有完全不同的认识——原来主键策略影响的不只是插入速度还影响查询时的索引性能、分页查询的排序稳定性。另外如果你想把这个添加功能做得更工程化可以尝试给实体类增加 JSR-303 校验注解比如NotBlank、Email在 Controller 层用Valid触发校验让非法数据在进入 Mapper 之前就被拦下。这也是很多企业级项目的标配和 MyBatis Plus 本身没有冲突属于锦上添花的技能。这个系列我还会继续往下写但进度可能不固定。如果你正卡在 Spring Boot 3 和 MyBatis Plus 的整合上尤其是本文涉及的插入环节照着代码敲一遍再把日志打开看一遍我相信比看十篇教程都管用。毕竟ORM 框架这东西亲手捅破那层“魔法”窗户纸之后剩下就是熟练度的问题了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑