Mybatis的变更

一、MyBatis 到底解决什么问题

MyBatis 是一个持久层框架,支持自定义 SQL、存储过程和高级映射,目标是减少几乎所有 JDBC 模板代码,并把参数设置、结果取回这些脏活抽掉。
官方介绍里写得很直白:它可以用 XML 或注解 来配置,把基本类型、Map、POJO 映射到数据库记录。

你可以这样理解

JDBC 时代你得自己写:

1
2
3
4
5
Connection conn = dataSource.getConnection();  
PreparedStatement ps = conn.prepareStatement("select * from tb_user where id = ?");
ps.setLong(1, 1L);
ResultSet rs = ps.executeQuery();
// 手动取字段,手动封装对象

MyBatis 的价值在于把下面几件事接住:

  • 安全执行 SQL

  • 参数对象映射到 JDBC 占位符

  • 结果集映射到 Java 对象

  • 动态生成 SQL


二、第一阶段:原生 MyBatis,核心是 XML 映射

这阶段的主角有两个:

1. 全局配置 mybatis-config.xml

官方配置文档给出的高层结构包括:propertiessettingstypeAliasestypeHandlerspluginsenvironmentsdatabaseIdProvidermappers。也就是说它是 MyBatis 的总控配置

2. 每个 Mapper 对应一个 Mapper.xml

官方文档强调,Mapper XML Files 的真正威力在 Mapped Statements,也就是 SQL 映射定义本身。


三、原生 XML 写法长什么样

1. 全局配置例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<typeAliases>
<package name="com.example.pojo"/>
</typeAliases>

<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>

<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>

</configuration>

2. Mapper 接口

1
2
3
public interface UserMapper {  
User selectById(Long id);
}

3. Mapper XML

1
2
3
4
5
<mapper namespace="com.example.mapper.UserMapper">  
<select id="selectById" resultType="User">
select * from tb_user where id = #{id}
</select>
</mapper>

这里的关键是:

  • namespace 对应接口全限定名

  • id 对应方法名

  • #{} 做参数绑定

  • resultType 指定结果类型

官方文档一直强调 Mapper XML 的重点就在这些 Mapped Statements


四、为什么早期大家很爱 XML

SQL 和 Java 分离

复杂 SQL 放 XML,比直接塞进注解里更可读。

结果映射更强

尤其复杂对象映射、关联映射、集合映射,XML 的表达力很强。官方也把 MyBatis 定位成支持“高级映射”的框架。

JDBC 代码大幅减少

官方直接说,相比等价 JDBC 代码,Mapper XML 通常能看到接近 95% 的代码节省。这个说法很夸张,但它反映了设计目标。


五、第二阶段:从“静态 XML”到“动态 XML”

这一步是 MyBatis 真正封神的地方。
因为静态 SQL 很快会碰到组合爆炸:

  • 有 name 就按 name 查

  • 有 age 就按 age 查

  • 两个都有就都加

  • 有 ids 就 IN

  • 有排序就拼 ORDER BY

  • 有更新时间区间就再拼两段

如果每种组合单独写一条 SQL,XML 会直接变成怪兽。

于是 MyBatis 的答案是 动态 SQL。官方文档给出的核心动态标签是:

  • if

  • choose / when / otherwise

  • trim

  • where

  • set

  • foreach

而且官方明确提到,MyBatis 3 相比过去的版本,动态元素数量减少到不到一半,并借助 OGNL 表达式来简化书写。


六、动态 XML 的典型写法

1. if:条件成立才拼接

1
2
3
4
5
6
7
8
9
10
11
<select id="listUsers" resultType="User">  
select * from tb_user
<where>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
</select>

作用:
避免你手动在 Java 里拼字符串。官方把 if 作为最常见的动态 SQL 元素来讲。


2. where:自动处理前导 AND

你手写时最烦的是:

  • 没条件时别出现孤零零的 where

  • 第一个条件别变成 where and ...

<where> 就是为这个准备的。官方把它视为 trim 的一个便利形式。


3. set:动态更新时自动处理逗号

update tb_user name = #{name}, age = #{age}, email = #{email}, where id = #{id}

这样就不用自己费劲处理最后一个逗号。官方也把 set 列为核心动态元素之一。


