MyBatis 入门实战:从“写死 SQL”到灵活 ORM 的成长之路

调皮猴
2025-06-15 02:46
阅读 646

开篇:缘起

开篇:缘起

作为一个 Java 后端开发,我经历过一段“原始开发”的阶段。当时刚接触后端系统时,数据层的操作全是通过 JDBC 手写 SQL 拼接完成的,不仅要考虑数据库连接池、事务管理,还要处理各种 ResultSet 的封装逻辑。那段时间,每次看到项目代码里满屏的 try-catch 和 switch-case 就头疼不已。

后来我们团队接手了一个中型项目的重构任务,需要快速搭建稳定的持久层结构。在这个节点上,我第一次正式引入了 MyBatis 作为 ORM 层的解决方案。这个框架在灵活性和性能之间取得了一个不错的平衡点,既没有 Spring Data JPA 那样“重”,又不像纯 JDBC 那么繁琐。

今天,我想基于这次项目经历,谈谈我是如何从“MyBatis 新手”成长为能够驾驭它的人,也希望能给还在使用 JDBC 或者准备入门 MyBatis 的同学一些启发。


问题描述:为什么我们需要 MyBatis?

问题描述:为什么我们需要 MyBatis?

项目背景

我们当时要重构的是一个企业内部的 CRM 系统,原本的版本由几个外包团队拼接而成,数据访问部分几乎全部是直接拼接 SQL + Map 手动映射结果集的方式。随着业务功能越来越多,维护成本越来越高:

  • SQL 分散在各个 DAO 类中,修改困难
  • 多个方法调用多个 SQL,事务控制混乱
  • 结果集映射易出错,经常有字段名写错导致数据为空
  • 查询优化难以统一处理

这种情况下,我们决定对数据访问层进行抽象,并评估了几种方案:

技术选型 优点 缺点
原生 JDBC 控制力强 开发效率低,容易出错
Hibernate/JPA 完全 ORM,自动生成 SQL 对复杂查询支持差,SQL 不透明
MyBatis 自定义 SQL,灵活性高 需要手动编写 XML

权衡之下,最终选择了 MyBatis,因为它允许我们保留 SQL 的精细控制能力,同时又能很好地解耦对象模型与数据库结构。


解决方案:引入 MyBatis 架构设计

解决方案:引入 MyBatis 架构设计

整体架构思路

我们在项目中采用了经典的三层架构:

Controller --> Service --> Mapper(MyBatis)

其中,Mapper 层完全由 MyBatis 驱动,而 Service 层负责协调事务并整合多个 Mapper 调用。

为了更好地组织代码,我们还做了几点设计优化:

  1. 统一接口命名规范

    public interface UserMapper {
        User selectById(Long id);
        List<User> selectAll();
        int insert(User user);
        int update(User user);
        int deleteById(Long id);
    }
    
  2. XML 文件集中管理: 把所有的 XML 映射文件统一放在 resources/mapper/ 下,并按模块划分目录结构,比如用户模块放在 resources/mapper/user/UserMapper.xml

  3. 动态 SQL 组装: 利用了 <if><choose> 等标签来简化条件拼接,比如搜索用户的逻辑:

    <select id="searchUser" resultType="User">
        SELECT * FROM users WHERE 1=1
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
    </select>
    
  4. 分页封装: 我们封装了通用的 Page 返回结构,并利用拦截器自动注入 count 查询,提升用户体验。

  5. 事务管理: 通过 Spring 的注解式事务控制,在 Service 层的方法加上 @Transactional,使得多张表操作可以在同一个事务中完成。


代码实践:一步步带你写第一个 Mapper

代码实践:一步步带你写第一个 Mapper

第一步:引入依赖

我们在 Spring Boot 项目中引入了如下依赖:

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Spring Boot 会自动配置 DataSource 和 SqlSessionFactory,非常方便。

第二步:配置 application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/my_crm?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.crm.model

这样就能让 Spring Boot 自动扫描实体类和 XML 映射文件。

第三步:创建 Model 和 Mapper 接口

public class User {
    private Long id;
    private String name;
    private String email;
    // setter & getter...
}
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectById(Long id);

    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(User user);
}

如果你不想在每个 Mapper 上加 @Mapper 注解,也可以在启动类上加 @MapperScan

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

第四步:编写 XML 映射文件

这是更常见的做法,尤其对于复杂的 SQL 来说,XML 更适合管理。

<!-- resources/mapper/user/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.crm.mapper.UserMapper">
    <select id="selectAll" resultType="User">
        SELECT * FROM users
    </select>

    <select id="searchUser" 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>
</mapper>

API接口文档-1


