MyBatis踩坑记:从零搭建Java持久层的那些事儿
上周五晚上十点,我正戴着耳机听着《晴天》改一个线上Bug,突然收到产品组发来的紧急需求——“这个接口性能太差,明天上线前必须优化完”。我盯着IDE里那堆手写JDBC的代码,心里默默问候了产品经理全家(开玩笑的,其实他人挺好的)。那一刻,我下定决心:必须上MyBatis!
作为杭州某厂(不点名,反正不是网易就是阿里系)的一名普通一本CS大四应届生,我已经拿到offer等入职了。最近在实习期间被安排接手一个老系统,代码里充斥着Connection、PreparedStatement、ResultSet,看得我头皮发麻。更离谱的是,SQL语句直接拼在Java字符串里,连个参数校验都没有。这哪是代码,这是祖传文物啊!
为什么选MyBatis?而不是Hibernate或者Go?
说实话,团队里也有人提过用Hibernate,但被我们后端组长一句话怼回去了:“我们又不是要写论文,搞什么全自动ORM?我们需要的是可控性。” 说得对!在电商这种高并发场景下,SQL的每一毫秒都得精打细算。MyBatis正好介于裸写JDBC和全自动ORM之间——你写SQL,它帮你处理映射,完美。
至于Go?虽然我们公司新项目确实在用Go(毕竟杭州这边Go生态越来越火),但这个老系统是Java栈,重写成本太高。而且我入职后大概率还是写Java,所以先把MyBatis玩明白才是正道。
环境搭建:别被配置劝退
我第一次配MyBatis的时候,光是mybatis-config.xml就折腾了两个小时。后来发现,Spring Boot + MyBatis 才是真香组合。直接加个依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
然后在application.yml里配个数据源:
spring:
datasource:
url: jdbc:mysql://localhost:3306/product_db?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.entity
搞定!比手写JDBC连接池省了不知道多少行代码。
写个Mapper:告别SQL拼接地狱
以前那种代码:
String sql = "SELECT * FROM products WHERE name LIKE '%" + productName + "%'";
不仅有SQL注入风险,还丑得要死。现在用MyBatis,XML里这么写:
<!-- ProductMapper.xml -->
<select id="searchProducts" resultType="Product">
SELECT id, name, price, stock
FROM products
WHERE name LIKE CONCAT('%', #{productName}, '%')
</select>
Java接口就一行:
public interface ProductMapper {
List<Product> searchProducts(@Param("productName") String productName);
}
注意:这里用#{}而不是${}!前者会自动转义,后者就是纯字符串替换,容易被注入。我上周差点因为这个被安全扫描扫出来,吓出一身冷汗。
动态SQL:if、foreach真香
产品需求总是变来变去。比如这个查询接口,一开始只要按名称搜,后来又要加价格区间、库存状态、分类ID……如果用传统方式,得写一堆if判断拼SQL,维护起来想哭。
MyBatis的动态SQL救我狗命:
<select id="complexSearch" resultType="Product">
SELECT id, name, price, stock, category_id
FROM products
WHERE 1=1
<if test="productName != null and productName != ''">
AND name LIKE CONCAT('%', #{productName}, '%')
</if>
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND price <= #{maxPrice}
</if>
<if test="categoryIds != null and categoryIds.size > 0">
AND category_id IN
<foreach collection="categoryIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
特别是<foreach>,处理IN查询太方便了。以前手写得循环拼字符串,现在一行搞定,还自动防注入。
性能优化:别让MyBatis背锅
有人说“MyBatis慢”,其实锅不在框架,而在你怎么用。我在压测时发现一个接口QPS只有50,查了半天,原来是没开二级缓存,而且每次查询都拉全表。
解决方案:
- 合理使用缓存:对不常变的数据(比如商品分类)开启二级缓存
- 分页查询:千万别
SELECT *,用LIMIT控制返回量 - 索引优化:确保WHERE条件字段有索引
MyBatis分页推荐用PageHelper插件,几行代码搞定:
PageHelper.startPage(1, 10); // 第1页,每页10条
List<Product> products = productMapper.searchProducts("手机");
与Go项目的对比思考
有意思的是,我们团队另一个小组在用Go写新服务,他们用的是GORM。我发现两者思路很像:都是让开发者专注SQL本身,而不是对象关系映射的魔法。只不过MyBatis更“显式”——SQL写在XML或注解里,一目了然;而GORM通过链式调用生成SQL,更“隐式”。
在调试时,MyBatis的日志输出特别清晰,能看到完整SQL和参数,排查问题快很多。这点比某些“全自动”ORM强太多。
最后一点真心话
作为一个即将正式入职的萌新,我深刻体会到:工具只是工具,核心还是对业务和数据的理解。MyBatis再好,如果你写的SQL本身就有N+1查询问题,照样慢成狗。
现在那个老系统已经重构了一半,接口响应时间从800ms降到80ms,产品组终于不再半夜@我了。虽然过程中踩了无数坑(比如XML命名空间写错、驼峰转换没开、事务没配……),但每解决一个,都感觉自己离“合格程序员”又近了一步。
对了,如果你也在杭州,也在用MyBatis,欢迎交流!说不定以后还能在阿里园区或网易门口偶遇,一起吐槽产品需求呢 😄
附:MyBatis vs 原生JDBC 关键对比
| 维度 | 原生JDBC | MyBatis |
|---|---|---|
| 代码量 | 多(连接、关闭、异常处理) | 少(自动管理) |
| SQL注入风险 | 高(需手动处理) | 低(#{}自动转义) |
| 可维护性 | 差(SQL散落在Java中) | 好(集中管理) |
| 性能控制 | 完全手动 | 精细可控 |
| 学习曲线 | 低(但重复劳动多) | 中(需理解映射机制) |
总之,别再手写JDBC了,除非你想体验“修仙”般的加班生活。

评论 0