4. foreach:批量 IN、批量插入、批量删除

1
2
3
4
5
6
7
<select id="selectByIds" resultType="User">  
select * from tb_user
where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>

foreach 是处理集合类参数的经典方式。官方动态 SQL 文档把它作为核心能力保留。


5. choose:多选一逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="queryUser" resultType="User">  
select * from tb_user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="name != null and name != ''">
name = #{name}
</when>
<otherwise>
status = 1
</otherwise>
</choose>
</where>
</select>

这个很像 Java 里的 if else if else


6. trim:更底层、更灵活的裁剪器

1
2
3
4
5
6
7
8
9
10
11
<select id="listUsers" resultType="User">  
select * from tb_user
<trim prefix="where" prefixOverrides="and |or ">
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="status != null">
and status = #{status}
</if>
</trim>
</select>

whereset 本质上都可以看成 trim 的便利包装。


七、你担心的“十几个字段怎么办”到底怎么落地

情况 A:字段 3 到 6 个

动态 XML 非常合适,写起来快,团队也容易看懂。

情况 B:字段 10 个以上,条件还是可选

还能写,但建议开始做分层:

1. 参数对象化

不要十几个参数散落方法签名里。

1
2
3
4
5
6
7
8
public class UserQuery {  
private String name;
private Integer age;
private Integer status;
private LocalDateTime startTime;
private LocalDateTime endTime;
private List<Long> deptIds;
}

2. SQL 片段复用

官方 Mapper XML 提供了 <sql><include> 来复用片段。

id, name, age, status, create_time
1
2
3
4
5
<select id="selectById" resultType="User">  
select <include refid="Base_Column_List"/>
from tb_user
where id = #{id}
</select>

3. 把“筛选条件”和“联表结果映射”分开想

很多人把复杂度全怪到 XML 头上,其实真正复杂的是业务查询本身。


八、第三阶段:注解式 Mapper 出现了

官方介绍里就明确说,MyBatis 可以使用 XML 或注解 进行配置。

典型例子

1
2
3
4
5
6
7
8
@Mapper  
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User selectById(Long id);

@Delete("delete from tb_user where id = #{id}")
int deleteById(Long id);
}

适合场景

  • 简单 CRUD

  • SQL 很短

  • 不想多维护一个 XML

不适合场景

  • 大段复杂 SQL

  • 动态条件很多

  • 复杂结果映射很多

换句话说,注解让小 SQL 更方便,但没有把 XML 淘汰掉。官方 MyBatis Dynamic SQL 甚至在新文档里还写得很直白:总体推荐注解 Mapper,但某些 JOIN 场景仍然需要 XML 来定义结果映射。


九、第四阶段:和 Spring 集成,SqlSession 被藏到幕后

MyBatis 的核心 Java API 是 SqlSession,官方文档明确说:
SqlSession 是使用 MyBatis 的主要 Java 接口,而 SqlSessionFactory 用来创建 SqlSession

原生用法像这样:

1
2
3
4
SqlSession session = sqlSessionFactory.openSession();  
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);
session.close();

后来和 Spring 集成后,很多样板都被框架接管了。
你只看到:

1
2
@Autowired  
private UserMapper userMapper;

背后其实是 Spring 帮你管理了 Mapper 代理、SqlSessionFactorySqlSessionTemplate 等对象。MyBatis Spring Boot 自动配置文档和测试文档都明确提到这些组件会被自动配置。


十、第五阶段:Spring Boot 时代,YAML 接管大部分框架配置

这就是你记忆里的“以前 XML,后来 YAML”。
它的真相是:

1. SQL 映射文件 Mapper.xml 还在

复杂 SQL 仍然常常放 XML。

2. MyBatis 总配置、数据源配置很多被搬到 application.yml

官方 Spring Boot Starter 文档明确支持自动扫描 @Mapper,也支持通过配置和 @MapperScan 做更多控制。

典型 application.yml

1
2
3
4
5
6
7
8
9
10
11
12
spring:  
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.pojo
configuration:
map-underscore-to-camel-case: true

