1. MyBatis与MyBatis-Plus的前世今生
作为Java开发者,持久层框架的选择直接影响着开发效率和系统性能。十年前我刚入行时,项目组还在用原始的JDBC手写SQL,直到遇见了MyBatis这个"半自动化"ORM框架,才真正体会到什么是高效的数据库操作。而MyBatis-Plus的出现,则让我们的CRUD操作变得更加优雅。
MyBatis最初是Apache的一个开源项目iBatis,2010年迁移到Google Code后改名为MyBatis。它的核心设计理念是"SQL与代码分离",通过XML或注解配置SQL语句,再通过简单的映射关系将结果集转换为Java对象。这种设计既保留了SQL的灵活性,又避免了JDBC的样板代码。
MyBatis-Plus(简称MP)则是国内团队在MyBatis基础上开发的增强工具,它保留了MyBatis的所有特性,同时内置了大量常用功能。就像给你的MyBatis装上了涡轮增压器,让常规开发效率直接翻倍。
2. 核心架构对比:理解设计哲学差异
2.1 MyBatis的核心组件
MyBatis的架构设计非常清晰,主要包含以下几个核心组件:
- SqlSessionFactory:每个MyBatis应用的核心,通过它创建SqlSession
- SqlSession:包含执行SQL命令的所有方法
- Mapper接口:定义数据库操作方法
- Mapper XML:编写SQL语句的地方
- Executor:SQL语句的实际执行者
这种设计让SQL与Java代码解耦,但也意味着开发者需要编写更多配置代码。比如一个简单的用户查询,我们需要:
- 定义UserMapper接口
- 编写对应的Mapper XML文件
- 在XML中编写SELECT语句
- 配置结果映射
public interface UserMapper { User selectById(Long id); }<!-- UserMapper.xml --> <select id="selectById" resultType="com.example.User"> SELECT * FROM user WHERE id = #{id} </select>2.2 MyBatis-Plus的增强设计
MP在MyBatis基础上做了几项关键增强:
- 通用Mapper:内置常用CRUD方法,无需编写XML
- 条件构造器:通过链式调用构建查询条件
- 代码生成器:自动生成Entity、Mapper、Service等代码
- 分页插件:内置物理分页支持
- 性能分析插件:帮助优化SQL性能
同样的用户查询,在MP中可以简化为:
User user = userMapper.selectById(1L);或者使用Lambda表达式:
List<User> users = userMapper.selectList( Wrappers.<User>lambdaQuery() .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); List<User> selectAll(); }<!-- XML配置 --> <insert id="insert" parameterType="com.example.User"> INSERT INTO user(name, age) VALUES(#{name}, #{age}) </insert>MyBatis-Plus方式: MP的BaseMapper已经内置了这些方法:
public interface UserMapper extends BaseMapper<User> { // 无需定义基础CRUD方法 } // 使用示例 userMapper.insert(user); userMapper.updateById(user); userMapper.deleteById(1L); User user = userMapper.selectById(1L);3.2 条件查询对比
MyBatis动态SQL: 需要手写XML中的 、 等标签:
<select id="selectByCondition" resultType="User"> SELECT * FROM user <where> <if test="name != null"> AND name = #{name} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>MyBatis-Plus条件构造器: 使用Java代码构建查询条件:
QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name", "张三") .gt("age", 18) .orderByDesc("create_time"); List<User> users = userMapper.selectList(wrapper);或者更优雅的Lambda方式:
List<User> users = userMapper.selectList( Wrappers.<User>lambdaQuery() .eq(User::getName, "张三") .gt(User::getAge, 18) .orderByDesc(User::getCreateTime) );3.3 分页实现差异
MyBatis分页: 需要手动处理分页逻辑,或者使用PageHelper插件:
// 使用PageHelper PageHelper.startPage(1, 10); List<User> users = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<>(users);MyBatis-Plus分页: 内置分页插件,配置后直接使用:
// 配置分页插件 @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } } // 使用分页 Page<User> page = new Page<>(1, 10); Page<User> 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和原生MyBatis?
MP完全兼容原生MyBatis,可以在同一个项目中混合使用:
public interface UserMapper extends BaseMapper<User> { // MP提供的方法 // 自定义的MyBatis方法 @Select("SELECT * FROM user WHERE name = #{name}") List<User> selectByName(String name); // XML配置的方法 List<User> 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:用户输入ID
- ASSIGN_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的灵活性,能够适应大多数业务场景。