MyBatis基础教程:从“手写JDBC”到“优雅持久化”的蜕变之路
大家好,我是阿杰,一名全栈工程师,在后端服务和数据库交互方面有比较多的经验。今天想分享的是我在一个中型电商平台重构项目中,是如何一步步从传统的 JDBC 手动操作切换到使用 MyBatis 的过程。
这个过程中,我经历了数据层代码臃肿、SQL 与业务逻辑混杂、难以维护等真实挑战。希望通过这篇实战经验总结,能帮你少走弯路,快速掌握 MyBatis 的基础用法,并在日常开发中灵活运用。
初识MyBatis —— 为什么是它?

事情得从一年前说起。当时我们公司正在对一个老电商系统做架构升级,原来的数据库交互方式全是通过裸 JDBC 写的 DAO 层,每个方法都充斥着 Connection、PreparedStatement、ResultSet 这些底层对象。
说实话,那时候每天打开 DAO 类就像面对一团乱麻,特别是遇到复杂查询时,一堆 try-catch 嵌套让人根本不想维护。更糟糕的是,每次改动 SQL 都需要重新编译 Java 文件,效率低下不说,还容易出错。
我们团队最初也考虑过 Hibernate 和 JPA,但最终选择了 MyBatis,主要有几个理由:
- 灵活性高:不强制 ORM 映射方式,适合我们这种已存在大量历史 SQL 的场景。
- 性能可调优:可以精确控制 SQL 语句,避免了框架自动生成可能带来的性能问题。
- 学习曲线平滑:对于熟悉 SQL 的开发者来说,上手快,文档丰富,社区活跃。
于是,我们在一次迭代中决定将用户中心模块作为试点,开始尝试使用 MyBatis 来重构这部分的 DAO 逻辑。
实战开整!第一个MyBatis接口实现

我们的第一个目标是把用户登录信息的获取接口从 JDBC 改造成 MyBatis 实现。原始的 JDBC 方法大概是这样的(简化版):
public User getUserById(int id) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?");
ps.setInt(1, id);
rs = ps.executeQuery();
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
// ...其他字段设置
return user;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeResources(conn, ps, rs);
}
return null;
}
这看起来还算清晰,但在实际工程中,往往还会夹杂日志、异常处理、事务控制、参数校验等等,导致代码膨胀严重,可读性差。
接下来,我们引入MyBatis后的步骤如下:
- 添加MyBatis依赖(以Maven为例):
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
- 配置MyBatis环境
创建一个 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"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 编写Mapper接口
public interface UserMapper {
User selectUserById(int id);
}
- 编写XML映射文件
位置为resources/mapper/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="selectUserById" resultType="com.example.model.User">
SELECT *
FROM user
WHERE id = #{id}
</select>
</mapper>
- 主函数测试调用
public class Main {
public static void main(String[] args) throws IOException {
String config = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(config);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user.getUsername());
}
}
}
短短几步之后,DAO 层变得简洁又直观,而且所有的 SQL 和 Java 对象绑定都在 XML 中统一管理,后续修改也方便多了。
踩坑实录:那些让我半夜抓头发的问题
当然,使用过程中也不可能一帆风顺,有几个常见的问题值得记录一下。
1. Mapper找不到?类路径配置没搞好!
刚开始的时候,我总是遇到 Invalid bound statement not found 的错误。排查了半天才发现是因为 MyBatis 没有扫描到 XML 文件或者接口路径不对。
解决方式:
- XML 文件要放在 resources 目录下,并且配置中的
<mapper>路径要对应。 - Spring Boot 中可通过注解自动注册 Mapper,如:
@Mapper
public interface UserMapper { ... }
也可以在启动类加上:
@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application {}
2. 自定义别名不起作用?
有时候我们不想每次都写长包路径,可以在 MyBatis 中定义别名:
<typeAliases>
<typeAlias alias="User" type="com.example.model.User"/>
</typeAliases>
然后就可以在映射文件中直接写:
<select id="selectUserById" resultType="User">...</select>
注意:如果用了 Maven,要确保资源过滤没问题,否则 xml 文件里引用不到别名。
3. 动态SQL搞不定怎么办?
比如搜索用户时,经常会有多个条件动态组合的情况。这时候就要用到 <if>、<choose>、<trim> 标签。
举个例子:
<select id="searchUsers" resultType="User">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
上面这段 SQL 在 username 或 status 为空时会自动忽略相应的条件,非常适合构建灵活查询接口。
性能优化和工程实践
作为一个注重性能的后端工程师,我也在实践中做了不少优化。
数据库设计建议
- 表结构尽量范式合理,减少冗余字段。
- 关键字段加索引,特别是常用于查询的字段。
- 尽量避免 N+1 查询,可以通过
<collection>或 join 方式一次性加载关联数据。
例如,我们要查用户及其订单列表:
<resultMap id="userWithOrdersResultMap" type="com.example.model.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="amount" column="amount"/>
</collection>
</resultMap>
<select id="getUserWithOrders" resultMap="userWithOrdersResultMap">
SELECT u.id, u.username, o.id as order_id, o.amount
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{userId}
</select>
这样就能在一个 SQL 中拿到主表和子表的数据,提升了性能。
接口设计层面的思考
- 分页一定要支持,可以用 PageHelper 插件实现。
- 返回结果类型要抽象,不建议直接暴露数据库实体类,应该使用 DTO 包装数据。
- 缓存机制结合使用,MyBatis 本身支持二级缓存,但推荐还是用 Redis 更稳定可控。
效果评估与收益回顾
项目上线三个月后,我们对比了使用 MyBatis 前后的几个关键指标:
| 指标 | JDBC时代 | MyBatis时代 |
|---|---|---|
| DAO类代码行数 | 平均300行/类 | 平均80行/类 |
| SQL变更效率 | 修改Java代码 + 重新部署 | 只需修改XML |
| 单元测试覆盖率 | 不足50% | 提升至75% |
| 新人学习成本 | 高(需理解JDBC细节) | 低(只需理解MyBatis语法) |
不仅开发效率显著提升,系统的可维护性和可扩展性也大大提高。后来在加入新模块时,直接沿用这套 MyBatis 结构,节省了大量的重复开发时间。
经验总结:给新手的一些建议

如果你刚接触 MyBatis,这里是我的几点小建议:
- 先学会 XML 编写,这是最基础也是最有灵活性的方式。
- 不要一开始就追求自动化 ORM,像 MyBatis Plus 可以后面慢慢引入。
- 多关注事务控制和连接池配置,尤其是生产环境中。
- 用好日志插件(如log4j、slf4j),方便跟踪 SQL 执行情况。
- 结合Spring Boot使用更加顺畅,现在几乎已经是标配了。
还有一个小感悟:其实工具和技术都是辅助手段,真正的核心永远是清晰的设计思维和良好的代码组织能力。MyBatis 只是个好用的锤子,但别忘了你是在建造一座房子,而不是单纯敲钉子。
后记
这篇文章算是我对 MyBatis 学习和实践经验的一个阶段性总结。如果你也在经历从手动 SQL 到使用 MyBatis 的转变,希望我的经历能给你带来一些启发。
最后送大家一句话:“好的代码不是写出来的,而是不断演进打磨出来的。”
技术的路上没有捷径,只有不断地动手、试错、复盘。愿你在编码的世界里找到属于自己的节奏与快乐 🚀

评论 0