MyBatis基础教程:从零入门Java持久层框架的实战之路

Shell脚本侠
2025-06-16 15:27
阅读 519

引言:第一次用MyBatis,我差点放弃了

引言:第一次用MyBatis,我差点放弃了

那是一个风和日丽的下午,我在一家初创公司做后端开发。当时我们正在重构老系统,原来的代码中全是JDBC操作,动不动就是PreparedStatementResultSet,代码混乱不堪,维护起来简直是个灾难。

项目组长建议我们引入一个ORM框架来简化数据库操作,考虑到团队对Hibernate都不太熟,而大家又都有Java开发经验,于是决定采用更轻量级的 MyBatis。作为一个刚刚接触它的新手,我一开始觉得“这玩意儿不就是写SQL还要自己映射吗?何必呢”,结果一上手才发现,它确实比原生JDBC要优雅得多,但也有很多细节需要注意。

今天我就想结合那次实战经历,分享一下我在MyBatis学习过程中踩过的坑、总结出的经验,希望你能在路上少走弯路。


项目背景与技术挑战

负载均衡配置-1

项目背景与技术挑战

我们的项目是一个企业内部管理系统,主要包括员工管理、考勤打卡、请假审批等模块。数据库使用MySQL,数据量不算太大,但因为是老系统迁移过来的,表结构比较复杂,存在不少关联关系和历史遗留字段。

在没有引入MyBatis之前,我们每次写接口都要手动开连接、处理事务、拼装查询条件、手动封装实体类,非常繁琐。特别是在涉及多表关联或者动态条件查询时,代码更是冗长复杂,容易出错。

主要痛点:

  • 每次增删改查都得写一大堆模板代码
  • 结果集映射需要大量重复逻辑
  • 多条件查询拼接麻烦且容易出错
  • 事务控制难以统一管理

这时候引入MyBatis就成了我们优化的重点方向之一。


我的选择:为什么是MyBatis?

虽然现在Spring Data JPA、Hibernate也很流行,但在以下几个方面,MyBatis显得更贴合我们的实际需求:

  1. 灵活掌控SQL:有些复杂查询必须手写SQL才能写出高效的语句,MyBatis在这方面非常自由。
  2. 轻量无侵入:相比Hibernate这类重量级ORM,MyBatis不会强制绑定对象模型,更适合现有系统改造。
  3. 性能可控:由于直接写SQL,可以精细地优化执行效率。
  4. 学习成本低:团队成员大多熟悉SQL,快速上手不是问题。

实践过程:MyBatis是如何改变我的编码方式的?

下面我会以员工信息查询功能为例,带你一起看看我们是怎么一步步搭建起MyBatis的骨架的。

1. 数据库设计:别小看这点

我们在用户管理模块有一个核心表employee,包含如下关键字段:

CREATE TABLE `employee` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `real_name` varchar(100) NOT NULL,
  `department_id` int(11) DEFAULT NULL,
  `status` tinyint(4) DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

状态字段status表示员工是否在职(1在职,0离职),这些设计其实在后续实现逻辑判断时会用到很多。

2. 环境搭建:从引入依赖开始

我们在Spring Boot项目中整合了MyBatis,所以只需要添加相关依赖即可:

<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>
    <version>8.0.29</version>
</dependency>

接着配置数据源和MyBatis扫描路径:

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

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

3. 映射文件:XML还是注解?我选XML

我个人更喜欢XML方式,因为当查询变得复杂的时候,比如有多条件判断、多表关联、动态SQL,XML写起来更清晰。以下是我们为员工查询写的Mapper XML:

<!-- EmployeeMapper.xml -->
<mapper namespace="com.example.mapper.EmployeeMapper">

    <select id="getById" resultType="Employee">
        SELECT * FROM employee WHERE id = #{id}
    </select>

    <select id="search" resultType="Employee">
        SELECT *
        FROM employee
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="departmentId != null">
                AND department_id = #{departmentId}
            </if>
            <if test="status != null">
                AND status = #{status}
            </if>
        </where>
    </select>

</mapper>

对应的Mapper接口:

@Mapper
public interface EmployeeMapper {
    Employee getById(Long id);
    List<Employee> search(@Param("username") String username, 
                          @Param("departmentId") Integer departmentId,
                          @Param("status") Integer status);
}

是不是看起来清爽多了?而且你看动态查询部分,<where> + <if>组合就解决了参数为空的问题。

4. 使用示例:Service层调用

接下来在Service层注入Mapper并调用:

@Service
public class EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    public Employee getEmployeeById(Long id) {
        return employeeMapper.getById(id);
    }

    public List<Employee> searchEmployees(String username, Integer deptId, Integer status) {
        return employeeMapper.search(username, deptId, status);
    }
}