踩坑经验:这些错误你可能也会犯

在实际开发中,我也踩了不少坑,这里挑几个典型的说说:

1. 事务失效了!

刚开始我们以为只要加上 @Transactional 注解就万事大吉,结果发现事务并没有生效。后来查资料才知道:

  • 如果事务方法不是通过 Spring 注入的 Bean 调用的(比如自己 new 实例),事务不生效;
  • 同一个类中非事务方法调用事务方法,也不会生效;
  • 方法必须为 public,protected/private 也不行。

解决办法很简单:确保事务方法通过注入的 Bean 调用即可。

2. 结果映射错误或字段名映射不上

有时候我们会遇到数据库字段是下划线命名,Java 是驼峰命名的情况,这时候就需要设置自动映射规则。

默认情况下,MyBatis 并不会自动帮你做 user_name -> userName 的映射。我们可以通过以下方式解决:

<resultMap id="userResultMap" type="User">
    <id property="id" column="id" />
    <result property="userName" column="user_name"/>
    <result property="email" column="email"/>
</resultMap>

或者启用自动映射:

mybatis:
  configuration:
    mapUnderscoreToCamelCase: true

3. 动态 SQL 标签用错了

比如 <where> 写成了 <if>,或者拼接条件的时候忘记加 AND,都会导致 SQL 出错。特别是下面这个例子:

<select id="wrongIf">
    SELECT * FROM users WHERE
    <if test="name != null">
        name = #{name}
    </if>
    <if test="age != null">
        age = #{age}
    </if>
</select>

如果 name 是空,生成的 SQL 可能变成:

SELECT * FROM users WHERE age = ?

这会导致语法错误,因为 WHERE 后面没有任何有效条件。因此建议使用 <where> 标签包裹所有条件。

4. 使用 resultMap 时出现循环引用

如果我们不小心在 resultMap 中引入了嵌套的对象映射,比如 User 对象包含 Department,而 Department 又反过来包含 UserList,就容易造成栈溢出或无限递归。

这种情况可以通过延迟加载(lazyLoadingEnabled)配合代理模式来处理,或者在关键地方断开循环。


效果总结:从杂乱到有序的转变

微服务架构示意图-2

引入 MyBatis 后,我们整个项目的数据访问层清晰了很多:

  • 可维护性提升:SQL 统一管理,修改方便;
  • 开发效率提升:避免重复写 Resultset 映射;
  • 可读性增强:Mapper 接口和 XML 分离,职责明确;
  • 事务控制得当:通过 Spring 注解实现细粒度事务控制;
  • 扩展性强:后续接入 PageHelper、MyBatis Plus 也非常顺利。

更重要的是,由于 SQL 还是我们自己写的,所以可以放心地做索引优化、执行计划分析等操作,完全掌控数据库层面的性能。


经验分享:给初学者的一些建议

如果你正在学习 MyBatis,我可以给你几个实用建议:

1. 保持 SQL 自主权

不要迷信“全自动 ORM”,尤其是面对复杂业务场景时。很多时候写几句 SQL 比配置一堆关联关系更快、更可控。

2. XML 文件要整洁

即使你可以用注解写 SQL,但对于复杂的查询、更新、批量插入等操作,XML 仍然是最佳选择。记得把 XML 按照模块分类存放。

3. 学好动态 SQL

掌握 <if><choose><where><foreach> 这些标签,它们会让你写出优雅的条件查询和批量操作语句。

4. 配合日志调试

使用 Logback 或 Log4j 输出 MyBatis 生成的 SQL,方便调试和优化。记得打开慢查询日志:

log4j.logger.com.example.crm.mapper=DEBUG

5. 合理使用 PageHelper

分页插件是一个很实用的工具,但注意使用时机和范围,避免影响全局查询。

6. 注意性能边界

虽然 MyBatis 比较轻量,但也别滥用。合理使用缓存、避免 N+1 查询、适当开启批处理,都是提升系统性能的关键点。


结语:MyBatis 是一道通往成熟架构的桥梁

回想起当年那个拿着 JDBC 在 try-catch 里打转的日子,再对比现在用 MyBatis 高效构建稳定系统的体验,真的感触颇深。

MyBatis 可能不是最现代的 ORM 框架,但它确实是一个非常适合中大型项目的技术选型。尤其是在国产化替代的大趋势下,它更是许多政企项目首选的数据访问层解决方案。

如果你也在用 MyBatis,或者正准备开始学习它,希望这篇文章能让你少走些弯路,早点享受它带来的高效与稳定。毕竟,一个好的数据访问层,是一切业务逻辑的根基。

如你有更多疑问或交流,欢迎留言评论,我们一起进步!

评论 0

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