这意味着什么

  • 数据源交给 Spring Boot

  • Mapper 扫描交给 Starter

  • 你还是可以保留 XML 写 SQL

  • 只是“框架装配层”很多不用手写 mybatis-config.xml


十一、第六阶段:Java DSL,MyBatis Dynamic SQL

这是 MyBatis 官方生态里非常重要的一步。
官方文档对这个库的定义很明确:它利用 MyBatis 负责安全执行 SQL、参数绑定、结果映射,而它自己则充当另一种动态 SQL 模板引擎

也就是说,它把这件事:

在 XML 里写动态 SQL 模板

变成了:

在 Java 里用 DSL 构建动态 SQL

适合谁

  • 字段多

  • 条件多

  • 想要类型安全

  • 不想被 XML 套娃

官方还特别提到

这个库可同时用于 XML 和注解 Mapper,但总体推荐注解 Mapper;JOIN 结果映射常常仍需要 XML。

例子风格大概像这样

1
2
3
4
5
6
7
SelectStatementProvider selectStatement =  
select(user.allColumns())
.from(user)
.where(user.name, isEqualToWhenPresent(query.getName()))
.and(user.age, isEqualToWhenPresent(query.getAge()))
.build()
.render(RenderingStrategies.MYBATIS3);

然后 Mapper 方法可以接这个 SelectStatementProvider 去执行。这个风格的意义在于:

  • 条件组合靠 Java 链式表达

  • 减少 XML 条件分支

  • 更适合复杂筛选器

官方文档还有一整套 insert、update、select 的 provider 模式,且对 insert 官方甚至明确说通常不推荐 XML mapper 写法,更推荐注解式 provider。


十二、一个完整的“书写演进例子”

我给你用同一个需求串起来:
“按可选条件查询用户列表,支持 name、age、status、ids”


阶段 1:纯静态 XML,几乎不可维护

1
2
3
<select id="listByName" resultType="User">  
select * from tb_user where name = #{name}
</select>
1
2
3
<select id="listByAge" resultType="User">  
select * from tb_user where age = #{age}
</select>
1
2
3
<select id="listByNameAndAge" resultType="User">  
select * from tb_user where name = #{name} and age = #{age}
</select>

问题:组合爆炸。


阶段 2:动态 XML,主流做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<select id="listUsers" resultType="User">  
select * from tb_user
<where>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="status != null">
and status = #{status}
</if>
<if test="ids != null and ids.size() > 0">
and id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>

优点:组合爆炸被控制住。
缺点:字段再多,XML 还是会越来越长。


阶段 3:注解版,适合简单查询

1
2
3
4
5
@Mapper  
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User selectById(Long id);
}

优点:简单快。
缺点:复杂 SQL 写起来难受。


阶段 4:Spring Boot 配置化

1
2
3
4
5
6
7
8
9
10
spring:  
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456

mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true

优点:框架配置集中。
缺点:SQL 的复杂度并没有减少,只是框架装配更轻松。


阶段 5:Java DSL,适合更复杂条件

1
2
3
4
5
6
7
8
SelectStatementProvider selectStatement =  
select(user.allColumns())
.from(user)
.where(user.name, isLikeWhenPresent(query.getName()))
.and(user.age, isEqualToWhenPresent(query.getAge()))
.and(user.status, isEqualToWhenPresent(query.getStatus()))
.build()
.render(RenderingStrategies.MYBATIS3);

优点:条件表达更程序化,更适合复杂筛选。
缺点:学习成本高一点,团队需要统一风格。


十三、你写笔记时可以这么总结“进化史”

1. 原生时代

  • mybatis-config.xml 管总配置

  • Mapper.xml 管 SQL

  • SqlSessionFactory 创建 SqlSession

  • SqlSession 执行 SQL、拿 Mapper

这一套是 MyBatis 最原始也最完整的形态。

2. 动态 XML 时代

  • 为了解决条件组合爆炸

  • 引入 ifwheresetforeach 等标签

  • MyBatis 3 对动态标签做了简化,依赖 OGNL 表达式

这是 XML 方案最成熟的形态。

3. 注解 Mapper 时代

  • 简单 SQL 可直接写在接口上

  • 降低样板代码

  • 但复杂 SQL 仍常常回到 XML

