MyBatis基础教程:Java持久层框架入门
上周五晚上十点半,我正躺在沙发上用MacBook Pro敲代码,窗外下着小雨,咖啡已经凉了。这时候产品经理突然在群里@我:“双11运营活动明天要上线,用户积分查询接口得加个条件过滤,能搞定吗?”
我嘴角一抽——这需求上周才改过三轮,现在又要动数据库逻辑。但没办法,谁叫我们是远程办公的“福报打工人”呢?不过好在我早就把项目从JDBC手写SQL迁到了MyBatis,不然今晚又得通宵。
其实说起来有点惭愧:作为一个Cursor重度用户,我已经离不开AI写代码了。每天打开IDE,第一件事就是Cmd+K唤出Cursor,让它帮我生成Mapper XML或者调试SQL。以前自己手写DAO层的时候,光是ResultSet取字段就能写到怀疑人生。现在?一句提示词的事儿。
为什么选MyBatis而不是JPA?
很多团队喜欢用Spring Data JPA,尤其是那些从Python转过来的同事(别问,问就是综合技术栈)。JPA确实香,注解一打,CRUD自动生成,看起来非常“现代化”。但问题来了:
- 复杂查询写起来像在拼乐高,性能调优全靠玄学
- SQL对DBA不透明,线上慢查询根本没法快速定位
- 动态条件拼接?那叫HQL地狱
而MyBatis不一样。它不替你写SQL,而是让你掌控SQL。作为喜欢研究底层原理的老码农,我宁愿多写几行XML,也不想被ORM框架“过度封装”。
有一次线上OOM,查了半天发现是JPA的N+1查询惹的祸。换成MyBatis后,SQL一目了然,连运维大哥都说:“你们这SQL格式化得比我的shell脚本还整齐。”
五分钟上手MyBatis核心三件套
MyBatis的核心就三样东西:SqlSessionFactory、Mapper接口、XML映射文件。听起来很抽象?我给你拆解成人类语言:
- SqlSessionFactory:数据库连接池的“包工头”,负责开工(创建SqlSession)
- Mapper接口:你写的Java接口,比如
UserMapper - XML文件:SQL语句的“老家”,所有增删改查都藏在这里
先看配置(mybatis-config.xml):
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ops_db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 告诉MyBatis去哪里找你的Mapper XML -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
接着写Mapper接口:
public interface UserMapper {
List<User> selectUsersByStatus(@Param("status") String status);
}
最后是XML(mappers/UserMapper.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">
<select id="selectUsersByStatus" resultType="com.example.model.User">
SELECT user_id, username, points
FROM users
WHERE status = #{status}
</select>
</mapper>
看到没?SQL明明白白写在XML里,运维查日志、DBA优化索引都方便。这才是可维护性!
动态SQL:让运营需求不再可怕
回到开头那个双11需求。产品经理要加“按积分范围+状态+注册时间”筛选,典型的动态查询。MyBatis的<where>和<if>标签简直就是救星:
<select id="searchUsers" resultType="User">
SELECT user_id, username, points, create_time
FROM users
<where>
<if test="minPoints != null">
AND points >= #{minPoints}
</if>
<if test="maxPoints != null">
AND points <= #{maxPoints}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
</where>
ORDER BY create_time DESC
</select>
注意:<where>会自动处理AND/OR的前缀,避免SQL语法错误。以前用JDBC手拼字符串,漏个空格就得debug半小时。现在?Cursor直接帮我生成模板,我只管填业务逻辑。
性能陷阱:别让MyBatis变成“慢Batis”
虽然MyBatis灵活,但用不好照样翻车。去年双11我们就踩过坑:
事故现场:运营后台导出百万级用户数据,直接把DB连接池打满。
根因:Mapper里写了SELECT *,而且没分页!
解决方案:
- 永远不要
SELECT *:明确指定字段,减少网络IO - 大查询必须分页:用
RowBounds或物理分页插件 - 开启二级缓存要谨慎:我们关掉了,因为数据实时性要求高
附上我们生产环境的调优参数对比:
| 配置项 | 开发环境 | 生产环境 | 说明 |
|---|---|---|---|
defaultFetchSize |
100 | 1000 | 控制ResultSet一次拉取的行数 |
mapUnderscoreToCamelCase |
true | true | 数据库下划线字段自动转驼峰 |
cacheEnabled |
true | false | 生产关闭二级缓存 |
logImpl |
STDOUT_LOGGING | SLF4J | 日志对接公司ELK |
Python党别走!MyBatis和你的世界也能联动
我知道读者里有不少Python选手(毕竟现在全栈工程师都得会点Python做数据分析)。虽然MyBatis是Java的,但思想可以复用:
- SQL与逻辑分离:Python里可以用SQLAlchemy Core,把SQL写在单独文件
- 动态查询构建:用
sqlglot或原生f-string(小心SQL注入!) - 运维脚本集成:我们用Python脚本定期分析MyBatis日志,找出慢SQL
举个例子,我写了个Python小工具,扫描所有Mapper XML,统计高频表和字段,反哺给DBA做索引优化。这叫综合效能提升!
最后一点碎碎念
说实话,学MyBatis真不是为了炫技。是因为被现实毒打过——手写JDBC的时代,一个字段类型改错,测试能提20个bug。现在用MyBatis + Cursor,开发效率飞起,连产品经理都说:“你这需求响应速度,比我点外卖还快。”
当然,MyBatis只是工具。真正重要的是理解数据流向:从HTTP请求 → Service → Mapper → DB → 返回结果。每一层都要有监控、有兜底、有日志。毕竟线上事故不分语言,Java崩了和Python崩了,老板骂人是一样的凶。
如果你也在被运营需求折磨,不妨试试MyBatis。至少下次凌晨三点改SQL的时候,你能优雅地喝口咖啡,而不是砸键盘。
(完)
作者:某大厂远程办公Java工程师,Cursor骨灰级用户,坚信“能用AI解决的问题都不是问题”。最近在研究MyBatis源码中的Executor设计,欢迎交流~

评论 0