MyBatis基础教程:Java持久层框架入门
MyBatis基础教程:Java持久层框架入门
背景:为什么我要写这篇关于 MyBatis 的文章
在我做后端开发的这几年里,几乎所有的项目都离不开数据库操作。最初接触 Java 数据访问的时候,我用的是 JDBC 手动写 SQL,虽然效率可控、透明度高,但代码重复性极高,特别是处理结果集映射和事务管理时,常常让人抓狂。
直到后来接触到了 MyBatis——一个轻量级却又非常强大的 Java 持久层框架。它不像 Hibernate 或 JPA 那样隐藏了太多底层细节,而是给了开发者更多自由去控制 SQL 语句和数据操作方式,尤其适合对性能有要求的系统架构设计。
今天我想结合自己在实际项目中使用 MyBatis 的经验,给大家分享一下 MyBatis 的入门要点,以及我在实战中踩过的坑、优化的思路。希望这篇文章能帮助刚接触 MyBatis 的你少走弯路,更高效地上手这个实用框架。
问题描述:项目初期的数据访问混乱
几年前,我参与了一个中型电商系统的开发工作。这个项目需要频繁地访问商品信息、订单状态、用户行为日志等数据。初期我们尝试使用原生 JDBC + 简单 DAO 层的方式进行数据访问,结果随着业务功能不断扩展,DAO 类越来越多,SQL 嵌套越来越复杂,维护起来简直令人头大。
主要问题包括:
- SQL 写死在 Java 代码中,难以维护;
- 结果集需要手动映射,容易出错;
- 多表关联查询时嵌套遍历效率低;
- 事务管理杂乱无章,容易出现并发问题;
- 无法灵活应对数据库迁移或字段变更。
我们当时意识到,必须引入一个更合理的数据访问层框架来重构代码结构,提升可维护性和性能。于是,MyBatis 成为了我们的选择。
解决方案:选型对比与 MyBatis 的优势
在选型阶段,我们也评估过其他流行的 ORM 框架,比如 Hibernate 和 Spring Data JPA。最终选择了 MyBatis,主要原因如下:
- 灵活性强:MyBatis 不强制使用特定的接口或注解,SQL 和 Java 对象之间可以自由绑定。
- 性能更好:没有像 Hibernate 那样的“自动代理”和延迟加载机制,执行效率更高。
- 学习成本低:配置文件清晰易懂,适合快速上手。
- 适合复杂查询场景:MyBatis 支持动态 SQL 构建,非常适合多条件组合查询和报表类需求。
当然,MyBatis 也有它的缺点,比如不提供自动分页、缺乏连接池默认实现(需整合 Druid 或 HikariCP),但这些都可以通过合理的设计弥补。
实践篇:从零开始搭建 MyBatis 工程
接下来,我将带你一步步完成一个基于 Spring Boot 整合 MyBatis 的简单项目。我们将构建一个基本的用户信息管理系统,并在这个过程中展示 MyBatis 的核心特性。
1. 项目背景
我们需要实现一个用户模块的功能,包括:
- 用户列表展示
- 根据姓名模糊搜索
- 新增用户
- 修改用户信息
- 删除用户
2. 环境准备
技术栈为:
- JDK 17
- Spring Boot 2.7.x
- MyBatis 3.5+
- MySQL 8.x
- Maven 构建工具
3. 数据库表设计
CREATE TABLE `user` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(64) NOT NULL COMMENT '用户名',
`email` VARCHAR(128) UNIQUE NOT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME ON UPDATE CURRENT_TIMESTAMP
);
4. 引入依赖
Maven 中加入以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
5. 配置 application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
stat-view-servlet:
enabled: true
url-pattern: /druid/*
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: "*.js,*.gif,*.jpg,*.css,/druid/*"
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
mapUnderscoreToCamelCase: true # 自动映射下划线字段到驼峰属性
6. 定义实体类
public class User {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getter and Setter 省略
}
7. Mapper 接口定义
@Mapper
public interface UserMapper {
List<User> listAllUsers();
@Select("SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')")
List<User> searchByName(String name);
int insertUser(User user);
int updateUser(User user);
int deleteUserById(Long id);
}
8. XML 文件配置 SQL 映射
资源路径 resources/mapper/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.mapper.UserMapper">
<select id="listAllUsers" resultType="com.example.model.User">
SELECT * FROM user
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, email)
VALUES (#{name}, #{email})
</insert>
<update id="updateUser">
UPDATE user
SET name = #{name}, email = #{email}
WHERE id = #{id}
</update>
<delete id="deleteUserById">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
9. 控制器接口示例
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping
public List<User> getAllUsers() {
return userMapper.listAllUsers();
}
@GetMapping("/search")
public List<User> searchUser(@RequestParam String name) {
return userMapper.searchByName(name);
}
@PostMapping
public ResponseEntity<?> createUser(@RequestBody User user) {
userMapper.insertUser(user);
return ResponseEntity.ok(user);
}
@PutMapping("/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
userMapper.updateUser(user);
return ResponseEntity.ok(user);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userMapper.deleteUserById(id);
return ResponseEntity.noContent().build();
}
}
这样一个简单的用户管理模块就完成了。你可以运行项目并通过 Postman 或 curl 测试各个接口。
踩坑经验:那些年在 MyBatis 里掉进的“陷阱”
任何技术都有其适用边界,下面是我亲身经历的一些“坑”,希望能帮助你避免重蹈覆辙。
坑一:动态 SQL 条件构造出错导致查询全表
我们曾在某个商品搜索模块中用了 <if> 标签,但由于没有设置默认值或主键限制,当所有参数都为空时,居然变成了 WHERE 后面空条件,直接查了全表,导致慢查询拖垮整个服务。
正确做法是加上 <where> 标签包裹 <if>,这样会自动处理 AND/OR 和空条件:
<select id="searchProducts" resultType="Product">
SELECT * FROM product
<where>
<if test="productName != null">
AND product_name LIKE CONCAT('%', #{productName}, '%')
</if>
<if test="category != null">
AND category_id = #{category}
</if>
</where>
</select>
坑二:自定义类型处理器未注册,转换失败
我们在一个日志记录模块中使用了枚举类型,结果插入失败,报“Unknown column type”。
原来是忘记为枚举类型编写 TypeHandler 并注册到 MyBatis。解决方案是自定义 Handler 并在配置中声明:
public class EnumTypeHandler extends BaseTypeHandler<LogType> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LogType parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.name());
}
// 其他方法省略...
}
全局注册:
mybatis:
typeHandlers:
- handler: com.example.handler.EnumTypeHandler
javaType: com.example.type.LogType
坑三:使用 resultMap 映射字段名错误,结果集为空
我们曾遇到一个奇怪的问题:SQL 明明返回了数据,但程序拿到的结果却是空对象。后来发现是表字段名和 Java 对象属性名不一致,又没显式配置 resultMap,导致映射失败。
解决办法是在 Mapper XML 中增加 <resultMap> 定义:
<resultMap id="productResultMap" type="Product">
<id property="productId" column="product_id"/>
<result property="name" column="product_name"/>
<result property="price" column="product_price"/>
</resultMap>
<select id="getProductById" resultMap="productResultMap">
SELECT product_id, product_name, product_price FROM product WHERE product_id = #{id}
</select>
效果总结:从手写 SQL 到优雅 ORM 的蜕变
在整个项目重构完成后,我们获得了以下几个显著的收益:
- 代码整洁度提升:SQL 移出了 Java 代码,逻辑更加清晰,便于团队协作;
- 性能优化空间更大:我们可以针对性地优化高频 SQL 查询,利用索引提高效率;
- 运维友好:通过整合 Druid,可以监控慢查询、连接池状态,提前发现问题;
- 拓展性强:新增字段、修改数据库时只需调整对应的 Mapper,无需大规模改动代码结构。
特别是在高并发访问场景下,我们借助 MyBatis 的缓存机制(如二级缓存)和动态 SQL 的灵活性,成功优化了几个关键接口的响应时间,平均从 200ms 降低到 30ms 以内。
经验分享:给初学者的一些建议
如果你正在学习或者刚开始使用 MyBatis,这里是一些来自实战的经验建议:
- 不要害怕写 SQL:MyBatis 的魅力就在于你对 SQL 的掌控力,适当封装而不是完全隐藏;
- 动态 SQL 是利器,也是双刃剑:合理使用
<if><choose><set>,但不要让它变得过于复杂; - 善用日志插件:比如使用
log4j或slf4j输出实际执行的 SQL,便于调试; - 合理使用缓存:MyBatis 提供了一级和二级缓存,适用于读多写少的场景;
- 配合连接池使用:比如 Druid、HikariCP,防止连接泄漏;
- 分页建议自定义:虽然有 PageHelper 插件,但在某些复杂场景下,不如自己写 LIMIT 更可靠;
- 定期分析执行计划:MySQL 的
EXPLAIN配合 MyBatis 使用,可以有效优化 SQL 性能。
写在最后:技术的初衷是为了让世界更好
回顾这段使用 MyBatis 的过程,我最大的感触就是:技术本身不是目的,而是解决问题的手段。MyBatis 之所以受欢迎,不是因为它有多炫酷的语法或者自动化能力,而是因为它解决了大多数 Java 开发者在数据库交互上的痛点,同时保持了足够的灵活性和性能优势。
作为一个后端工程师,我们要做的不仅是写出跑得通的代码,更要写出能扛得住流量、经得起时间考验、利于团队协作的系统。而 MyBatis,在这条路上给了我不少助力。
希望这篇基于我真实工作经验的分享能对你有所帮助。如果你在使用 MyBatis 过程中也遇到了一些奇奇怪怪的问题,欢迎留言交流,我们一起成长 🚀

评论 0