在 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>) |
分页查询 | - |
IService 在 BaseMapper 基础上进一步封装,提供了更符合业务语义的方法,如 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 = 1userService.getById(1L)→ 自动拼接AND deleted = 0- 已删除数据不会被基础查询方法检索到
3.6 自动填充(审计字段)
自动处理 create_time、update_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 中解放出来------两者结合,方为最佳实践。