MyBatis 与 MyBatis-Plus:从入门到精通的完整指南

在 Java 后端开发中,持久层框架的选择直接影响开发效率和代码质量。MyBatis 作为经典的半自动 ORM 框架,以其灵活的 SQL 控制能力深受开发者喜爱;而 MyBatis-Plus 则在此基础上进一步封装,大幅提升了开发效率。本文将从基础概念出发,逐步深入,带你全面理解两者的关系与使用场景。


一、什么是 MyBatis?------ 持久层框架的基石

1.1 为什么需要 ORM 框架?

在没有 ORM(对象关系映射)框架的时代,Java 开发者需要手写 JDBC 代码来操作数据库:

java 复制代码
// 纯 JDBC 方式(繁琐且易出错)
Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
ps.setLong(1, 1L);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
    User user = new User();
    user.setId(rs.getLong("id"));
    user.setName(rs.getString("name"));
    // ... 手动映射每一个字段
}
rs.close(); ps.close(); conn.close();

这种方式存在明显问题:

  • 代码冗余:每次都要写连接、预编译、结果集映射、资源释放
  • SQL 硬编码:SQL 语句散落在 Java 代码中,难以维护
  • 类型转换繁琐:需要手动处理数据库类型与 Java 类型的映射

1.2 MyBatis 的诞生

MyBatis 是一款优秀的半自动 ORM 框架,它解决了上述痛点:

  • SQL 与代码分离:SQL 写在 XML 文件中,便于管理和优化
  • 自动结果映射:通过配置将数据库字段自动映射到 Java 对象
  • 动态 SQL 支持 :通过标签(如 <if><foreach>)灵活构建 SQL

1.3 MyBatis 的核心组件

组件 作用
SqlSessionFactory 创建 SqlSession 的工厂类
SqlSession 执行 SQL 的会话,线程不安全
Mapper 接口 定义数据库操作方法
Mapper XML 存放具体的 SQL 语句
Configuration MyBatis 的全局配置

1.4 一个完整的 MyBatis 示例

实体类:

java 复制代码
@Data
public class User {
    private Long id;
    private String username;
    private Integer age;
    private String email;
}

Mapper 接口:

java 复制代码
public interface UserMapper {
    User selectById(Long id);
    List<User> selectByCondition(@Param("username") String username, 
                                  @Param("minAge") Integer minAge);
    int insert(User user);
    int updateById(User user);
    int deleteById(Long id);
}

