MyBatis基础教程:Java持久层框架入门
引言

作为一名后端开发工程师,我在多个项目中与数据库打交道的过程中,深刻体会到选择一个合适的持久层框架对开发效率和系统可维护性的重要性。早些年我还在用JDBC直接操作数据库,虽然灵活但代码重复多、易出错;后来接触Hibernate时,觉得它的ORM映射确实不错,但有时候又过于“重”,尤其是在需要写一些复杂SQL时,反而不太自由。
直到我接触到MyBatis,才真正找到了一种在灵活性与规范性之间取得平衡的方式。它既不像JDBC那样手动管理连接和结果集,也不像Hibernate那样自动做太多事让我失去对SQL的控制。于是从那时候起,MyBatis成了我大部分项目的默认持久层选择。
这篇文章,我想结合自己参与的一个实际项目来聊聊MyBatis的基础使用、常见问题以及一些开发过程中的实战经验,帮助刚刚接触MyBatis的新手少走弯路。
项目背景与遇到的问题

这个项目是去年我们团队负责的一个在线教育平台的后端服务,主要功能包括课程管理、用户报名、讲师信息维护等模块。早期我们使用的是原生JDBC进行数据访问,随着业务增长,数据表越来越多,DAO层代码越来越臃肿,维护成本极高。
举个例子,当时我们要实现一个查询某个讲师所带所有课程的功能,光写一个带条件分页查询的方法,就写了将近50行的JDBC代码——还要手动处理ResultSet转换成对象,一不小心就会出空指针或者字段不匹配的错误。
更糟糕的是,当SQL语句变多了以后,整个DAO类变得杂乱无章,根本不敢轻易重构。于是我们决定引入MyBatis作为我们的持久层框架,希望借助它解决这些问题。
解决方案:引入MyBatis后的架构设计

在技术选型阶段,我们对比了Spring Data JPA、Hibernate、MyBatis等多个框架,最终选择了MyBatis的原因有以下几点:
- 可以灵活编写SQL,适合我们这种需要较多定制化查询的业务
- 轻量级框架,学习成本低,社区活跃度高
- 与Spring集成良好,几乎零配置即可使用
- 支持动态SQL,可以很好地应对复杂查询
为了更好地整合到现有项目中,我们采用了Spring Boot + MyBatis的组合,整体架构如下图所示(简化版本):
Controller → Service → Mapper(MyBatis接口) → XML SQL文件
我们遵循了一个基本的设计原则:Service层处理业务逻辑,Mapper层只负责SQL映射。这样可以让代码结构更加清晰,也方便测试和维护。
代码实践:从零搭建一个简单的CRUD示例

下面我会以一个简单的User实体为例,展示如何快速上手MyBatis。这是我们在切换过程中做的一个POC(Proof of Concept)小项目,用来验证MyBatis的可行性。
第一步:添加依赖(Maven)
<dependencies>
<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.30</version>
</dependency>
</dependencies>
这里我们使用的是mybatis-spring-boot-starter,非常适合Spring Boot项目。
第二步:配置数据源和MyBatis相关参数
在application.yml中配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?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
mapper-locations是XML映射文件的位置;type-aliases-package是用于别名的Java Bean包路径,后续写SQL时可以省略全限定类名。
第三步:定义Java Bean
package com.example.model;
public class User {
private Long id;
private String name;
private Integer age;
private String email;
// 省略getter/setter
}
第四步:定义Mapper接口
package com.example.mapper;
import com.example.model.User;
import java.util.List;
public interface UserMapper {
List<User> findAll();
User findById(Long id);
void insert(User user);
void update(User user);
void deleteById(Long id);
}
MyBatis的核心思想就是将这些接口方法和具体的SQL绑定起来。你可以通过注解方式,也可以通过XML文件来实现。我们团队习惯使用XML,因为更便于管理复杂的SQL。
第五步:编写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.UserMapper">
<select id="findAll" resultType="User">
SELECT * FROM users
</select>
<select id="findById" parameterType="long" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insert">
INSERT INTO users (name, age, email)
VALUES (#{name}, #{age}, #{email})
</insert>
<update id="update">
UPDATE users SET name=#{name}, age=#{age}, email=#{email}
WHERE id=#{id}
</update>
<delete id="deleteById">
DELETE FROM users WHERE id=#{id}
</delete>
</mapper>
注意几个关键词:
namespace对应接口的全限定类名;id要和接口方法名一致;parameterType表示传入参数类型,如果是基本类型或只有一个参数可以直接写long、int;resultType指定返回值类型,这里用了别名(因为我们配置了type-aliases-package)。
第六步:调用Mapper(Service层)
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> getAllUsers() {
return userMapper.findAll();
}
public User getUserById(Long id) {
return userMapper.findById(id);
}
public void createUser(User user) {
userMapper.insert(user);
}
public void updateUser(User user) {
userMapper.update(user);
}
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
}
至此,一个完整的CRUD流程就已经完成。可以看到MyBatis的使用非常直观,也没有太多抽象层。
踩坑经验:那些让人头大的事儿
虽然MyBatis总体体验很好,但在实际开发过程中还是遇到了一些“坑”,在这里分享一下,希望能帮大家避免类似问题。