MyBatis 官方始终把 XML 和注解并列作为两种主流配置方式。

4. Spring / Spring Boot 整合时代

  • 数据源、Mapper 扫描、会话模板自动配置

  • 很多配置从 mybatis-config.xml 转到 application.yml

  • 但复杂 SQL 的 Mapper.xml 并未消失

这就是你脑中“xml 变 yml”的来源。

5. Java DSL 时代

  • 官方提供 MyBatis Dynamic SQL

  • 让动态 SQL 从 XML 模板转向 Java 构建器

  • 更适合复杂筛选和更强类型约束

这是官方生态对“XML 规则也会炸”这个问题的一种进阶回应。


十四、你笔记里最值得抄的一段结论

MyBatis 的本质

MyBatis 不是 ORM 全自动魔法,它更像一个以 SQL 为中心的持久层框架。它负责安全执行 SQL、参数绑定、结果映射,并提供 XML 动态标签或 Java DSL 来生成动态 SQL。

MyBatis 的演进主线

最早主要依赖 XML 映射 SQL,随后通过 动态 XML 解决条件组合问题;再后来支持 注解 Mapper,并在 Spring/Spring Boot 生态中把大量框架配置迁移到 application.yml;面对更复杂的动态查询,官方又提供了 MyBatis Dynamic SQL,把动态 SQL 构建从 XML 进一步推进到 Java DSL。

什么时候选什么

  • 简单 CRUD:注解或简单 XML

  • 中等复杂查询:动态 XML

  • 非常复杂筛选:优先考虑拆业务,再考虑 Java DSL

  • 联表结果映射复杂:XML 依然强势

这套选择逻辑和官方能力边界是吻合的。

十五、MyBatis 和 MyBatis Plus

MyBatis

传统写法是什么样

你得先自己写 Mapper 接口:

1
2
3
4
public interface UserMapper {
User selectById(Long id);
int insert(User user);
}

再自己写对应的 XML:

1
2
3
4
5
6
7
8
9
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="User">
select * from user where id = #{id}
</select>