Mapper XML:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    
    <resultMap id="BaseResultMap" type="com.example.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="user_name"/>
        <result property="age" column="age"/>
        <result property="email" column="email"/>
    </resultMap>
    
    <sql id="Base_Column_List">
        id, user_name, age, email
    </sql>
    
    <select id="selectById" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM user WHERE id = #{id}
    </select>
    
    <!-- 动态条件查询 -->
    <select id="selectByCondition" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM user
        <where>
            <if test="username != null and username != ''">
                AND user_name LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="minAge != null">
                AND age >= #{minAge}
            </if>
        </where>
    </select>
    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (user_name, age, email)
        VALUES (#{username}, #{age}, #{email})
    </insert>
</mapper>

调用方式:

java 复制代码
@Autowired
private UserMapper userMapper;

public void demo() {
    // 根据 ID 查询
    User user = userMapper.selectById(1L);
    
    // 条件查询
    List<User> list = userMapper.selectByCondition("张", 18);
    
    // 新增
    User newUser = new User();
    newUser.setUsername("李四");
    newUser.setAge(25);
    userMapper.insert(newUser);
}

1.5 MyBatis 的优缺点

优点 缺点
SQL 完全可控,便于优化 单表 CRUD 需重复编写大量 SQL
动态 SQL 灵活强大 XML 文件过多,项目结构复杂
学习曲线平缓,只需掌握 SQL 分页、批量操作需自行实现或集成插件
性能接近原生 JDBC 字段映射需手动维护

二、MyBatis-Plus 是什么?------ 效率革命的增强工具

2.1 定位与理念

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具 ,它的核心理念是 "只做增强不做改变" 。它在完全兼容 MyBatis 的基础上,封装了高频的 CRUD 操作,让开发者从重复的 SQL 编写中解放出来。

可以把 MyBatis-Plus 理解为 MyBatis 的"超级外挂"------你仍然可以使用 MyBatis 的全部能力,同时获得了大量开箱即用的便捷功能。

2.2 引入依赖

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.9</version>
</dependency>

2.3 第一个 MyBatis-Plus 程序

实体类(添加注解):

java 复制代码
@Data
@TableName("user")  // 指定表名
public class User {
    
    @TableId(type = IdType.AUTO)  // 主键策略:数据库自增
    private Long id;
    
    @TableField("user_name")  // 字段映射
    private String username;
    
    private Integer age;
    private String email;
}

Mapper 接口(继承 BaseMapper):

java 复制代码
public interface UserMapper extends BaseMapper<User> {
    // 无需编写任何方法!BaseMapper 已提供 17+ 个通用方法
}

Service 层(可选,但推荐):

java 复制代码
// Service 接口
public interface UserService extends IService<User> {}

// Service 实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> 
    implements UserService {}

使用示例:

java 复制代码
@Autowired
private UserService userService;

public void demo() {
    // 新增
    User user = new User();
    user.setUsername("王五");
    user.setAge(30);
    userService.save(user);
    
    // 根据 ID 查询
    User u = userService.getById(1L);
    
    // 查询所有
    List<User> list = userService.list();
    
    // 条件查询
    List<User> adults = userService.lambdaQuery()
        .ge(User::getAge, 18)
        .list();
    
    // 分页查询
    Page<User> page = userService.page(new Page<>(1, 10));
    
    // 批量保存
    userService.saveBatch(userList, 500);
    
    // 根据 ID 删除
    userService.removeById(1L);
}

对比总结: 原本需要编写几十行 XML 的 CRUD 操作,现在只需继承一个接口即可实现 。


三、核心功能详解 ------ 从基础到高级

3.1 基础 CRUD:BaseMapper 与 IService

MyBatis-Plus 的 BaseMapper 内置了丰富的通用方法 :

方法 说明 示例
insert(T entity) 插入记录 userMapper.insert(user)
deleteById(Serializable id) 根据 ID 删除 userMapper.deleteById(1L)
deleteByMap(Map<String, Object>) 根据 Map 条件删除 -
delete(Wrapper<T> wrapper) 条件删除 -
updateById(T entity) 根据 ID 更新(非 null 字段) userMapper.updateById(user)
update(T entity, Wrapper<T> wrapper) 条件更新 -
selectById(Serializable id) 根据 ID 查询 userMapper.selectById(1L)
selectBatchIds(Collection<?>) 批量 ID 查询 -
selectByMap(Map<String, Object>) Map 条件查询 -
selectOne(Wrapper<T> wrapper) 查询单条 -
selectCount(Wrapper<T> wrapper) 查询总数 -
selectList(Wrapper<T> wrapper) 条件查询列表 -
selectPage(Page<T> page, Wrapper<T>) 分页查询 -

IServiceBaseMapper 基础上进一步封装,提供了更符合业务语义的方法,如 save()saveOrUpdate()saveBatch() 等 。

3.2 条件构造器:QueryWrapper 与 LambdaQueryWrapper

这是 MyBatis-Plus 最强大的功能之一,用于动态拼接 SQL 条件

传统 MyBatis 动态 SQL:

xml 复制代码
<select id="selectByCondition" resultMap="BaseResultMap">
    SELECT * FROM user
    <where>
        <if test="name != null">AND user_name LIKE #{name}</if>
        <if test="age != null">AND age = #{age}</if>
        <if test="email != null">AND email = #{email}</if>
    </where>
</select>

MyBatis-Plus LambdaQueryWrapper:

java 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
    .like(StringUtils.isNotBlank(name), User::getUsername, name)  // 条件成立才拼接
    .eq(age != null, User::getAge, age)
    .eq(email != null, User::getEmail, email)
    .ge(User::getAge, 18)           // 大于等于
    .le(User::getAge, 60)           // 小于等于
    .between(User::getAge, 18, 30)  // 范围查询
    .likeRight(User::getUsername, "张")  // 右模糊:张%
    .orderByDesc(User::getCreateTime)     // 降序排序
    .last("LIMIT 10");                    // 追加 SQL

List<User> list = userMapper.selectList(wrapper);

核心优势:

  • 类型安全User::getUsername 是方法引用,编译期检查,避免字段名拼写错误
  • 动态条件 :第一个参数为 false 时自动跳过该条件,等效于 XML 的 <if> 标签
  • 链式调用:代码简洁,可读性高

3.3 分页插件

MyBatis-Plus 内置分页插件,无需手动编写 LIMIT 语句

配置(Spring Boot 3.5+ 可自动注入,无需手动配置):

java 复制代码
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

使用:

java 复制代码
// 简单分页
Page<User> page = new Page<>(1, 10);  // 第1页,每页10条
Page<User> result = userMapper.selectPage(page, null);

System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页数据:" + result.getRecords());

// 带条件的分页
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery()
    .ge(User::getAge, 18);
Page<User> result2 = userService.page(new Page<>(1, 10), wrapper);

自定义 SQL 分页(多表联查场景):

java 复制代码
// Mapper 接口
public interface UserMapper extends BaseMapper<User> {
    IPage<UserVO> selectUserWithDept(Page<User> page, @Param("deptId") Long deptId);
}

// XML(无需写 LIMIT,MP 自动拼接)
<select id="selectUserWithDept" resultType="com.example.vo.UserVO">
    SELECT u.*, d.dept_name 
    FROM user u 
    LEFT JOIN dept d ON u.dept_id = d.id
    WHERE u.dept_id = #{deptId}
</select>

3.4 代码生成器

MyBatis-Plus 提供代码生成器,可根据数据库表结构自动生成 Entity、Mapper、Service、Controller 等代码 。

java 复制代码
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mydb", "root", "password")
    .globalConfig(builder -> {
        builder.author("YourName")
               .outputDir(System.getProperty("user.dir") + "/src/main/java");
    })
    .packageConfig(builder -> {
        builder.parent("com.example")
               .entity("entity")
               .mapper("mapper")
               .service("service")
               .controller("controller");
    })
    .strategyConfig(builder -> {
        builder.addInclude("user", "order", "product")  // 指定要生成的表
               .entityBuilder()
               .enableLombok()
               .enableTableFieldAnnotation();
    })
    .templateEngine(new FreemarkerTemplateEngine())
    .execute();

3.5 逻辑删除

逻辑删除通过标记字段实现"假删除",保障数据可恢复性 。

配置:

yaml 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted      # 全局逻辑删除字段
      logic-delete-value: 1            # 已删除值
      logic-not-delete-value: 0        # 未删除值

实体类:

java 复制代码
@Data
public class User {
    private Long id;
    private String username;
    
    @TableLogic  // 逻辑删除字段
    private Integer deleted;
}

效果:

  • userService.removeById(1L) → 自动执行 UPDATE user SET deleted = 1 WHERE id = 1
  • userService.getById(1L) → 自动拼接 AND deleted = 0
  • 已删除数据不会被基础查询方法检索到

3.6 自动填充(审计字段)

自动处理 create_timeupdate_time 等通用字段 。

实体类:

java 复制代码
@Data
public class User {
    private Long id;
    private String username;
    
    @TableField(fill = FieldFill.INSERT)         // 插入时填充
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)  // 插入和更新时填充
    private LocalDateTime updateTime;
}

