MyBatis基础教程:一个后端开发者的真实成长记录

深度学习小白
2025-06-18 03:22
阅读 702

引言:从JDBC到MyBatis的转变

引言:从JDBC到MyBatis的转变

我至今还记得第一次独立负责公司内部管理系统开发时,数据库操作那部分代码写得有多么痛苦。那时我还在用原生的JDBC来写数据层,每次查询都要手动打开连接、处理结果集,还要小心翼翼地防止SQL注入。后来在一次技术分享会上听前辈提到MyBatis这个持久层框架,抱着试试看的心态开始尝试,没想到这一用就是好几年。

今天我想和大家聊聊我是如何一步步掌握MyBatis的,以及在这个过程中踩过哪些坑,总结出哪些经验教训。文章内容都来自我的真实工作场景,不是那种死板的教程,更像是我和MyBatis一起“成长”的故事。


项目背景与挑战

项目背景与挑战

去年我们团队接了一个新项目——为某连锁零售企业搭建一套门店库存管理平台。系统需要对接多个门店的销售终端,每天要处理数万条商品进出库的数据,并支持按天、周、月维度做库存分析。

起初我们的DAO层采用的是最原始的Spring JDBC方式,写着写着发现几个明显的问题:

  1. 重复代码太多:几乎每个方法都是一样的模板代码(打开连接、预编译、执行SQL等),维护起来非常麻烦;
  2. 字段映射繁琐:每次查完ResultSet都要手动一个个set到实体类中,一不小心还会搞错顺序;
  3. 性能瓶颈显现:随着测试数据量增大,频繁的数据库操作开始影响接口响应速度;
  4. 事务控制困难:多个业务操作必须保证同时成功或失败,而原生代码里加try-catch实在太丑陋了。

这个时候我就意识到:是时候用点更成熟的ORM工具了,MyBatis成了首选。


接入MyBatis:从入门到上手

初探MyBatis

