裸辞半年后,我用 MyBatis 重新敲开了 Spring Boot 的门
去年十月,我从某一线大厂裸辞了。
不是因为被裁,也不是因为卷不动——纯粹是觉得“再这样下去,代码写得再好,也救不了自己快熄灭的热情”。Gap 半年,白天看书、健身、陪猫,晚上则常常窝在电脑前,一行行敲着代码。说来奇怪,越是不用打卡的日子,我写代码反而越高效。可能是因为没有产品经理半夜甩过来的“紧急需求”,也没有运维同学在群里@全体成员说“线上又挂了”。
但 Gap 不是躺平。最近开始重新投简历,面试官一开口就是:“Spring Boot + MyBatis 用得熟吗?讲讲一级缓存和二级缓存的区别?”
我:……(内心 OS:这不就是我在家撸的那套玩意儿吗!)
于是决定写点东西,既是复习,也是给同样在找工作的兄弟们一点参考。今天就聊聊 MyBatis ——这个在 Java 持久层里低调但稳如老狗的框架。
为什么不是 JPA?为什么是 MyBatis?
在大厂那会儿,我们团队用的是 MyBatis,而不是 Spring Data JPA。原因很简单:可控性。
JPA 写起来确实爽,findByUserIdAndStatus() 这种方法名直接生成 SQL,省事。但一旦业务复杂起来——比如要写多表关联、动态条件、分页加排序、甚至手写窗口函数——JPA 就容易“翻车”。而 MyBatis 把 SQL 明确写在 XML 或注解里,每一行 SQL 都是你亲手写的,出了问题你背锅,但也意味着你能精准调优。
尤其是在双11这种大促期间,DBA 看到你传过去的慢查询语句,能直接骂到你工位上。这时候,一个清晰、可读、可维护的 SQL 文件,比什么都重要。
Spring Boot + MyBatis:三分钟跑通不是梦
先别想什么缓存、插件、分页,先让项目跑起来。这是我 Gap 期间在家搭的第一个 Demo,用的是 Spring Boot 3.x + MyBatis 3.5+。
1. 引入依赖(Maven)
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
别忘了配数据源!我一开始忘了加
spring.datasource.url,启动时报Failed to configure a DataSource,差点以为 Spring Boot 嫌弃我失业太久不配连数据库 😅
2. 实体类 + Mapper 接口
public class User {
private Long id;
private String name;
private String email;
// getter/setter 略
}
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
}
注意 @Options(useGeneratedKeys = true) —— 这是获取自增主键的关键。很多新人面试被问“插入后怎么拿 ID”,答不上来就凉了。其实就这一行配置。
XML 还是注解?我的选择是……
我知道现在很多人喜欢用注解写 SQL,觉得“少一个 XML 文件,清爽”。但我坚持用 XML,原因有三:
- SQL 复杂时,注解里写多行字符串太丑(尤其带动态
WHERE) - XML 支持
<include>、<trim>等标签,复用性强 - 团队协作时,SQL 和 Java 逻辑分离,DBA 更容易 review
举个真实场景:产品要求“用户列表支持按姓名模糊查、按邮箱精确查、按状态筛选,三个条件可选”。用注解写?试试看:
@Select("<script>" +
"SELECT * FROM users " +
"<where>" +
"<if test='name != null'>AND name LIKE CONCAT('%', #{name}, '%')</if>" +
"<if test='email != null'>AND email = #{email}</if>" +
"<if test='status != null'>AND status = #{status}</if>" +
"</where>" +
"</script>")
List<User> queryUsers(@Param("name") String name,
@Param("email") String email,
@Param("status") Integer status);
是不是看着就想砸键盘?换成 XML:
<select id="queryUsers" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">AND name LIKE CONCAT('%', #{name}, '%')</if>
<if test="email != null">AND email = #{email}</if>
<if test="status != null">AND status = #{status}</if>
</where>
</select>
干净、清晰、可维护。这就是我坚持 XML 的理由。
缓存?别被面试题带偏了
说到 MyBatis,面试必问:“一级缓存和二级缓存有什么区别?”
- 一级缓存:SqlSession 级别,默认开启。同一个 SqlSession 中,重复查询会走缓存。
- 二级缓存:Mapper 级别,需手动开启,跨 SqlSession 共享。
但我要泼冷水:生产环境慎用二级缓存!
为啥?因为 MyBatis 的二级缓存是基于 namespace 的,一旦多个服务实例共享数据库(比如微服务架构),缓存脏读风险极高。我们大厂曾经有个事故:用户改了资料,但其他节点读的还是旧缓存,客服电话被打爆。
所以我的建议是:
- 一级缓存:默认就行,别动
- 二级缓存:除非你明确知道自己在做什么,否则关掉
- 真要缓存?上 Redis,配合 Cache-Aside 模式,更安全
可读性 > 聪明:我的代码人生信条
Gap 期间重看以前写的代码,发现有些地方为了“炫技”用了复杂的嵌套 resultMap 或自定义 TypeHandler,结果三个月后自己都看不懂。现在我写 MyBatis,只遵循一条原则:让接手的人一眼看懂你在干啥。
比如关联查询,宁愿多查一次,也不写三层嵌套的 <collection>。性能差?加个 Redis 补回来。但可读性丢了,团队协作成本就上去了。
这也是我在面试时特别看重的一点:代码是给人看的,其次才是给机器跑的。很多候选人能把 MyBatis 源码讲得头头是道,但写出的 XML 像天书——这种人,我不敢要。
附:MyBatis 常见面试题速查表
| 问题 | 关键点 |
|---|---|
| 一级 vs 二级缓存 | 作用域、是否默认开启、线程安全 |
#{} 和 ${} 区别 |
#{} 预编译防注入,${} 直接拼接(慎用!) |
| 如何获取自增主键? | @Options(useGeneratedKeys=true, keyProperty="id") |
| 动态 SQL 标签有哪些? | <if>, <choose>, <foreach>, <set>, <trim> |
| 如何防止 SQL 注入? | 永远用 #{},避免 ${};输入校验 |
记住:
${}是 SQL 注入的入口!除非你 100% 信任输入(比如枚举值),否则别碰。
最后:Gap 不是空白,是蓄力
这半年,我没荒废。每天深夜写代码,不是为了卷,而是找回那种“一行代码改变世界”的感觉。MyBatis 虽然老,但它稳定、透明、可控——就像一个靠谱的老同事。
下周就要去新公司入职了,技术栈依然是 Spring Boot + MyBatis。这次,我想把代码写得更 clean 一点,把文档写得更清楚一点,把坑填得更早一点。
毕竟,代码人生,不在快,而在稳。
祝所有正在找工作的朋友,早日拿到 offer。
也祝我自己,在下一段旅程里,少些加班,多些热爱。

评论 0