处理器:

java 复制代码
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
    
    @Override
    public void updateFill(MetaObject metaObject) {
        strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

3.7 乐观锁

解决并发更新冲突,通过版本号机制实现 。

配置:

java 复制代码
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

实体类:

java 复制代码
@Data
public class User {
    private Long id;
    private String username;
    
    @Version  // 乐观锁版本号
    private Integer version;
}

原理:

sql 复制代码
-- 查询时获取 version = 1
-- 更新时自动拼接:
UPDATE user SET username = '新名字', version = 2 
WHERE id = 1 AND version = 1

-- 如果 version 已被其他线程修改,则更新失败(影响行数为0)

3.8 主键策略

MyBatis-Plus 支持多种主键生成策略 :

策略 说明 适用场景
AUTO 数据库自增 单机数据库
ASSIGN_ID 雪花算法生成 Long 类型 ID 分布式系统(默认策略)
ASSIGN_UUID 生成 UUID 字符串 需要全局唯一标识
INPUT 用户手动输入 已有 ID 生成规则
NONE 无策略,跟随全局配置 -
java 复制代码
@TableId(type = IdType.ASSIGN_ID)  // 雪花算法
private Long id;

四、MyBatis vs MyBatis-Plus 深度对比

4.1 整体对比

维度 MyBatis MyBatis-Plus
SQL 定义 手写 XML 或注解 通用 CRUD 自动生成,复杂 SQL 仍可手写
表名映射 XML 中写死 @TableName 注解声明
字段映射 XML 中逐个 #{} 绑定 自动驼峰转下划线
单表 CRUD 需手写 SQL 继承 BaseMapper,零 SQL
条件查询 <if> 动态 SQL 标签 Lambda 链式条件构造器
分页 需手写 LIMIT 或集成插件 内置分页插件,开箱即用
批量操作 手写 <foreach> 内置 saveBatch()updateBatchById()
代码生成 无内置工具 内置代码生成器
逻辑删除 手动在 SQL 中添加条件 @TableLogic 注解自动实现
乐观锁 需自行实现 内置插件,配置即用
自动填充 手动设置 MetaObjectHandler 自动处理
学习成本 中(需掌握 SQL 和 XML) 中低(基于 MyBatis,易于上手)

4.2 代码量对比

MyBatis 传统方式:

复制代码
├── mapper/
│   ├── UserMapper.java          # Mapper 接口(5+ 个方法)
│   └── UserMapper.xml           # SQL 映射文件(50+ 行 XML)
├── domain/
│   └── User.java                # 实体类

MyBatis-Plus 方式:

复制代码
├── mapper/
│   └── UserMapper.java          # 继承 BaseMapper,无需 XML
├── entity/
│   └── User.java                # 带注解的实体类
├── service/
│   └── UserService.java         # 继承 IService
└── impl/
    └── UserServiceImpl.java     # 继承 ServiceImpl

五、如何选择?------ 场景化建议

5.1 选择建议

场景 推荐方案
简单 CRUD、快速开发 MyBatis-Plus --- 零 XML,效率高
复杂多表关联查询 MyBatis XML --- 灵活度更高
需要精细控制 SQL 性能 MyBatis XML --- 可手动优化索引和执行计划
新项目、微服务 MyBatis-Plus --- 开发效率优先
老项目维护 保持现有方式,避免混用带来的认知负担

5.2 最佳实践

MyBatis-Plus 并不排斥 XML 。在同一个项目中,推荐采用混合策略

  • 简单单表操作:使用 MyBatis-Plus 的 BaseMapper / IService,零 SQL 开发
  • 复杂多表查询:仍然编写 Mapper XML,保留 MyBatis 的灵活性
  • 性能敏感场景:手写 SQL 进行针对性优化
java 复制代码
public interface UserMapper extends BaseMapper<User> {
    // 简单 CRUD 使用 BaseMapper 内置方法
    
    // 复杂查询仍然可以自定义 SQL
    @Select("SELECT u.*, d.dept_name FROM user u LEFT JOIN dept d ON u.dept_id = d.id WHERE u.id = #{id}")
    UserVO selectUserWithDept(Long id);
    
    // 或者写在 XML 中
    List<UserVO> selectComplexUserList(@Param("query") UserQueryDTO query);
}

六、总结

MyBatis 是 Java 持久层开发的基石,它提供了灵活的 SQL 控制能力和优秀的性能表现。而 MyBatis-Plus 在此基础上,通过通用 CRUD、条件构造器、分页插件、代码生成器等增强功能,将开发效率提升到了新的高度 。

两者的关系并非替代,而是增强与兼容

  • 如果你是 MyBatis 老用户,引入 MyBatis-Plus 几乎零成本,它能立即为你减少 80% 的重复代码
  • 如果你是新项目开发者,直接使用 MyBatis-Plus 可以让你更专注于业务逻辑,而非繁琐的 SQL 编写

记住 MyBatis-Plus 的核心理念:只做增强不做改变。 它不会限制你手写 SQL 的能力,而是在你需要的时候,提供最便捷的自动化支持。

💡 一句话总结:MyBatis 让你掌控每一行 SQL,MyBatis-Plus 让你从重复的 SQL 中解放出来------两者结合,方为最佳实践。

相关推荐
DolphinScheduler社区3 小时前
DolphinScheduler 3.3.2 如何调用 DataX 3.0 + SeaTunnel 2.3.12?附 Demo演示!
java·spark·apache·海豚调度·大数据工作流调度
亦暖筑序3 小时前
AI 客服系统安全加固:JWT 鉴权 + Bucket4j 三层限流
java·架构
xhuiting3 小时前
项目技术总结
java
某人辛木3 小时前
JDK安装配置
java·开发语言
counting money3 小时前
Spring框架基础(依赖注入-全注解形式)
java·数据库·spring
小王师傅663 小时前
【Java结构化梳理】泛型-初步了解-下
java·开发语言
逝水如流年轻往返染尘3 小时前
JAVA中的String类
java
一只叫煤球的猫3 小时前
ThreadForge 1.2.0 发布:让 Java 并发代码更好写,这次补齐了高阶编排、示例与观测能力
java·设计模式·设计
counting money4 小时前
Spring框架基础(依赖注入-半注解形式)
java·后端·spring