MyBatis基础教程:Java持久层框架入门
开篇:为什么我选择分享这个话题?

记得刚工作那会儿,我在公司接的第一个项目是一个小型的CRM系统,主要功能是管理客户信息、订单以及售后服务记录。当时我们使用的是JDBC直接操作数据库,代码量大不说,维护起来也特别痛苦。每写一个业务逻辑就得考虑怎么拼SQL语句、处理结果集,而且一旦字段变了,一堆地方得同步修改。
直到后来我们团队决定引入MyBatis作为ORM框架,我才真正体会到什么叫“优雅地和数据库打交道”。那时候刚开始学MyBatis,踩过不少坑,比如XML配置出错、参数绑定问题、还有懒加载不生效这些常见的陷阱。但现在回想起来,正是这些问题让我对MyBatis的理解从表面深入到了底层机制。
今天我想结合自己的实战经验,带大家走进MyBatis的世界,通过一个真实的项目背景,手把手带你从零开始搭建一个基于MyBatis的基础工程,并逐步解决一些常见问题。如果你是刚接触MyBatis的小白,或者在实际开发中总感觉用得不够顺手,这篇文章应该能帮到你。
项目背景:小型CRM系统的重构

系统概览
我们要重构的是一个企业内部使用的客户管理系统,主要包括以下几个模块:
- 客户管理(增删改查)
- 订单管理(订单与客户的关联)
- 售后服务(工单流转)
数据库使用MySQL,表结构大致如下:
CREATE TABLE customer (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
phone VARCHAR(20),
create_time DATETIME
);
CREATE TABLE order_info (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
customer_id BIGINT,
product_code VARCHAR(50),
amount DECIMAL(10,2),
FOREIGN KEY (customer_id) REFERENCES customer(id)
);
CREATE TABLE service_ticket (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
customer_id BIGINT,
title VARCHAR(100),
status ENUM('NEW', 'PROCESSING', 'CLOSED'),
assignee VARCHAR(50),
create_time DATETIME,
update_time DATETIME
);
旧版系统使用JDBC直接访问数据库,接口设计耦合严重,扩展性和可维护性极差。于是我们决定重构,目标之一就是引入MyBatis来简化数据访问层。
问题描述:遇到的挑战与痛点
第一次尝试使用MyBatis的时候,我们遇到了几个典型的问题:
- 不知道如何组织DAO接口和Mapper文件
- SQL写在XML里,怎么传参、怎么映射结果?
- 如何实现多表关联查询的自动映射?
- 日志调试难,出现问题无法快速定位
尤其是第三个问题——多表联查时对象嵌套映射,困扰了我一段时间。例如,在订单详情页面我们需要显示客户信息和订单信息,这时候就需要JOIN两个表,还要把结果映射成两个对象组合的结构,这在JDBC时代需要手动拼装,但MyBatis有没有更优雅的方式呢?
另外,我们在生产环境还发现了一个性能问题:频繁的数据库连接没有复用,导致数据库压力过大。这其实是MyBatis配置不当造成的。
解决方案:一步步引入MyBatis并优化架构
第一步:引入MyBatis核心依赖
我们的项目是Spring Boot项目,所以只需要在pom.xml里添加以下依赖即可:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
然后在application.yml中配置数据源和MyBatis相关参数:
spring:
datasource:
url: jdbc:mysql://localhost:3306/crm_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.crm.model
第二步:创建实体类 & Mapper接口
以客户模块为例,创建对应的实体类和Mapper接口:
public class Customer {
private Long id;
private String name;
private String phone;
private LocalDateTime createTime;
// getter/setter省略
}
@Mapper
public interface CustomerMapper {
Customer selectById(Long id);
List<Customer> selectAll();
void insert(Customer customer);
void update(Customer customer);
void deleteById(Long id);
}
这样我们就定义好了DAO接口。接下来是关键部分:Mapper XML文件的编写。
第三步:编写Mapper XML,搞定SQL和映射
这里要重点说明一下XML文件的写法:
<!-- src/main/resources/mapper/CustomerMapper.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.crm.mapper.CustomerMapper">
<resultMap id="BaseResultMap" type="Customer">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="phone" property="phone"/>
<result column="create_time" property="createTime"/>
</resultMap>
<select id="selectById" resultMap="BaseResultMap">
SELECT * FROM customer WHERE id = #{id}
</select>
<select id="selectAll" resultMap="BaseResultMap">
SELECT * FROM customer
</select>
<insert id="insert">
INSERT INTO customer (name, phone, create_time)
VALUES (#{name}, #{phone}, #{createTime})
</insert>
<!-- 其他update/delete方法省略 -->
</mapper>
第四步:多表关联映射技巧(真正的难点来了!)
还记得之前说的订单详情页面需要展示客户信息吗?我们希望一次SQL就能查出订单+客户对象的嵌套关系。
假设有一个DTO类:
public class OrderDetailDTO {
private OrderInfo order;
private Customer customer;
// setter/getter
}
那我们可以这样写XML:
<resultMap id="OrderDetailResultMap" type="OrderDetailDTO">
<association property="order" javaType="OrderInfo">
<id column="order_id" property="id"/>
<result column="product_code" property="productCode"/>
<result column="amount" property="amount"/>
</association>
<association property="customer" javaType="Customer">
<id column="customer_id" property="id"/>
<result column="name" property="name"/>
<result column="phone" property="phone"/>
</association>
</resultMap>
<select id="getOrderDetailById" resultMap="OrderDetailResultMap">
SELECT
o.id AS order_id,
o.product_code,
o.amount,
c.id AS customer_id,
c.name,
c.phone
FROM order_info o
JOIN customer c ON o.customer_id = c.id
WHERE o.id = #{orderId}
</select>
这就是MyBatis最强大的地方之一:支持复杂对象图的自动映射。只需正确配置resultMap,就可以轻松实现嵌套对象的填充。
第五步:解决性能问题 —— 配置连接池
之前提到的生产环境数据库连接压力大的问题,其实是因为默认情况下Spring Boot使用的是SimpleDriverDataSource,它没有连接池,每次请求都新建连接!
我们后来换成了HikariCP:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
auto-commit: true
效果非常显著,QPS提升了一倍,而且数据库连接资源得到了有效利用。
效果总结:重构后的收益
完成整个重构之后,我们得到了以下几个方面的提升:
- 代码量大幅减少:原本每个模块几十行JDBC代码现在都被浓缩成Mapper接口和XML文件。
- 维护成本降低:SQL集中管理,便于排查问题和优化执行效率。
- 系统性能提升:引入连接池后,响应速度变快,数据库负载下降。
- 可扩展性强:如果将来需要对接新的数据库类型,改动成本大大降低。
此外,还有一个附加的好处是——开发效率提升了。我们可以在本地开启MyBatis的日志打印,方便调试:
logging:
level:
com.example.crm.mapper: debug
这样就会输出所有执行的SQL语句和参数值,非常实用。
经验分享:我的建议与注意事项
1. 别怕写SQL,学会控制自由度
很多人觉得MyBatis不如Hibernate自动化,但实际上,手动写SQL才是掌控性能的关键。尤其是在面对大数据量场景时,一句SQL写得好坏直接影响系统表现。
比如我曾遇到一个慢查询,用MyBatis动态生成分页SQL比直接用PageHelper更高效。
2. 规范命名,避免混淆
建议遵循如下命名习惯:
| 类型 | 规范示例 |
|---|---|
| 实体类 | Customer, OrderInfo |
| Mapper接口 | CustomerMapper, OrderMapper |
| XML文件 | CustomerMapper.xml |
| 表名 | customer, order_info |
3. 善用注解 vs XML?看需求定
对于简单CRUD,可以考虑用注解方式:
@Mapper
public interface CustomerMapper {
@Select("SELECT * FROM customer WHERE id=#{id}")
Customer selectById(Long id);
@Insert("INSERT INTO customer(name, phone, create_time) VALUES(#{name}, #{phone}, #{createTime})")
void insert(Customer customer);
}
但对于复杂的查询、特别是涉及多表联合、条件拼接等场景,还是建议使用XML,便于管理和阅读。
4. 动态SQL是个好东西
比如根据条件搜索客户,就可以这么写:
<select id="searchCustomers" resultType="Customer">
SELECT *
FROM customer
<where>
<if test="name != null and name.trim() != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="phone != null and phone.trim() != ''">
AND phone = #{phone}
</if>
</where>
</select>
这比在Java代码里拼接字符串清爽太多了,而且不容易出错。
5. 不要忽视缓存机制
MyBatis本身支持一级缓存(Session级别)和二级缓存(Mapper级别),适当使用可以进一步提升性能。
虽然我们这次项目没用上,但在某些读多写少的场景下(比如字典表、静态数据),开启二级缓存非常有用。
6. 日志监控不能少
生产环境中一定要配合日志系统(如ELK)记录慢查询、错误SQL等,及时预警。
写在最后:一点感悟
回想自己从JDBC一路“打怪升级”到MyBatis的过程,真的是痛并快乐着。每一次踩坑都是成长的机会,而MyBatis正是那个既灵活又强大的工具,它不会替你决定怎么做,而是给你足够的自由去按自己的想法操作数据库。
如果你正在学习MyBatis或者准备用在新项目中,希望这篇实战文章对你有所帮助。记住一句话:
框架只是工具,理解原理才能游刃有余。
祝你在MyBatis的学习路上越走越远,写出高性能、易维护的好代码!
如果你喜欢这类实战型技术分享,欢迎点赞、转发或留言交流~

评论 0