1. MyBatis与MyBatis-Plus的前世今生作为Java开发者持久层框架的选择直接影响着开发效率和系统性能。十年前我刚入行时项目组还在用原始的JDBC手写SQL直到遇见了MyBatis这个半自动化ORM框架才真正体会到什么是高效的数据库操作。而MyBatis-Plus的出现则让我们的CRUD操作变得更加优雅。MyBatis最初是Apache的一个开源项目iBatis2010年迁移到Google Code后改名为MyBatis。它的核心设计理念是SQL与代码分离通过XML或注解配置SQL语句再通过简单的映射关系将结果集转换为Java对象。这种设计既保留了SQL的灵活性又避免了JDBC的样板代码。MyBatis-Plus简称MP则是国内团队在MyBatis基础上开发的增强工具它保留了MyBatis的所有特性同时内置了大量常用功能。就像给你的MyBatis装上了涡轮增压器让常规开发效率直接翻倍。2. 核心架构对比理解设计哲学差异2.1 MyBatis的核心组件MyBatis的架构设计非常清晰主要包含以下几个核心组件SqlSessionFactory每个MyBatis应用的核心通过它创建SqlSessionSqlSession包含执行SQL命令的所有方法Mapper接口定义数据库操作方法Mapper XML编写SQL语句的地方ExecutorSQL语句的实际执行者这种设计让SQL与Java代码解耦但也意味着开发者需要编写更多配置代码。比如一个简单的用户查询我们需要定义UserMapper接口编写对应的Mapper XML文件在XML中编写SELECT语句配置结果映射public interface UserMapper { User selectById(Long id); }!-- UserMapper.xml -- select idselectById resultTypecom.example.User SELECT * FROM user WHERE id #{id} /select2.2 MyBatis-Plus的增强设计MP在MyBatis基础上做了几项关键增强通用Mapper内置常用CRUD方法无需编写XML条件构造器通过链式调用构建查询条件代码生成器自动生成Entity、Mapper、Service等代码分页插件内置物理分页支持性能分析插件帮助优化SQL性能同样的用户查询在MP中可以简化为User user userMapper.selectById(1L);或者使用Lambda表达式ListUser users userMapper.selectList( Wrappers.UserlambdaQuery() .eq(User::getName, 张三) .gt(User::getAge, 18) );注意虽然MP提供了便利但它并不是要替代MyBatis而是在其基础上提供更多开箱即用的功能。复杂的SQL仍然可以通过MyBatis原生方式实现。3. 实战对比从CRUD到高级特性3.1 基础CRUD操作MyBatis方式 每个CRUD操作都需要在Mapper接口定义方法并在XML中编写SQL// 接口定义 public interface UserMapper { int insert(User user); int update(User user); int delete(Long id); User selectById(Long id); ListUser selectAll(); }!-- XML配置 -- insert idinsert parameterTypecom.example.User INSERT INTO user(name, age) VALUES(#{name}, #{age}) /insertMyBatis-Plus方式 MP的BaseMapper已经内置了这些方法public interface UserMapper extends BaseMapperUser { // 无需定义基础CRUD方法 } // 使用示例 userMapper.insert(user); userMapper.updateById(user); userMapper.deleteById(1L); User user userMapper.selectById(1L);3.2 条件查询对比MyBatis动态SQL 需要手写XML中的 、 等标签select idselectByCondition resultTypeUser SELECT * FROM user where if testname ! null AND name #{name} /if if testage ! null AND age #{age} /if /where /selectMyBatis-Plus条件构造器 使用Java代码构建查询条件QueryWrapperUser wrapper new QueryWrapper(); wrapper.eq(name, 张三) .gt(age, 18) .orderByDesc(create_time); ListUser users userMapper.selectList(wrapper);或者更优雅的Lambda方式ListUser users userMapper.selectList( Wrappers.UserlambdaQuery() .eq(User::getName, 张三) .gt(User::getAge, 18) .orderByDesc(User::getCreateTime) );3.3 分页实现差异MyBatis分页 需要手动处理分页逻辑或者使用PageHelper插件// 使用PageHelper PageHelper.startPage(1, 10); ListUser users userMapper.selectAll(); PageInfoUser pageInfo new PageInfo(users);MyBatis-Plus分页 内置分页插件配置后直接使用// 配置分页插件 Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } } // 使用分页 PageUser page new Page(1, 10); PageUser result userMapper.selectPage(page, null);4. 高级特性与实战技巧4.1 代码生成器的妙用MP的代码生成器可以大幅减少重复劳动。以下是一个典型配置FastAutoGenerator.create(jdbc:mysql://localhost:3306/test, root, password) .globalConfig(builder - { builder.author(baomidou) // 设置作者 .outputDir(D://); // 指定输出目录 }) .packageConfig(builder - { builder.parent(com.example) // 设置父包名 .moduleName(system) // 设置模块名 }) .strategyConfig(builder - { builder.addInclude(user, role) // 设置需要生成的表名 .entityBuilder() .enableLombok() // 启用Lombok .controllerBuilder() .enableRestStyle(); // 开启RestController }) .execute();运行后会生成Entity、Mapper、Service、Controller等全套代码特别是对于管理后台这类CRUD密集的系统能节省70%以上的重复代码编写时间。4.2 乐观锁实现并发控制MP提供了优雅的乐观锁实现方式在表中添加version字段在Entity中添加Version注解配置乐观锁插件Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } // Entity类 public class User { Version private Integer version; // 其他字段... }更新时会自动检查版本号User user userMapper.selectById(1L); user.setName(新名字); userMapper.updateById(user); // 会自动带上version条件4.3 多租户架构实现对于SaaS系统MP提供了多租户支持Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() { Override public String getTenantIdColumn() { return tenant_id; } Override public Expression getTenantId() { return new LongValue(1L); // 实际应从上下文中获取 } Override public boolean ignoreTable(String tableName) { return !user.equalsIgnoreCase(tableName); // 指定哪些表需要过滤 } })); return interceptor; }配置后所有相关SQL都会自动加上tenant_id条件避免跨租户数据泄露。5. 性能优化与最佳实践5.1 SQL性能分析MP的SQL性能分析插件可以帮助发现慢查询Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor interceptor new PerformanceInterceptor(); interceptor.setMaxTime(1000); // 设置SQL执行最大时间(ms)超过会抛出异常 interceptor.setFormat(true); // 格式化SQL return interceptor; }警告生产环境慎用此插件建议只在开发测试环境开启因为它会带来额外的性能开销。5.2 逻辑删除的最佳实践逻辑删除是业务系统的常见需求MP提供了优雅的实现在表中添加deleted字段在Entity中添加TableLogic注解配置逻辑删除值// Entity类 public class User { TableLogic private Integer deleted; // 其他字段... } // 配置(可选默认0未删除1已删除) mybatis-plus: global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0配置后deleteById方法会自动变为逻辑删除查询也会自动过滤已删除数据。5.3 枚举类型的优雅处理MP支持将数据库字段与Java枚举自动转换// 定义枚举 public enum Gender { MALE(1, 男), FEMALE(2, 女); EnumValue // 标记数据库存储的值 private final int code; private final String desc; // 构造方法、getter等 } // Entity类 public class User { private Gender gender; // 其他字段... }这样gender字段在数据库中存储的是1或2而在Java对象中会自动转换为对应的枚举值。6. 常见问题与解决方案6.1 如何混用MP和原生MyBatisMP完全兼容原生MyBatis可以在同一个项目中混合使用public interface UserMapper extends BaseMapperUser { // MP提供的方法 // 自定义的MyBatis方法 Select(SELECT * FROM user WHERE name #{name}) ListUser selectByName(String name); // XML配置的方法 ListUser selectComplexQuery(UserQuery query); }对应的XML文件仍然可以正常使用MP不会影响原生功能。6.2 多表联查如何处理MP主要针对单表操作对于复杂联查建议使用Select注解编写SQL使用XML配置复杂SQL对于一对多查询可以使用Results注解Select(SELECT u.*, r.role_name FROM user u LEFT JOIN user_role ur ON u.id ur.user_id LEFT JOIN role r ON ur.role_id r.id WHERE u.id #{id}) Results({ Result(property id, column id), Result(property roles, column id, many Many(select com.example.mapper.RoleMapper.selectByUserId)) }) User selectUserWithRoles(Long id);6.3 如何自定义ID生成策略MP默认提供了几种ID生成策略// Entity类 public class User { TableId(type IdType.AUTO) // 数据库自增 private Long id; // 其他字段... }可选的IdType包括AUTO数据库ID自增NONE未设置主键INPUT用户输入IDASSIGN_ID分配ID(默认雪花算法)ASSIGN_UUID分配UUID也可以自定义生成器public class CustomIdGenerator implements IdentifierGenerator { Override public Number nextId(Object entity) { // 实现自定义ID生成逻辑 return IdWorker.getId(); } } // 配置 Bean public IdentifierGenerator idGenerator() { return new CustomIdGenerator(); }7. 项目选型建议经过多年的实战经验我总结出以下选型建议新项目直接使用MyBatis-Plus它能显著提升开发效率特别是管理后台类项目老项目如果已经是MyBatis项目可以逐步引入MP功能两者完全兼容简单CRUD优先使用MP提供的方法减少重复代码复杂SQL仍然使用MyBatis原生方式保持灵活性性能敏感注意MP某些特性(如分页)的性能影响必要时可回归原生方式在实际项目中我通常会这样组织代码结构src/main/java ├── com.example │ ├── config │ │ └── MybatisPlusConfig.java // MP配置 │ ├── controller │ ├── entity // 实体类 │ ├── mapper // 继承BaseMapper │ ├── service │ │ ├── impl │ │ └── IService.java // 可选扩展通用Service │ └── vo // 视图对象 src/main/resources ├── mapper │ ├── UserMapper.xml // 复杂SQL的XML配置这种结构既利用了MP的便利性又保留了MyBatis的灵活性能够适应大多数业务场景。