1. 动态SQL拼接错误
在初期写动态查询的时候,我们经常会出现拼接失败的情况。例如:
<select id="searchUsers" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
这段SQL的意思是根据name模糊查询和按年龄筛选,但如果所有的条件都不满足,那么WHERE后面会变成只有“1=1”——这在MySQL下没问题,但有些数据库(比如Oracle)就不允许这样写。
所以我们改为使用 <where> 标签:
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
这样即使没有条件,WHERE也不会被渲染出来,兼容性更强。
2. 字段映射不匹配导致的结果为null
我们之前遇到一个问题:数据库字段是下划线命名,而Java Bean用的是驼峰命名。比如数据库字段是user_name,对应的Java字段却是userName,结果发现查询出来的字段值一直是null。
这个问题可以通过开启自动映射设置:
mybatis:
configuration:
mapUnderscoreToCamelCase: true
这样MyBatis会自动把数据库的下划线风格字段转换成Java对象的驼峰命名风格字段。
如果你不想全局生效,也可以在XML里单独指定:
<resultMap id="BaseResultMap" type="User">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_name" property="userName" jdbcType="VARCHAR"/>
...
</resultMap>
3. 事务管理问题
我们有一个需求是批量插入用户信息并记录操作日志。刚开始没加事务,结果插入成功但日志写失败,导致数据不一致。
解决方案是在Service层加上注解:
@Transactional
public void batchInsert(List<User> users) {
for (User user : users) {
userMapper.insert(user);
logMapper.insert(new Log(...));
}
}
同时还要确保你的配置里启用了事务:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {}
效果总结:引入MyBatis后的变化
引入MyBatis之后,整个项目的DAO层有了明显的提升:
- 代码简洁很多:原本几十行的手动JDBC现在几行XML搞定;
- SQL集中管理,易于维护和排查问题;
- 团队协作更顺畅:新人也能快速上手,不再担心SQL嵌在Java代码中难以阅读;
- 性能稳定:相比于Hibernate那种自动生成SQL的方式,我们能精准优化每条语句,特别是在大数据量场景下优势明显;
- 调试工具友好:配合Log4j或者MyBatis自带的日志插件,可以直接看到执行的具体SQL和参数,方便调试。
更重要的是,在后续上线后,我们还发现MyBatis对连接池的利用非常好,结合Druid监控,能够实时查看慢SQL和异常情况,极大提升了线上运维能力。
经验分享:给新手的几点建议

结合我的个人经验,下面是一些给刚入门MyBatis的朋友的建议:
1. 坚持使用XML方式写SQL,不要一开始就追求注解化
MyBatis的注解方式固然简洁,但对于稍微复杂一点的SQL(尤其是带循环和判断的),XML的优势非常明显。而且后期修改SQL不需要改代码编译部署,只需要更新XML即可,尤其适合灰度发布或者紧急修复。
2. 合理划分Mapper文件,保持职责单一
每个表对应一个Mapper接口,不要把太多不相关的SQL塞在一个Mapper里面。比如用户相关放在UserMapper.xml,订单相关放在OrderMapper.xml,便于查找和维护。
3. 统一命名规范,减少歧义
无论是数据库字段命名(推荐snake_case),还是Java属性命名(推荐camelCase),都要统一。MyBatis虽然可以通过配置自动映射,但保持一致的命名会让代码更整洁、更容易维护。
4. 善用MyBatis的插件生态
MyBatis本身提供了丰富的插件机制,比如PageHelper用于分页、MyBatis-Plus用于增强CRUD操作、还有通用DAO工具类等等。可以根据团队规模和技术栈适当引入,提升开发效率。
我个人在项目中用到了MyBatis-Plus,它提供的LambdaQueryWrapper特别好用,推荐有兴趣的朋友可以尝试。
5. 重视日志输出,别让SQL成为“黑盒”
生产环境下一定要开启MyBatis的日志输出(比如使用log4j或slf4j),方便排查慢SQL、参数错误等问题。你永远不知道哪天SQL里的一个参数没传对,会引发什么后果。
写在最后:MyBatis带给我的成长
回顾整个引入MyBatis的过程,其实并不是一蹴而就的。期间经历了反复调试、踩坑、查资料、看文档、甚至一度想放弃。但正是这一段经历,让我对持久层的理解更加深入,也让我意识到一个优秀的开发者不仅仅是写代码的人,更是善于发现问题并解决问题的人。
如今,MyBatis已经成为我技术栈中不可或缺的一环,不仅在我的日常项目中广泛使用,也在团队知识分享会上作为重点内容进行了讲解。
如果你也是刚入门MyBatis,或者正在考虑使用它作为你的持久层框架,我相信这篇文章能为你提供一些参考和启发。记住一句话:技术没有银弹,只有最合适的选择。
希望你在接下来的学习和工作中,也能找到属于自己的那把“钥匙”。💡
(完)

评论 0