MyBatis基础教程:Java持久层框架入门
大家好,我是小张,一个来自某双非院校的大二“老油条”。别看我才大二,其实已经在一个创业小团队里摸爬滚打快两年了——没错,就是那种老板画饼、产品经理天天改需求、运维大哥神龙见首不见尾的典型互联网草台班子。我平时除了被DDL追着跑(上周五晚上11点还在改接口),最大的爱好就是在 GitHub 上瞎折腾,顺便写写技术博客记录自己踩过的坑。今天这篇,就是关于 MyBatis 的实战入门笔记。
为什么突然要学 MyBatis?被逼的!
去年双11前一周,我们组接了个紧急需求:给一个老系统加个商品库存同步功能。原本用的是纯 JDBC 手写 SQL,代码又臭又长,每次改字段都要翻半天 DAO 层。更离谱的是,测试同学跑完压测后直接甩过来一句:“你这接口 QPS 才 80?线上崩了谁背锅?”
我当时真的想砸电脑。
组长看不下去了,拍我肩膀说:“小张啊,都 2024 年了,还手写 PreparedStatement?赶紧上 MyBatis,两天内搞定。”
我:???
我连 MyBatis 是啥都不知道,只听说过“买比特”(bushi)。
但没办法,为了保住饭碗(和实习转正机会),我连夜啃文档、扒 GitHub 上的开源项目,硬是把 MyBatis 给搞明白了。今天就把这段“血泪史”分享出来,希望你们别像我一样在 deadline 前夜哭着 debug。
MyBatis 到底是个啥?比手写 JDBC 强在哪?
简单说,MyBatis 是一个 Java 持久层框架,它帮你把数据库操作和 Java 对象映射起来,不用再手写那些又臭又长的 ResultSet 解析代码了。
以前我们这么干:
PreparedStatement ps = conn.prepareStatement("SELECT id, name, price FROM product WHERE id = ?");
ps.setInt(1, productId);
ResultSet rs = ps.executeQuery();
Product p = new Product();
if (rs.next()) {
p.setId(rs.getInt("id"));
p.setName(rs.getString("name"));
p.setPrice(rs.getBigDecimal("price"));
}
现在用 MyBatis,一行 XML + 一个接口就搞定:
<select id="selectProductById" resultType="Product">
SELECT id, name, price FROM product WHERE id = #{id}
</select>
public interface ProductMapper {
Product selectProductById(Integer id);
}
是不是清爽多了?而且 MyBatis 还支持动态 SQL、缓存、插件扩展……虽然我现在还没用到那么深,但光是自动映射这一点,就已经让我少写了 70% 的样板代码。
实战:从零搭建一个 MyBatis 项目
我用的是 Maven + MySQL + IntelliJ IDEA,项目结构参考了 GitHub 上 star 很多的 mybatis-3 官方仓库(建议新手先 clone 下来看看)。
第一步:加依赖(别漏了数据库驱动!)
<dependencies>
<!-- MyBatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 单元测试(可选但强烈推荐) -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
⚠️ 血泪教训:有一次我忘了加 MySQL 驱动,启动时报 ClassNotFoundException: com.mysql.cj.jdbc.Driver,折腾了半小时才发现是依赖没加。运维大哥看到日志后笑出声:“你这届实习生不行啊。”
第二步:写配置文件 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/shop_db?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 注册 Mapper XML 文件 -->
<mappers>
<mapper resource="mappers/ProductMapper.xml"/>
</mappers>
</configuration>
注意 & 要转义,不然 XML 解析会报错。这个坑我踩过,当时以为是时区问题,结果是 XML 特殊字符没处理。
第三步:定义实体类和 Mapper 接口
public class Product {
private Integer id;
private String name;
private BigDecimal price;
// getter / setter 省略
}
public interface ProductMapper {
Product selectProductById(Integer id);
List<Product> selectAllProducts();
int insertProduct(Product product);
}
第四步:写 Mapper XML
<!-- mappers/ProductMapper.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.ProductMapper">
<select id="selectProductById" resultType="com.example.model.Product">
SELECT id, name, price FROM product WHERE id = #{id}
</select>
<select id="selectAllProducts" resultType="Product">
SELECT * FROM product
</select>
<insert id="insertProduct" parameterType="Product">
INSERT INTO product (name, price) VALUES (#{name}, #{price})
</insert>
</mapper>
这里有个小技巧:resultType 如果写了全限定名太长,可以在 mybatis-config.xml 里配 <typeAliases> 简化。
踩坑实录:那些让我凌晨三点还在抓头发的 Bug
坑1:属性名和字段名不一致
数据库字段叫 product_name,Java 属性叫 productName,结果查出来全是 null。
解决方案:用 resultMap 显式映射:
<resultMap id="ProductMap" type="Product">
<id property="id" column="id"/>
<result property="name" column="product_name"/>
<result property="price" column="price"/>
</resultMap>
<select id="selectProductById" resultMap="ProductMap">
SELECT id, product_name, price FROM product WHERE id = #{id}
</select>
或者直接改数据库字段命名风格(但我们 DBA 不让改,说是历史遗留 😭)。
坑2:事务没生效
我写了个批量插入,结果部分成功部分失败,数据不一致。后来发现是因为没手动开启事务——MyBatis 默认 auto-commit 是 true!
正确姿势:
SqlSession session = sqlSessionFactory.openSession(false); // 关闭自动提交
try {
ProductMapper mapper = session.getMapper(ProductMapper.class);
mapper.insertProduct(p1);
mapper.insertProduct(p2);
session.commit(); // 手动提交
} catch (Exception e) {
session.rollback(); // 出错回滚
} finally {
session.close();
}
这事儿让我深刻理解了 ACID 的重要性,也明白了为什么面试官总问“你怎么保证数据一致性”。
性能与设计:别只顾着 CRUD
虽然我只是个实习生,但组长经常强调:“代码能跑 ≠ 代码能上线。” 所以我在设计 Mapper 时也考虑了几点:
| 考虑点 | 实践建议 |
|---|---|
| SQL 注入 | 永远用 #{},别用 ${}(除非你真知道自己在干嘛) |
| N+1 查询 | 用 <collection> 或 <association> 做关联查询,避免循环查库 |
| 缓存 | MyBatis 有一级缓存(SqlSession 级别)和二级缓存(Mapper 级别),但生产环境慎用,容易脏读 |
| 分页 | 别 SELECT * 再内存分页!用 LIMIT 或 PageHelper 插件 |
对了,说到算法——虽然 MyBatis 本身不涉及复杂算法,但良好的 SQL 设计本身就是一种算法思维。比如索引怎么建、JOIN 顺序怎么优化、如何避免全表扫描……这些都直接影响接口性能。我们上次压测从 QPS 80 提升到 1200,主要靠的就是 SQL 优化 + 连接池调参,而不是换框架。
最后:为什么我觉得 MyBatis 适合我们这种小团队?
- 学习曲线平缓:不像 Hibernate 那么“全自动”,MyBatis 让你对 SQL 有完全控制权,适合我们这种 DBA 和开发界限模糊的小公司。
- 调试友好:SQL 写在 XML 里,出问题直接看日志就行,不用猜 ORM 到底生成了啥语句。
- GitHub 生态丰富:MyBatis-Plus、PageHelper、MyBatis-Spring 等扩展一搜一大把,拿来即用。
当然,如果你在大厂做高并发系统,可能要考虑更高级的方案(比如 ShardingSphere)。但对我们这种日活几千的小项目,MyBatis + 手写 SQL + 良好索引设计,完全够用。
结语:从“手写 JDBC 狗”到“半吊子 ORM 用户”
现在回头看,那段被 MyBatis 折磨的日子其实挺值的。不仅让我摆脱了重复劳动,还逼我深入理解了 JDBC 底层原理(毕竟 MyBatis 就是封装 JDBC 嘛)。最近我还试着给公司的 GitHub 私有仓库提了个 PR,把几个老模块迁移到了 MyBatis,居然被 merge 了!组长请我喝了杯瑞幸(虽然是美式,但好歹是咖啡)。
如果你也是和我一样的“野生程序员”,别怕框架。所有看似高深的技术,拆开看都是 CRUD + 一点设计思想。MyBatis 只是个工具,真正重要的是你对数据、对业务、对性能的理解。
最后附上我的练习项目地址(脱敏版):github.com/zhang-coder/mybatis-demo(Star 不强求,但 Issues 欢迎提!)
好了,今天就水到这里。下周产品经理又要加“智能推荐”功能了,据说要用机器学习……我先去补《算法导论》了,告辞!

评论 0