Mybatis的变更
一、MyBatis 到底解决什么问题
MyBatis 是一个持久层框架,支持自定义 SQL、存储过程和高级映射,目标是减少几乎所有 JDBC 模板代码,并把参数设置、结果取回这些脏活抽掉。 官方介绍里写得很直白:它可以用 XML 或注解 来配置,把基本类型、Map、POJO 映射到数据库记录。
你可以这样理解
JDBC 时代你得自己写:
代码块收起展开
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
官方配置文档给出的高层结构包括:properties、settings、typeAliases、typeHandlers、plugins、environments、databaseIdProvider、mappers。也就是说它是 MyBatis 的总控配置。
2. 每个 Mapper 对应一个 Mapper.xml
官方文档强调,Mapper XML Files 的真正威力在 Mapped Statements,也就是 SQL 映射定义本身。
三、原生 XML 写法长什么样
1. 全局配置例子
代码块收起展开
<?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 接口
代码块收起展开
public interface UserMapper {
User selectById(Long id);
}3. Mapper XML
代码块收起展开
<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:条件成立才拼接
代码块收起展开
<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:动态更新时自动处理逗号
这样就不用自己费劲处理最后一个逗号。官方也把 set 列为核心动态元素之一。
4. foreach:批量 IN、批量插入、批量删除
代码块收起展开
<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:多选一逻辑
代码块收起展开
<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:更底层、更灵活的裁剪器
代码块收起展开
<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>where 和 set 本质上都可以看成 trim 的便利包装。
七、你担心的“十几个字段怎么办”到底怎么落地
情况 A:字段 3 到 6 个
动态 XML 非常合适,写起来快,团队也容易看懂。
情况 B:字段 10 个以上,条件还是可选
还能写,但建议开始做分层:
1. 参数对象化
不要十几个参数散落方法签名里。
代码块收起展开
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> 来复用片段。
3. 把“筛选条件”和“联表结果映射”分开想
很多人把复杂度全怪到 XML 头上,其实真正复杂的是业务查询本身。
八、第三阶段:注解式 Mapper 出现了
官方介绍里就明确说,MyBatis 可以使用 XML 或注解 进行配置。
典型例子
代码块收起展开
@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。
原生用法像这样:
代码块收起展开
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);
session.close();后来和 Spring 集成后,很多样板都被框架接管了。
你只看到:
代码块收起展开
@Autowired
private UserMapper userMapper;背后其实是 Spring 帮你管理了 Mapper 代理、SqlSessionFactory、SqlSessionTemplate 等对象。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
代码块收起展开
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。
例子风格大概像这样
代码块收起展开
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,几乎不可维护
代码块收起展开
<select id="listByName" resultType="User">
select * from tb_user where name = #{name}
</select> 代码块收起展开
<select id="listByAge" resultType="User">
select * from tb_user where age = #{age}
</select> 代码块收起展开
<select id="listByNameAndAge" resultType="User">
select * from tb_user where name = #{name} and age = #{age}
</select>问题:组合爆炸。
阶段 2:动态 XML,主流做法
代码块收起展开
<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:注解版,适合简单查询
代码块收起展开
@Mapper
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User selectById(Long id);
}优点:简单快。
缺点:复杂 SQL 写起来难受。
阶段 4:Spring Boot 配置化
代码块收起展开
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,适合更复杂条件
代码块收起展开
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 时代
-
为了解决条件组合爆炸
-
引入
if、where、set、foreach等标签 -
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 接口:
代码块收起展开
public interface UserMapper {
User selectById(Long id);
int insert(User user);
}再自己写对应的 XML:
代码块收起展开
<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 的核心价值,就是把这几类高频重复劳动压缩掉。
对应的写法会简化到什么程度
很多时候你只要定义:
代码块收起展开
public interface UserMapper extends BaseMapper<User> {
}然后就能直接用:
代码块收起展开
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 抽象,就是为了把很多重复服务层代码再压一层。
比如:
代码块收起展开
public interface UserService extends IService<User> {
}实现类:
代码块收起展开
@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
可以写成:
代码块收起展开
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 很香
代码块收起展开
User::getName比直接写字符串 "name" 更稳。字段改名时,IDE 重构更友好,不容易出现魔法字符串翻车。
4. 分页插件开箱即用
MyBatis Plus 提供分页支持,这也是它在业务开发里非常常见的原因。
大概用法像这样:
代码块收起展开
Page<User> page = new Page<>(1, 10);
Page<User> result = userMapper.selectPage(page, wrapper);如果纯手写 MyBatis,往往还要自己写分页 SQL,或者额外接 PageHelper 之类的东西;MP 把这块也顺手包掉了。
5. 注解配置更丰富
它支持很多常见注解,比如:
-
@TableName -
@TableId -
@TableField
这些注解用来做表名、主键、字段映射配置。
例子
代码块收起展开
@TableName("tb_user")
public class User {
@TableId
private Long id;
@TableField("user_name")
private String name;
private Integer age;
}这能解决:
-
类名和表名不一致
-
属性名和字段名不一致
-
主键策略配置
一个完整例子
实体类
代码块收起展开
@TableName("tb_user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private Integer status;
}Mapper
代码块收起展开
@Mapper
public interface UserMapper extends BaseMapper<User> {
}按 id 查
代码块收起展开
User user = userMapper.selectById(1L);查全部
代码块收起展开
List<User> list = userMapper.selectList(null);条件查
代码块收起展开
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.gt(User::getAge, 18);
List<User> list = userMapper.selectList(wrapper);插入
代码块收起展开
User user = new User();
user.setName("Gilbert");
user.setAge(20);
user.setStatus(1);
userMapper.insert(user);这个味道你应该能感觉到:大量简单 SQL 已经不需要自己写 XML 了。
它最适合什么
单表 CRUD 很多的项目
管理后台、基础业务表、字典表、用户表、角色表,这类场景特别舒服。
中等复杂度条件查询
eq、like、between、in、orderByDesc 这类组合,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 可读性和可控性