ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MyBatis-Plus保存带ID数据的实现与优化

MyBatis-Plus保存带ID数据的实现与优化 1. MyBatis-Plus保存带ID数据的核心机制在MyBatis-Plus中保存带有预定义ID的数据本质上是在处理主键生成策略与数据库交互的协同问题。框架默认提供了三种主键处理模式ASSIGN_ID默认策略采用雪花算法生成19位数字IDASSIGN_UUID生成32位不含连字符的UUID字符串AUTO依赖数据库自增机制当我们需要保存带预定义ID的数据时实际上是在使用ASSIGN_ID策略的变体——由开发者而非框架来分配ID值。这种场景常见于数据迁移、分布式系统ID预生成等业务场景。关键点MyBatis-Plus通过TableId注解的type属性控制主键生成策略。当实体对象已设置id属性值时无论采用何种策略框架都会优先使用该值而非自动生成。2. 保存带ID数据的三种实现方式2.1 基础保存方式最直接的保存方式是直接设置实体对象的id属性后调用save方法User user new User(); user.setId(123456L); // 显式设置ID user.setName(测试用户); user.setAge(30); userService.save(user);这种方式的底层执行逻辑是MyBatis-Plus检测到实体已设置ID值跳过ID生成器逻辑直接执行INSERT语句包含ID字段2.2 批量保存带ID数据对于批量保存场景可以使用saveBatch方法ListUser userList new ArrayList(); // 构建带ID的对象列表 userList.add(new User().setId(1001L).setName(用户1)); userList.add(new User().setId(1002L).setName(用户2)); userService.saveBatch(userList);批量保存时需要注意MySQL等数据库需要确保批量插入语句支持显式ID设置批量大小建议控制在1000条以内以避免内存溢出事务管理要合理设置2.3 使用自定义ID生成器对于需要特定ID生成规则的场景可以实现IdentifierGenerator接口Component public class CustomIdGenerator implements IdentifierGenerator { Override public Number nextId(Object entity) { // 业务逻辑生成ID return BusinessIdGenerator.generate(); } }配置后框架会优先使用自定义生成器但仍会尊重实体对象中预设的ID值。3. 不同数据库的适配处理3.1 MySQL数据库MySQL在使用自增主键时需要确保表定义允许显式插入IDCREATE TABLE user ( id BIGINT NOT NULL, -- 注意不是AUTO_INCREMENT name VARCHAR(255), PRIMARY KEY (id) );3.2 Oracle数据库Oracle通常使用序列需要关闭序列触发TableId(value ID, type IdType.INPUT) // 显式指定INPUT类型 private Long id;3.3 PostgreSQL数据库PostgreSQL需要处理序列缓存问题TableId(value id, type IdType.ASSIGN_ID) private Long id;4. 实战中的常见问题与解决方案4.1 ID冲突问题当插入的ID已存在时会抛出唯一键冲突异常。解决方案包括先查询后插入if (!userService.exists(queryWrapper)) { userService.save(user); }使用REPLACE INTO语法MySQLInsert(REPLACE INTO user(id,name) VALUES(#{id},#{name})) int forceSave(User user);4.2 事务一致性处理带ID的保存操作在分布式系统中需要考虑Transactional public void saveWithId(User user) { // 前置校验 validateUser(user); // 保存操作 userService.save(user); // 后续处理 sendEvent(user); }4.3 性能优化建议批量保存时使用rewriteBatchedStatementstrueMySQL合理设置JDBC批处理大小mybatis-plus: global-config: db-config: batch-size: 10005. 高级应用场景5.1 分布式ID预生成方案结合分布式ID生成服务如美团的Leaf、Twitter的Snowflakepublic void saveWithDistributedId(User user) { long id idService.generate(); // 获取分布式ID user.setId(id); userService.save(user); }5.2 多租户系统中的ID处理在多租户环境下ID可能需要包含租户信息public class TenantIdGenerator implements IdentifierGenerator { Override public Number nextId(Object entity) { String tenantId TenantContext.getCurrentTenant(); return Long.parseLong(tenantId System.currentTimeMillis()); } }5.3 历史数据迁移方案对于历史数据迁移可以采用分批处理Scheduled(fixedDelay 5000) public void migrateData() { ListLegacyUser legacyUsers legacyDao.findBatch(lastId, 100); legacyUsers.forEach(legacy - { User user convert(legacy); user.setId(legacy.getOldId()); // 保持原始ID userService.save(user); }); }6. 调试与问题排查技巧6.1 查看实际执行的SQL启用MyBatis-Plus的SQL日志logging: level: com.baomidou.mybatisplus: debug6.2 验证ID生成策略通过单元测试验证配置Test public void testIdGeneration() { User user new User(); user.setName(测试); userService.save(user); assertNotNull(user.getId()); // 验证ID生成 User predefined new User(); predefined.setId(999L); userService.save(predefined); assertEquals(999L, predefined.getId()); // 验证预设ID }6.3 常见错误处理ID为null检查TableId注解配置类型不匹配确保Java类型与数据库类型一致批量插入失效检查数据库驱动配置7. 最佳实践建议明确ID策略在项目初期确定好ID生成方案保持一致性整个系统使用同一种ID生成方式考虑扩展性ID长度要预留足够空间文档化约定团队内部明确ID使用规范对于新项目我通常建议使用ASSIGN_ID作为默认策略重要业务实体采用业务可读的ID格式建立ID转换层处理不同系统的ID映射在最近的一个电商项目中我们采用时间戳业务编码随机数的ID格式既保证了唯一性又包含了业务信息在问题排查时非常有用。例如20240615130130012_ORDER_4582这样的ID可以直接反映出创建时间和业务类型。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进