从 JDBC 到 MyBatis:一个后端工程师的成长之路

写给机器的诗
2025-06-13 19:14
阅读 267

我是一个工作了五年的后端 Java 工程师,这几年一直在做系统后台的开发工作,参与过多个中大型项目的架构和实现。回想刚开始入行时,写数据库交互的部分总是让人头疼——手写 SQL、管理连接池、处理结果集,不仅效率低还容易出错。

直到后来接触到 MyBatis 这个框架,才真正体会到了持久层框架的魅力。它不像 Hibernate 那样“封装太重”,也不像纯 JDBC 那么“原始”。它的设计很贴近我们日常工作的实际需求,灵活性和性能之间找到了一个不错的平衡点。

所以今天我想结合自己的真实项目经验,来聊聊我对 MyBatis 的理解和使用心得,特别是对于刚入门的同学来说,如何快速上手并避免踩坑。


一、初识 MyBatis:一次真实项目的痛楚

一、初识 MyBatis:一次真实项目的痛楚

大概三年前,我在一家做供应链系统的公司负责订单模块的开发。项目本身不大,但我们需要频繁与数据库打交道,尤其是订单状态变更、库存调整这类业务逻辑。

当时我们用的是最原始的 JDBC + DAO 模式,代码大概是这样的:

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;

try {
    conn = dataSource.getConnection();
    ps = conn.prepareStatement("SELECT * FROM orders WHERE user_id = ?");
    ps.setInt(1, userId);
    rs = ps.executeQuery();
    
    while (rs.next()) {
        Order order = new Order();
        order.setId(rs.getLong("id"));
        order.setUserId(rs.getInt("user_id"));
        // ...一堆 set 方法
    }
} catch (SQLException e) {
    // 异常处理...
} finally {
    // 关闭资源...
}

这段代码看起来没什么问题,但一旦业务复杂起来,你会发现到处都是类似的样板代码,而且很容易漏掉某些字段赋值,或者忘记关闭资源导致连接泄漏。再加上不同人写的 SQL 风格不统一,维护起来特别痛苦。

问题总结:

  • 样板代码太多
  • 容易出现资源泄露
  • SQL 和 Java 对象之间的映射关系手工处理
  • 可读性差、不易维护

二、为什么选择 MyBatis?

二、为什么选择 MyBatis?

在那次项目上线后,我们团队决定对数据库访问层进行重构。我当时也是第一次正式接触 MyBatis,开始研究怎么用它替代原有的 JDBC 写法。

MyBatis 最吸引我的几个特点是:

1. 灵活的 SQL 控制

你可以完全掌握 SQL 的编写权,MyBatis 只负责帮你自动映射参数和结果。这相比 Hibernate 的自动 SQL 生成机制,更适合我们的场景(经常需要优化查询性能)。

2. 轻量级、易集成

不需要复杂的配置,直接通过 XML 或注解定义 SQL 映射即可。适合中小型项目快速搭建。

3. 支持动态 SQL

比如 <if><foreach> 这些标签,能帮助你写出结构清晰又功能强大的条件查询语句。

4. 天然支持分页、缓存等高级特性(需配合插件)

MyBatis 提供了良好的扩展接口,后续我们可以加入 PageHelper 做分页,或是二级缓存提升性能。


三、MyBatis 实战演练:从零搭建订单 DAO 层

三、MyBatis 实战演练:从零搭建订单 DAO 层

下面我们就以订单模块为例,一步步看看如何用 MyBatis 来构建数据库访问层。

1. 依赖引入(Spring Boot 项目)

如果你用的是 Spring Boot,只需要加上如下依赖即可:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>

2. 数据库表结构

为了简单起见,假设我们有如下订单表 orders

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    product_code VARCHAR(50),
    amount DECIMAL(10,2),
    status TINYINT DEFAULT 0,
    create_time DATETIME
);

对应的实体类 Order.java

public class Order {
    private Long id;
    private Integer userId;
    private String productCode;
    private BigDecimal amount;
    private Byte status;
    private LocalDateTime createTime;

    // getter / setter ...
}

3. Mapper 接口

创建一个 OrderMapper 接口:

@Mapper
public interface OrderMapper {
    List<Order> selectByUserId(@Param("userId") Integer userId);
}

4. Mapper XML 文件(SQL 映射)

新建文件 resources/mapper/OrderMapper.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.OrderMapper">
    <select id="selectByUserId" resultType="com.example.entity.Order">
        SELECT *
        FROM orders
        WHERE user_id = #{userId}
    </select>
</mapper>

5. 启动类添加扫描路径(非 Spring Boot 可跳过)

@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

现在,我们就可以直接注入 OrderMapper 并使用了:

@Autowired
private OrderMapper orderMapper;

public List<Order> getOrders(Integer userId) {
    return orderMapper.selectByUserId(userId);
}

是不是比以前清爽太多了?更关键的是,MyBatis 自动帮我们完成了数据库记录到 Java 对象的映射,大大减少了出错率。