再配合Controller层,就可以轻松实现接口了。


踩坑经验:那些年我掉进去的坑

说了这么多顺利的部分,其实真正开发中也遇到了一些典型问题,这里给你打个样:

坑一:动态SQL中的条件合并逻辑错误

有一次我们写了一个复杂的搜索功能,多个<if>标签嵌套,结果导致生成的SQL在某些情况下出现了语法错误。后来我们发现是MyBatis的<where>标签自动处理AND/IF开头的情况,避免了SQL格式问题。

✅ 解决方案:记住用<where>包裹多个动态条件。

坑二:MyBatis映射不到字段

我们在数据库里有个字段叫real_name,对应的Java属性名却是realName。刚开始没设置字段映射,结果一直读不出值。

✅ 解决方法有两种:

  1. 使用resultMap显式映射:
<resultMap id="employeeMap" type="Employee">
    <id property="id" column="id"/>
    <result property="realName" column="real_name"/>
</resultMap>
  1. 或者在全局配置中开启自动映射:
mybatis:
  configuration:
    mapUnderscoreToCamelCase: true

我强烈推荐后者,简单省事!

坑三:PageHelper分页失效

我们在做列表分页时用了PageHelper插件,但有时候分页失效,总数不对,翻页出错。

经过排查发现是因为在查询前没有正确调用PageHelper.startPage(pageNum, pageSize)方法,或者中间调用了别的查询语句导致上下文丢失。

✅ 解决方式:

PageHelper.startPage(1, 10);
List<Employee> list = employeeMapper.selectAll();
PageInfo<Employee> pageInfo = new PageInfo<>(list);

确保startPage紧跟你的第一个查询语句,并且只对紧跟着的那个查询生效。


性能优化:不只是功能,还要跑得快

虽然MyBatis本身是高性能的,但在实际使用中,有几个地方还是需要特别注意的:

1. 批量插入慎用 foreach

在一次数据导入任务中,我们需要一次性插入几千条数据,最开始尝试了foreach循环批量插入:

<insert id="batchInsert">
    INSERT INTO table (a, b, c)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.a}, #{item.b}, #{item.c})
    </foreach>
</insert>

结果发现速度极慢。后来换成了JDBC批处理的方式,效率提升了十几倍。所以建议大数据量场景下考虑原生Batch操作或MyBatis BatchExecutor模式

2. 关联查询尽量用JOIN,不要多次查询

我们在某个报表功能里用了N+1查询,导致系统卡顿。后来改用LEFT JOIN一次性取数据,在DAO层手动组装对象,效果明显提升。

3. 缓存机制要合理使用

虽然MyBatis有二级缓存,但我们并没有启用,主要原因在于业务中很多查询都是实时的,缓存命中率不高。如果你的系统读多写少,可以考虑开启。


效果总结:效率提升,维护更容易

自从全面引入MyBatis后,我们取得了几个显著的效果:

  • 接口开发速度提升50%,代码简洁清晰,可读性强
  • SQL逻辑集中化,便于统一优化
  • 动态查询逻辑清晰,调试方便
  • 减少了大量样板代码,提高了团队协作效率

更重要的是,我们可以在不影响上层业务的情况下,灵活调整底层SQL,这对于长期维护来说非常重要。


给你的几点建议

最后,作为一名经历过“血与泪”洗礼的老司机,我给刚学MyBatis的同学几点建议:

  1. 先练手写SQL再追求自动化
    ORM只是工具,理解SQL才是根本。MyBatis让你保留掌控力,别怕写SQL。

  2. 学会用好<where> <set> <choose>等标签
    这些动态SQL标签是你构建灵活查询的神器。

  3. 注意字段映射策略
    别让real_name变成null,合理利用mapUnderscoreToCamelCaseresultMap

  4. 别过度依赖插件
    PageHelper、通用Mapper等插件很好用,但也容易让人忽略底层原理,建议你了解其原理后再用。

  5. 多用日志打印SQL,调试更方便
    开启MyBatis日志(log4j2或logback),看真实执行的SQL语句,便于排查问题。


写在最后:技术只是工具,解决问题才是目的

回头看,MyBatis并不是什么高深的技术,但它确实在日常工作中帮了我很大的忙。通过这次实战,我深刻体会到:技术本身并不复杂,关键是在遇到问题时如何灵活应对。

现在的Java生态百花齐放,但无论用什么框架,背后的逻辑和原理都是相通的。希望这篇MyBatis的基础教程不仅能帮助你快速上手,还能启发你思考更深层的设计问题。

如果你也有关于MyBatis使用的经历或者踩过的坑,欢迎留言交流,我们一起进步 🤝


关注我,更多实战经验分享等你来看

评论 0

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