<insert id="insert">
insert into user(name, age) values(#{name}, #{age})
</insert>
</mapper>

一句话说,它更像是:你自己写 SQL,框架负责执行、参数绑定和结果映射。

它更适合什么场景

  • 多表 JOIN 很复杂

  • 聚合统计很多

  • 结果映射复杂

  • 想精确控制 SQL 和执行细节

这类场景里,原生 MyBatis 仍然更稳,因为你对 SQL 的掌控是最直接的。

它和 MyBatis Plus 的关系怎么理解

MyBatis 是底盘,MyBatis Plus 是建立在它之上的增强层。

所以你可以把它们理解成:

  • MyBatis:保留原生 SQL 控制力

  • MyBatis Plus:把大量重复 CRUD 和中等复杂查询封装掉

更复杂的时候,还是可能回到:

  • 自定义 SQL

  • 注解 SQL

  • XML SQL

也就是说,MyBatis Plus 不是推翻 MyBatis,而是在 MyBatis 之上减少你写无聊 SQL 的次数。

MyBatis Plus

它为什么流行

因为你前面吐槽的点,正是它要解决的点:

  • 单表 CRUD 重复代码很多

  • Service 层模板代码容易越写越机械

  • 中等复杂查询总在 XML 里反复拼条件

MyBatis Plus 的核心价值,就是把这几类高频重复劳动压缩掉。

对应的写法会简化到什么程度

很多时候你只要定义:

1
2
public interface UserMapper extends BaseMapper<User> {
}

然后就能直接用:

1
2
3
4
userMapper.selectById(1L);
userMapper.insert(user);
userMapper.deleteById(1L);
userMapper.updateById(user);

因为 BaseMapper 已经内置了很多通用 CRUD 方法,这就是它最核心的卖点之一。

它到底帮你省了什么

1. 少写 Mapper XML

很多基础操作直接不用自己写 SQL。官方 Quick Start 展示的典型方式,就是继承 BaseMapper<T>,然后直接用它提供的方法做常见 CRUD。

2. 少写 Service 层模板代码

官方文档里有 IService / ServiceImpl 这一套通用 Service 抽象,就是为了把很多重复服务层代码再压一层。

比如:

1
2
public interface UserService extends IService<User> {
}

实现类:

1
2
3
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

这样很多通用服务能力就能直接复用,不用每个业务都从零写一遍。

3. 条件查询不用手搓动态 XML

它提供了各种 Wrapper 条件构造器,能把很多 if + where + and + like 这种中等复杂度查询,从 XML 挪到 Java 链式调用里。

比如你想查:

  • age > 18

  • name like “Gil”

  • status = 1

可以写成:

1
2
3
4
5
6
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
.like(User::getName, "Gil")
.eq(User::getStatus, 1);

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

Wrapper / LambdaQueryWrapper 是 MP 的核心使用形态之一。

为什么 LambdaQueryWrapper 很香

1
User::getName

比直接写字符串 "name" 更稳。字段改名时,IDE 重构更友好,不容易出现魔法字符串翻车。

4. 分页插件开箱即用

MyBatis Plus 提供分页支持,这也是它在业务开发里非常常见的原因。

大概用法像这样:

1
2
Page<User> page = new Page<>(1, 10);
Page<User> result = userMapper.selectPage(page, wrapper);

如果纯手写 MyBatis,往往还要自己写分页 SQL,或者额外接 PageHelper 之类的东西;MP 把这块也顺手包掉了。

5. 注解配置更丰富

它支持很多常见注解,比如:

  • @TableName

  • @TableId

  • @TableField

这些注解用来做表名、主键、字段映射配置。

例子

1
2
3
4
5
6
7
8
9
10
11
@TableName("tb_user")
public class User {

@TableId
private Long id;

@TableField("user_name")
private String name;

private Integer age;
}

这能解决:

  • 类名和表名不一致

  • 属性名和字段名不一致

  • 主键策略配置

一个完整例子

实体类

1
2
3
4
5
6
7
8
@TableName("tb_user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private Integer status;
}

Mapper

1
2
3
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

查询

按 id 查:

1
User user = userMapper.selectById(1L);

查全部:

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

条件查:

1
2
3
4
5
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.gt(User::getAge, 18);

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

插入:

1
2
3
4
5
User user = new User();
user.setName("Gilbert");
user.setAge(20);
user.setStatus(1);
userMapper.insert(user);

这个味道你应该能感觉到:大量简单 SQL 已经不需要自己写 XML 了。

它最适合什么

单表 CRUD 很多的项目

管理后台、基础业务表、字典表、用户表、角色表,这类场景特别舒服。

中等复杂度条件查询

eqlikebetweeninorderByDesc 这类组合,Wrapper 很顺手。

想快速起业务

尤其你这种要做项目、做后台、做可展示工程,MP 很适合提高开发速度。

它不那么香的地方

1. 特别复杂的多表联查

复杂 JOIN、复杂结果映射这类场景,仍然更依赖原生 MyBatis 的 XML 或手写 SQL。

2. 容易让人过度依赖“自动 CRUD”

很多人一上来什么都想用 Wrapper 和通用方法糊过去,最后 SQL 可读性反而变差。这不是 MP 本身的问题,是使用姿势的问题。

3. 复杂业务 SQL 还是得回到原生能力

比如:

  • 多表 JOIN 很复杂

  • 聚合统计很多

  • 子查询多

  • 特殊索引优化要求高

这时候你还是可能写 XML 或手写 SQL。MP 没把 SQL 本身的复杂性消灭,只是把简单和中等复杂度的重复劳动压缩了。

面试里怎么说比较像样

你可以直接背这个版本:

MyBatis Plus 是什么

MyBatis Plus 是基于 MyBatis 的增强工具,在不改变 MyBatis 核心的前提下,提供了通用 CRUD、条件构造器、分页、通用 Service 等能力,用来进一步简化持久层开发。

它的优点

  • 减少单表 CRUD 样板代码

  • 通过 BaseMapper 提供通用数据库操作

  • 通过 Wrapper 构造查询条件

  • 支持注解配置、分页插件等能力

它的局限

  • 复杂多表联查和复杂统计场景下,仍然需要手写 SQL

  • 过度依赖通用封装可能影响 SQL 可读性和可控性

这个说法挺稳,不会一吹就飞。