四、挑战升级:动态 SQL 实战

四、挑战升级:动态 SQL 实战

有一次我们需要根据用户输入的多个筛选条件来查订单,包括用户 ID、商品编码、订单状态、时间段等。

这时候就需要用到 MyBatis 的动态 SQL 了。看下最终的写法:

<select id="searchOrders" parameterType="map" resultType="com.example.entity.Order">
    SELECT *
    FROM orders
    <where>
        <if test="userId != null">
            AND user_id = #{userId}
        </if>
        <if test="productCode != null and productCode != ''">
            AND product_code LIKE CONCAT('%', #{productCode}, '%')
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="startTime != null">
            AND create_time >= #{startTime}
        </if>
        <if test="endTime != null">
            AND create_time <= #{endTime}
        </if>
    </where>
</select>

这个 <where> 标签非常贴心,会自动忽略开头的 “AND” 或 “OR”,还能动态拼接条件。

这样写出来的 SQL,不仅逻辑清晰,而且非常易于后期维护和拓展。


五、常见问题和经验分享

在使用 MyBatis 的过程中,我也踩过不少坑,这里总结几点建议,送给正在学习或已经使用的小伙伴。

1. 使用 resultMap 显式映射字段,避免歧义

虽然可以直接用 resultType="Order",但如果表字段名和 Java 属性不一致,推荐显式定义 resultMap,比如:

<resultMap id="orderResultMap" type="Order">
    <id property="id" column="id"/>
    <result property="userId" column="user_id"/>
    <result property="productCode" column="product_code"/>
    <!-- 其他字段 -->
</resultMap>

<select id="selectAll" resultMap="orderResultMap">
    SELECT * FROM orders
</select>

2. 使用日志插件查看执行的 SQL

调试阶段一定要打开 SQL 日志,方便排查问题。可以在配置文件中启用:

logging:
  level:
    com.example.mapper: debug

你会在日志中看到类似如下的输出:

==>  Preparing: SELECT * FROM orders WHERE user_id = ?
==> Parameters: 123(Integer)

3. 小心空集合的 foreach 问题

当你传入一个空列表给 <foreach>,MyBatis 默认不会报错,但可能让你的 SQL 语法出错。例如:

<foreach collection="ids" item="id" open="(" separator="," close=")">
    #{id}
</foreach>

如果 ids 是空的,那么最后的 SQL 就变成 ( ),很可能引起语法错误。建议加一层判断:

<if test="ids != null and ids.size() > 0">
    AND id IN
    <foreach ... />
</if>

六、MyBatis 的进阶玩法

随着项目越做越大,我们会逐渐接触到一些更高级的功能:

1. 一级缓存 & 二级缓存

MyBatis 自带的一级缓存默认是开启的(基于 SqlSession)。二级缓存可以通过配置实现跨 SqlSession 的数据缓存,适用于读多写少的场景。

2. 分页插件 PageHelper

在后台管理系统中,分页几乎是标配。PageHelper 插件可以非常方便地实现分页逻辑,只需一句:

PageHelper.startPage(pageNum, pageSize);
List<Order> list = orderMapper.selectAll();
PageInfo<Order> pageInfo = new PageInfo<>(list);

它会在你执行查询前自动修改 SQL 加上 limit 子句。

3. 批处理操作(Batch Insert/Update)

对于需要大批量插入或更新数据的情况,可以使用 SqlSession.batchInsert() 或自定义批量 SQL,提升数据库吞吐量。


七、生产环境的经验教训

  1. 不要把业务逻辑放在 XML 中
    虽然 MyBatis 支持 <script> 标签嵌入复杂逻辑,但这会让 SQL 更难阅读和测试。保持 SQL 的简洁性,尽量只用简单的条件拼接。

  2. 合理使用主键策略
    比如在 MySQL 中我们可以设置 useGeneratedKeyskeyProperty 自动获取主键,而不是手动查询 max(id)。

  3. 避免 N+1 查询问题
    当有多表关联查询时,注意使用 <association><collection> 做延迟加载或者联表查询优化。

  4. SQL 注入防范
    所有查询必须使用 #{} 占位符,禁止拼接字符串。如果有模糊查询需求,也应使用 CONCAT('%', #{xxx}, '%') 而不是在代码里拼接 %value%


八、写在最后

MyBatis 不是最先进的 ORM 框架,但它足够轻量、灵活,也非常适合国内大多数后端团队的实际需求。作为一线开发者,我真心觉得它是值得花时间深入掌握的技术。

希望这篇文章能帮你快速理解 MyBatis 的基本用法,并避免常见的坑。后续我还会继续分享更多关于 MyBatis 的实战技巧,比如多数据源、性能调优、MyBatis Plus 的使用等,欢迎关注。

最后送大家一句话:

“好的工具应该让你专注业务,而不是被底层细节绊倒。”

而 MyBatis 正是那个好帮手。

评论 0

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