我先在一个小型功能模块中试水:商品类目管理。整个过程大致分为三步:

  1. 引入依赖

    <!-- Maven依赖示例 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    
  2. 配置数据源

    application.yml里加上MySQL连接信息,使用HikariCP作为连接池:

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/inventory?useSSL=false&serverTimezone=UTC
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    
  3. 创建Mapper接口和XML文件

    // CategoryMapper.java
    public interface CategoryMapper {
        List<Category> findAll();
        Category findById(Long id);
        int insert(Category category);
        int update(Category category);
        int deleteById(Long id);
    }
    
    <!-- CategoryMapper.xml -->
    <mapper namespace="com.example.mapper.CategoryMapper">
        <select id="findAll" resultType="com.example.model.Category">
            SELECT * FROM categories;
        </select>
    
        <select id="findById" parameterType="long" resultType="com.example.model.Category">
            SELECT * FROM categories WHERE id = #{id};
        </select>
    
        <insert id="insert" parameterType="com.example.model.Category">
            INSERT INTO categories (name, description)
            VALUES (#{name}, #{description});
        </insert>
    </mapper>
    

刚开始的时候我还担心MyBatis会像Hibernate那样自动生成很多复杂的SQL,结果发现它的理念刚好相反:“不封装过多逻辑,把控制权还给开发者”,这正是我喜欢的地方。


真实问题来了!

不过刚上手MyBatis时还是遇到几个棘手的问题:

问题1:字段名和属性名对不上怎么办?

数据库字段是create_time,Java属性叫createTime,导致自动映射失败。

解决方法

<resultMap id="BaseResultMap" type="com.example.model.Category">
    <id column="id" property="id" jdbcType="BIGINT"/>
    <result column="name" property="name" jdbcType="VARCHAR"/>
    <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>

或者直接用注解:

@Results(id = "categoryMap", value = {
    @Result(property = "createTime", column = "create_time")
})

问题2:嵌套对象映射太麻烦?

比如订单中有用户信息、商品信息等多个关联对象,手动拼装特别累。

解决思路

  • 使用<association>进行一对一映射;
  • 使用<collection>进行一对多映射;
  • 或者直接在SQL中JOIN两张表,手动赋值(适合复杂场景);

举个例子:

<resultMap id="OrderDetailMap" type="com.example.model.Order">
    <id column="order_id" property="id"/>
    <result column="amount" property="amount"/>
    <association property="customer" javaType="com.example.model.Customer">
        <id column="customer_id" property="id"/>
        <result column="customer_name" property="name"/>
    </association>
</resultMap>

这样就能自动把客户信息填充进Order对象里了。

问题3:动态SQL该怎么写?

比如查询条件不确定、分页参数变化等情况。

解决方案:善用MyBatis的标签:

<select id="searchProducts" parameterType="map" resultType="Product">
    SELECT * FROM products
    <where>
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="minPrice != null">
            AND price >= #{minPrice}
        </if>
        <if test="maxPrice != null">
            AND price <= #{maxPrice}
        </if>
    </where>
    LIMIT #{offset}, #{limit}
</select>

这部分我一开始完全不会,全靠文档和Stack Overflow翻出来才搞定。


上线后的优化和实战经验

数据流转过程-1

项目上线之后,我们也遇到了一些生产环境上的问题,这里也想跟大家分享一下我踩过的坑。

1. SQL语句没走索引,查询巨慢!

某个接口响应时间高达3秒以上,排查后发现是用了模糊匹配又没有索引。

修复方法

  • 给常用查询字段加索引;
  • 避免在WHERE子句中对字段进行函数操作;
  • 使用EXPLAIN分析执行计划。

2. 不小心写了N+1查询

在查订单列表的时候,顺带查了每个订单的客户信息,结果每条记录都触发了一次SQL:

for (Order order : orders) {
    Customer customer = customerMapper.findById(order.getCustomerId());
}

这种写法在大数据量下性能很差。

优化方案

  • 改成批量查询 List<Customer> findBatchByIds(List<Long> ids)
  • 使用懒加载 <association fetchType="lazy">
  • 更极端的可以用JOIN一次性取出所有字段

3. Mapper文件路径扫描不到?

有时候项目启动时报Invalid bound statement not found错误。

原因:Spring Boot默认只扫描resources目录下的mapper XML,如果放在其他地方就找不到。

解决办法

application.yml中添加:

mybatis:
  mapper-locations: classpath:mapper/**/*.xml

另外注意namespace是否对应正确。


使用MyBatis后的收益

自从全面接入MyBatis后,我们的系统架构也有了很大改观:

  1. 代码简洁度显著提升,不再有大量的冗余SQL和结果集处理代码;
  2. 可读性和可维护性更强,SQL都在XML里统一管理,修改更方便;
  3. 性能可控性强:不像某些ORM框架会自动生成各种复杂查询,MyBatis的SQL都是自己写的,更容易做索引优化;
  4. 便于扩展:后续集成PageHelper、通用Mapper等插件都很简单。

我的一些建议和经验分享

如果你刚开始接触MyBatis,以下几点建议可能会对你有所帮助:

1. 不要一上来就用通用Mapper

很多新手喜欢直接引入MyBatis-Plus、tk.mybatis这样的工具包,但建议你先手写CRUD练习一遍,理解基本流程后再去使用这些高级工具。

2. SQL日志一定要打开

开发阶段可以在配置里开启SQL打印:

logging:
  level:
    com.example.mapper: debug

看到真正的SQL执行情况有助于调试和性能优化。

3. 尽量不要将复杂业务逻辑写在XML中

MyBatis只是帮你简化数据库交互的工具,不是用来替代业务逻辑的。别在XML里塞一堆CASE WHEN IF ELSE,这样后期维护会很痛苦。

4. 考虑结合二级缓存提升性能

对于访问频繁但不常更新的数据(如类目、区域等),可以开启MyBatis的二级缓存机制:

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

但要注意并发场景下数据一致性问题。

5. 多写单元测试验证SQL行为

写一些简单的测试用例,比如调用Mapper的某个方法后,看看执行的SQL是什么样子的,避免出现意外的结果。


结语:MyBatis是我最喜欢的Java ORM之一

一路走来,虽然MyBatis并不是最“现代化”的ORM工具,但它足够灵活,对数据库的掌控感强,性能也容易优化。特别是在高并发、大数据量的场景下,比全自动映射框架更适合使用。

如果你是一个追求性能、喜欢掌控底层细节的后端开发者,真心推荐你学习并熟练掌握MyBatis。它就像一把瑞士军刀,在真实的生产环境中非常实用。

希望这篇文章能给你带来一些启发和帮助。如果你也在工作中遇到类似的问题,欢迎留言交流,我们一起进步!

评论 0

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