MyBatis基础教程:从零入门Java持久层框架的实战之路
引言:第一次用MyBatis,我差点放弃了

那是一个风和日丽的下午,我在一家初创公司做后端开发。当时我们正在重构老系统,原来的代码中全是JDBC操作,动不动就是PreparedStatement、ResultSet,代码混乱不堪,维护起来简直是个灾难。
项目组长建议我们引入一个ORM框架来简化数据库操作,考虑到团队对Hibernate都不太熟,而大家又都有Java开发经验,于是决定采用更轻量级的 MyBatis。作为一个刚刚接触它的新手,我一开始觉得“这玩意儿不就是写SQL还要自己映射吗?何必呢”,结果一上手才发现,它确实比原生JDBC要优雅得多,但也有很多细节需要注意。
今天我就想结合那次实战经历,分享一下我在MyBatis学习过程中踩过的坑、总结出的经验,希望你能在路上少走弯路。
项目背景与技术挑战


我们的项目是一个企业内部管理系统,主要包括员工管理、考勤打卡、请假审批等模块。数据库使用MySQL,数据量不算太大,但因为是老系统迁移过来的,表结构比较复杂,存在不少关联关系和历史遗留字段。
在没有引入MyBatis之前,我们每次写接口都要手动开连接、处理事务、拼装查询条件、手动封装实体类,非常繁琐。特别是在涉及多表关联或者动态条件查询时,代码更是冗长复杂,容易出错。
主要痛点:
- 每次增删改查都得写一大堆模板代码
- 结果集映射需要大量重复逻辑
- 多条件查询拼接麻烦且容易出错
- 事务控制难以统一管理
这时候引入MyBatis就成了我们优化的重点方向之一。
我的选择:为什么是MyBatis?
虽然现在Spring Data JPA、Hibernate也很流行,但在以下几个方面,MyBatis显得更贴合我们的实际需求:
- 灵活掌控SQL:有些复杂查询必须手写SQL才能写出高效的语句,MyBatis在这方面非常自由。
- 轻量无侵入:相比Hibernate这类重量级ORM,MyBatis不会强制绑定对象模型,更适合现有系统改造。
- 性能可控:由于直接写SQL,可以精细地优化执行效率。
- 学习成本低:团队成员大多熟悉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。刚开始没设置字段映射,结果一直读不出值。
✅ 解决方法有两种:
- 使用
resultMap显式映射:
<resultMap id="employeeMap" type="Employee">
<id property="id" column="id"/>
<result property="realName" column="real_name"/>
</resultMap>
- 或者在全局配置中开启自动映射:
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的同学几点建议:
先练手写SQL再追求自动化
ORM只是工具,理解SQL才是根本。MyBatis让你保留掌控力,别怕写SQL。学会用好
<where><set><choose>等标签
这些动态SQL标签是你构建灵活查询的神器。注意字段映射策略
别让real_name变成null,合理利用mapUnderscoreToCamelCase或resultMap。别过度依赖插件
PageHelper、通用Mapper等插件很好用,但也容易让人忽略底层原理,建议你了解其原理后再用。多用日志打印SQL,调试更方便
开启MyBatis日志(log4j2或logback),看真实执行的SQL语句,便于排查问题。
写在最后:技术只是工具,解决问题才是目的
回头看,MyBatis并不是什么高深的技术,但它确实在日常工作中帮了我很大的忙。通过这次实战,我深刻体会到:技术本身并不复杂,关键是在遇到问题时如何灵活应对。
现在的Java生态百花齐放,但无论用什么框架,背后的逻辑和原理都是相通的。希望这篇MyBatis的基础教程不仅能帮助你快速上手,还能启发你思考更深层的设计问题。
如果你也有关于MyBatis使用的经历或者踩过的坑,欢迎留言交流,我们一起进步 🤝
✨ 关注我,更多实战经验分享等你来看!

评论 0