MyBatis 初探:一个外包仔的持久层救赎之路

王华
2026-01-15 15:52
阅读 796

上周五晚上十一点,我正窝在沙发上一边刷《鱿鱼游戏2》预告,一边等客户验收。突然微信弹出一条消息:“后端接口怎么又500了?明天上线前必须修好!”——典型的甲方语录,既没日志也没上下文,仿佛我们程序员是通灵的。

我叹了口气,打开远程桌面,一查日志,果然是数据库操作炸了。原因?同事手写的 JDBC 连接池没关,测试环境跑着跑着直接把 MySQL 干到拒绝连接。那一刻,我真想穿越回唐朝,用竹简写代码,至少不用 deal with connection leaks。

但现实是,咱得吃饭。作为靠接外包搞副业的斜杠程序员,我白天在一家小 SaaS 公司写 Go 微服务(对,就是那个号称“云原生首选”的 Go),晚上还得帮朋友公司维护 Java 后台。而 Java 项目里,最让我头疼的就是原始的 JDBC 操作——冗长、易错、可读性差得像产品经理画的原型图。

于是,我决定给这个老项目引入 MyBatis。不是为了炫技,纯粹是想早点下班,多陪陪我家猫主子。


为什么是 MyBatis?而不是 JPA 或裸 JDBC?

先说说我踩过的坑。早年刚入行时,被领导逼着用过 Hibernate(JPA 的实现之一),结果写个复杂查询愣是调了三天,最后还是改回原生 SQL。那玩意儿“全自动”听起来很美,但一旦涉及多表关联、动态条件、分页优化,就变成“自动翻车”。

而裸 JDBC?别提了。打开项目目录,满屏都是 try-catch-finally + ResultSet 遍历,连我养的猫看了都摇头:“这代码,不如我打翻的猫粮整齐。”

MyBatis 的优势就在这儿:它不替你做决定,只帮你省力气。SQL 你写,映射你配,框架只负责把参数塞进去、把结果映出来。灵活性和控制力拉满,特别适合我们这种既要快速交付、又要保证代码可读性的外包狗。

而且,MyBatis 的 XML 映射文件天然支持复用和模块化,配合良好的命名规范,后期维护起来不至于想删库跑路。


快速上手:三步搞定 CRUD

别被“框架”俩字吓到。MyBatis 的核心就仨东西:

  1. Mapper 接口(定义方法)
  2. XML 映射文件(写 SQL)
  3. SqlSessionFactory(管理会话)

咱们用一个超简化的用户管理模块来演示。

1. 引入依赖(Maven)

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

注:实际项目中建议用 Spring Boot + MyBatis Starter,但为了讲清楚底层,这里先裸配。

2. 配置 SqlSessionFactory

创建 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/demo_db?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mappers/UserMapper.xml"/>
  </mappers>
</configuration>

注意:POOLED 数据源是 MyBatis 自带的简易连接池,生产环境建议换成 HikariCP 或 Druid。

3. 写 Mapper 接口 + XML

User.java

public class User {
    private Long id;
    private String name;
    private String email;
    // getter/setter 省略
}

UserMapper.java

public interface UserMapper {
    User selectById(Long id);
    List<User> selectAll();
    void insert(User user);
    void update(User user);
    void delete(Long id);
}

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="selectById" resultType="com.example.model.User">
    SELECT * FROM users WHERE id = #{id}
  </select>

  <select id="selectAll" resultType="com.example.model.User">
    SELECT * FROM users
  </select>

  <insert id="insert" parameterType="com.example.model.User">
    INSERT INTO users (name, email) VALUES (#{name}, #{email})
  </insert>

  <update id="update" parameterType="com.example.model.User">
    UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
  </update>

  <delete id="delete" parameterType="long">
    DELETE FROM users WHERE id = #{id}
  </delete>
</mapper>

4. 调用示例

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectById(1L);
    System.out.println(user.getName());
    session.commit(); // 别忘了提交!
}

看到没?没有一行 ResultSet,没有手动关连接,连 SQL 都和 Java 代码分离了。这就是工具的价值——把重复劳动自动化,让我们专注业务逻辑。


动态 SQL:告别 if-else 拼接地狱

外包项目最怕什么?需求变更。昨天说“按姓名查”,今天加“按邮箱查”,明天又说“还要支持模糊+分页”。

如果用 JDBC,你可能写出这种代码:

String sql = "SELECT * FROM users WHERE 1=1";
if (name != null) sql += " AND name LIKE '%" + name + "%'";
if (email != null) sql += " AND email = '" + email + "'";
// ...然后祈祷没 SQL 注入

而在 MyBatis 中,用 <if><where><foreach> 等标签,轻松搞定:

<select id="searchUsers" resultType="User">
  SELECT * FROM users
  <where>
    <if test="name != null and name != ''">
      AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="email != null and email != ''">
      AND email = #{email}
    </if>
  </where>
</select>

<where> 标签会自动去掉开头的 AND,避免语法错误。这种设计,简直是为“需求飘忽”的甲方量身定制。


性能与可维护性:外包党的生存之道

作为靠口碑接单的自由开发者,我深知:代码可读性 = 回头客率

MyBatis 在这方面做得不错:

  • SQL 集中管理:所有查询都在 XML 文件里,DBA 审核、性能优化一目了然。
  • 类型安全:通过 resultTyperesultMap 明确映射关系,避免字段名拼错。
  • 缓存支持:一级缓存(SqlSession 级)默认开启,二级缓存可配置,减少数据库压力。

不过,也别迷信缓存。去年双11期间,我就因为误开二级缓存导致数据不一致,被客户骂到凌晨三点。后来学乖了:缓存只用于读多写少的场景,且必须设置合理的 TTL

另外,MyBatis 本身不处理事务,但在 Spring 集成后,配合 @Transactional 注解,事务管理就变得非常优雅。


对比其他方案:Go 开发者的视角

有趣的是,我在主业写 Go 时,常用 GORM 或 sqlx。GORM 类似 Hibernate,全自动;sqlx 更像 MyBatis,半自动。

特性 MyBatis (Java) sqlx (Go) GORM (Go)
SQL 控制权
学习曲线
复杂查询支持 极佳 一般
自动生成 无(需插件)
社区生态 成熟 活跃 非常活跃

作为跨语言开发者,我反而觉得 MyBatis 的“显式优于隐式”哲学,更符合工程直觉。尤其是在需要精细调优 SQL 的场景下,它比 ORM 更可靠。


最后几句掏心窝子的话

写这篇教程,不是因为我多爱 MyBatis,而是它实实在在帮我减少了加班时间。在这个“996 是福报”的时代,能早点回家撸猫,比什么都强。

如果你也在维护一个祖传 Java 项目,或者正被 JDBC 折磨得想转行送外卖,不妨试试 MyBatis。它不会让你一夜暴富,但至少能让你的 代码人生 少点 bug,多点 sleep。

对了,别忘了单元测试。MyBatis 支持内存数据库(如 H2)做集成测试,写起来不难,但能避免 80% 的线上事故。毕竟,我们不是在写代码,是在交付信任

好了,我去回客户微信了。希望这次他说的“小问题”真的只是小问题……

(完)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