一、引言:为什么 MyBatis 是你的 "入门首选"?
作为 Java 开发者,你是不是曾被 JDBC 的繁琐代码劝退 ------ 加载驱动、创建连接、处理 ResultSet,一堆模板代码占用大量精力?又或者觉得 Hibernate 的 "全自动" 太笨重,想改个 SQL 都要绕半天?
MyBatis 恰好解决了这些痛点:它是轻量级持久层框架,既能简化 JDBC 的重复工作,又保留了 SQL 的灵活性,不需要像 Hibernate 那样学习复杂的 ORM 映射规则。对于初级开发者来说,掌握 MyBatis 就能快速解决项目中 90% 的数据库操作需求,比如用户注册、订单查询、数据修改等基础 CRUD 场景。
二、MyBatis 基础认知:3 个核心概念先理清
在动手前,我们先搞懂 MyBatis 的 "骨架"------3 个核心组件 + 1 个核心流程。
1. MyBatis 的核心优势

2. MyBatis 的 3 个核心组件
首先看下MyBatis 核心组件交互流程图:


具体解释如下:
SqlSessionFactory:它是 "工厂类",一生只创建一次(项目启动时)。作用是生成SqlSession,就像仓库只负责给厨师提供食材,不直接做饭。
SqlSession:它是 "数据库操作的载体",每次数据库交互都需要一个SqlSession(用完要关闭)。你可以把它理解为 "临时厨师",拿到食材(SqlSessionFactory提供的配置)后,按菜谱(Mapper)做饭(执行 SQL)。
Mapper 接口:它是 "方法定义接口",没有实现类(MyBatis 会动态生成代理对象)。比如你定义User selectUserById(Integer id),MyBatis 会自动帮你绑定到 XML 里的select标签,不用自己写实现代码。
3. MyBatis 的核心流程(一句话总结)
项目启动时:读取配置文件 → 创建SqlSessionFactory
每次数据库操作时:SqlSessionFactory → 创建SqlSession → 通过Mapper接口执行 SQL → 关闭SqlSession。
三、环境搭建:3 步走,复制代码就能成
环境搭建是入门的 "第一道坎",这里我用「SpringBoot+Maven」组合(最主流的企业级配置),每一步都给完整代码和注释,跟着做就行。
1. 第一步:引入 Maven 依赖(pom.xml)
打开你的 SpringBoot 项目,在pom.xml中添加 3 个核心依赖:MyBatis 核心包、MySQL 驱动、SpringBoot 整合 MyBatis 的 starter(帮你简化配置)。
java
<!-- 1. SpringBoot整合MyBatis的starter(核心依赖) -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version> <!-- 稳定版本,可直接用 -->
</dependency>
<!-- 2. MySQL驱动(连接数据库用) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- 3. JUnit(测试用,后面写CRUD测试会用到) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
注意:如果你的项目是纯 JavaSE(没有 SpringBoot),需要额外引入mybatis核心包和druid连接池,但企业中几乎不用这种方式,所以优先推荐 SpringBoot 方案。
2. 第二步:配置 MyBatis 核心参数(application.yml)
SpringBoot 项目中,我们不用写传统的mybatis-config.xml(太繁琐),直接在src/main/resources/application.yml中配置即可,关键参数都有注释:
java
# 数据库连接配置(必须正确,否则连不上数据库)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL8+必须用cj包下的驱动
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
# 说明:localhost是你的数据库地址,3306是端口,mybatis_demo是数据库名(需要自己创建)
username: root # 你的数据库用户名(默认是root)
password: 123456 # 你的数据库密码(自己设置的)
# MyBatis配置(告诉MyBatis去哪找映射文件、怎么给实体类起别名)
mybatis:
# 1. 映射文件(XXXMapper.xml)的位置:这里是放在resources/mapper目录下
mapper-locations: classpath:mapper/*.xml
# 2. 实体类的别名配置:指定实体类所在包,后续在XML中不用写全类名
type-aliases-package: com.example.mybatisdemo.entity
# 3. 日志配置:打印执行的SQL语句(方便调试,初级开发者必开)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. 第三步:创建数据库表(给你现成的 SQL)
打开 MySQL 客户端(如 Navicat、DataGrip),先创建数据库mybatis_demo,再执行下面的 SQL 创建user表(用户表是最常用的测试场景):
java
-- 1. 创建数据库(如果已存在则跳过)
CREATE DATABASE IF NOT EXISTS mybatis_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 2. 使用数据库
USE mybatis_demo;
-- 3. 创建user表
CREATE TABLE IF NOT EXISTS `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID(自增主键)',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`age` INT(3) DEFAULT NULL COMMENT '用户年龄',
`email` VARCHAR(100) DEFAULT NULL COMMENT '用户邮箱',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
执行完后,你可以在数据库客户端看到user表的结构,后续我们的 CRUD 操作都围绕这张表展开。
四、快速实现 CRUD:4 个步骤,代码可直接复制
按 "实体类→Mapper 接口→映射文件→测试" 的顺序,实现 "查询单个用户、查询所有用户、新增用户、修改用户、删除用户"5 个基础操作,每个步骤都给完整代码 + 详细注释。
1. 步骤 1:创建实体类(User.java)
实体类是 "数据库表的 Java 映射",每个字段对应表中的一列,遵循JavaBean 规范(有 getter/setter 方法,无参构造器)。
在com.example.mybatisdemo.entity包下创建User.java:
java
package com.example.mybatisdemo.entity;
import lombok.Data;
import java.util.Date;
// Lombok的@Data注解:自动生成getter/setter、toString、equals等方法(不用手动写!)
// 注意:如果没引入Lombok,需要手动写getter/setter(文末有补充方案)
@Data
public class User {
// 对应user表的id字段(自增主键)
private Integer id;
// 对应username字段(用户名)
private String username;
// 对应age字段(年龄)
private Integer age;
// 对应email字段(邮箱)
private String email;
// 对应create_time字段(创建时间)
private Date createTime;
}
补充:如果没引入 Lombok,需手动添加 getter/setter。比如:
java
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
// 其他字段同理...
2. 步骤 2:创建 Mapper 接口(UserMapper.java)
Mapper 接口是 "数据库操作的方法定义",相当于 "菜谱",只写方法名和参数,不用写实现(MyBatis 会动态生成代理对象)。
在com.example.mybatisdemo.mapper包下创建UserMapper.java:
java
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
// @Mapper注解:告诉MyBatis这是一个Mapper接口,会自动扫描并生成代理对象
@Mapper
public interface UserMapper {
// 1. 根据ID查询用户(单条查询)
// 参数:Integer id(用户ID),返回值:User(查询到的用户对象)
User selectUserById(Integer id);
// 2. 查询所有用户(多条查询)
// 返回值:List<User>(用户列表)
List<User> selectAllUsers();
// 3. 新增用户
// 参数:User user(要新增的用户对象,包含username、age、email)
// 返回值:int(受影响的行数,成功则返回1)
int insertUser(User user);
// 4. 修改用户(只修改非空字段)
// 参数:User user(要修改的用户,需包含id,其他字段非空则修改)
int updateUser(User user);
// 5. 根据ID删除用户
// 参数:Integer id(要删除的用户ID)
int deleteUserById(Integer id);
}
关键注意点:
方法名建议遵循 "selectXxx、insertXxx、updateXxx、deleteXxx" 的规范,便于后续维护;
@Mapper 注解必须加,否则 Spring 无法扫描到这个接口,会报 "找不到 Bean" 的错误。
3. 步骤 3:创建映射文件(UserMapper.xml)
映射文件是 "方法与 SQL 的绑定桥梁",我们在这写具体的 SQL 语句,然后通过namespace关联到 Mapper 接口。
在src/main/resources/mapper目录下创建UserMapper.xml:
java
<?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">
<!-- 关键:namespace必须等于Mapper接口的全类名(包名+类名),否则绑定失败 -->
<mapper namespace="com.example.mybatisdemo.mapper.UserMapper">
<!-- 1. 根据ID查询用户:对应UserMapper.selectUserById方法 -->
<!-- id:必须和Mapper接口的方法名一致;resultType:返回值类型(因为配置了别名,直接写User即可) -->
<select id="selectUserById" resultType="User">
-- SQL语句:#{id}是参数占位符(相当于JDBC的?),MyBatis会自动把方法参数id的值传进来
SELECT id, username, age, email, create_time AS createTime
FROM user
WHERE id = #{id}
</select>
<!-- 2. 查询所有用户:对应UserMapper.selectAllUsers方法 -->
<select id="selectAllUsers" resultType="User">
-- 注意:数据库字段create_time是下划线命名,实体类是驼峰命名createTime,MyBatis会自动映射(无需额外配置)
SELECT id, username, age, email, create_time
FROM user
</select>
<!-- 3. 新增用户:对应UserMapper.insertUser方法 -->
<!-- useGeneratedKeys="true":开启自增主键回显;keyProperty="id":把自增的id赋值给User对象的id字段 -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, age, email)
VALUES (#{username}, #{age}, #{email})
-- #{username}:对应User对象的username属性(MyBatis会自动调用getUsername()获取值)
</insert>
<!-- 4. 修改用户:对应UserMapper.updateUser方法(动态SQL初步:只修改非空字段) -->
<!-- <set>标签:自动去除多余的逗号,避免"update user set username='xxx', where id=1"的语法错误 -->
<update id="updateUser">
UPDATE user
<set>
-- <if>标签:如果User对象的username非空,才拼接"username=#{username}"
<if test="username != null">username = #{username},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
<!-- 5. 删除用户:对应UserMapper.deleteUserById方法 -->
<delete id="deleteUserById">
DELETE FROM user
WHERE id = #{id}
</delete>
</mapper>
新手常见坑点:
- namespace 写错:比如少写一个包名,导致 "方法找不到";
- SQL 语句语法错误:比如多写逗号(标签已解决)、字段名和表名写错;
- 参数占位符用:{}::{}会直接拼接 SQL(有注入风险),初级阶段一律用#{}。
4. 步骤 4:编写测试类(验证 CRUD 是否生效)
我们用 SpringBoot 的测试类来执行 CRUD 方法,验证代码是否正确。在src/test/java/com/example/mybatisdemo/mapper下创建UserMapperTest.java:
java
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
// @SpringBootTest:启动Spring容器,才能注入UserMapper的代理对象
@SpringBootTest
public class UserMapperTest {
// @Autowired:自动注入MyBatis生成的UserMapper代理对象(不用自己new)
@Autowired
private UserMapper userMapper;
// 测试1:根据ID查询用户(先新增再查询,确保有数据)
@Test
public void testSelectUserById() {
// 第一步:先新增一个用户(避免查询不到数据)
User insertUser = new User();
insertUser.setUsername("张三");
insertUser.setAge(25);
insertUser.setEmail("zhangsan@example.com");
userMapper.insertUser(insertUser); // 执行新增
System.out.println("新增的用户ID:" + insertUser.getId()); // 自增ID会回显到对象中
// 第二步:根据新增的ID查询用户
User queryUser = userMapper.selectUserById(insertUser.getId());
System.out.println("查询到的用户:" + queryUser);
// 预期结果:控制台打印用户信息,如User(id=1, username=张三, age=25, ...)
}
// 测试2:查询所有用户
@Test
public void testSelectAllUsers() {
List<User> userList = userMapper.selectAllUsers();
// 遍历打印所有用户
for (User user : userList) {
System.out.println("用户:" + user);
}
// 预期结果:如果之前新增过数据,会打印所有用户列表
}
// 测试3:修改用户(只修改年龄)
@Test
public void testUpdateUser() {
// 第一步:先获取一个已存在的用户ID(这里用1,实际可根据查询结果调整)
Integer existId = 1;
User updateUser = new User();
updateUser.setId(existId); // 必须传ID,否则不知道改哪个用户
updateUser.setAge(26); // 只修改年龄,其他字段留空(不会被修改)
// 第二步:执行修改
int affectedRows = userMapper.updateUser(updateUser);
System.out.println("受影响的行数:" + affectedRows); // 成功则返回1
// 第三步:验证修改结果
User updatedUser = userMapper.selectUserById(existId);
System.out.println("修改后的年龄:" + updatedUser.getAge()); // 预期是26
}
// 测试4:删除用户
@Test
public void testDeleteUserById() {
// 第一步:指定要删除的用户ID(这里用1,实际可根据新增结果调整)
Integer deleteId = 1;
// 第二步:执行删除
int affectedRows = userMapper.deleteUserById(deleteId);
System.out.println("受影响的行数:" + affectedRows); // 成功则返回1
// 第三步:验证删除结果(查询不到该用户)
User deletedUser = userMapper.selectUserById(deleteId);
System.out.println("删除后查询结果:" + deletedUser); // 预期是null
}
}
五、实战问题排查:3 个新手高频错误及解决方法
即使跟着步骤做,也可能遇到问题。这里整理了 3 个初级开发者最常踩的坑,帮你快速定位解决:
1. 错误 1:"Could not find resource mapper/UserMapper.xml"(找不到映射文件)
原因:application.yml 中配置的mapper-locations路径和实际文件路径不匹配,比如你把 XML 放在了resources/mybatis目录,但配置写的是classpath:mapper/*.xml。
解决方法:
方案 1:把 XML 文件移到resources/mapper目录(和配置一致);
方案 2:修改配置为实际路径,比如mapper-locations: classpath:mybatis/*.xml。
2. 错误 2:"Namespace does not match the mapper interface"(namespace 不匹配)
原因:UserMapper.xml 的namespace不是 UserMapper 接口的全类名,比如少写了包名com.example.mybatisdemo。
解决方法:
打开 UserMapper.java,复制类的全路径(右键→Copy→Copy Reference),粘贴到namespace中,确保完全一致:
java
namespace="com.example.mybatisdemo.mapper.UserMapper"
3. 错误 3:"Unknown column 'createTime' in 'field list'"(字段名不匹配)
原因:SQL 中查询的字段名和数据库表字段不一致,比如你写了SELECT createTime,但数据库表字段是create_time。
解决方法:
方案 1:用AS给字段起别名,比如create_time AS createTime(推荐,清晰);
方案 2:开启 MyBatis 的 "下划线转驼峰" 配置,在 application.yml 中添加:
java
mybatis:
configuration:
map-underscore-to-camel-case: true # 自动把下划线字段转驼峰命名