初步了解Mybatis
MyBatis 入门项目文档
一、项目概述
这是一个 Spring Boot 3 + MyBatis 3 的入门学习项目,演示如何用最简单的方式操作 SQL Server 数据库。
具体包括:MyBatis 是什么、怎么连接数据库、怎么做增删改查(CRUD)、MyBatis 帮你省掉了什么工作。
二、技术栈介绍
| 技术 | 版本 | 它的作用(新手友好解释) |
|---|---|---|
| Spring Boot | 3.2.0 | Java 项目的"管家",帮你管理依赖、启动应用、注入对象 |
| MyBatis | 3.0.3 | 一个持久层框架,让你用"声明接口"的方式操作数据库,不用手写 JDBC |
| SQL Server | --- | 微软的关系型数据库 |
| Maven | --- | 包管理工具,自动下载依赖的 jar 包 |
| Java | 21 | 编程语言 |
| JUnit 5 | --- | 单元测试框架(随 spring-boot-starter-test 引入) |
三、项目文件结构
pj4/
├── pom.xml # Maven 配置:声明依赖
├── init.sql # 数据库初始化脚本
├── mvnw / mvnw.cmd # Maven Wrapper(无需本地装 Maven)
├── src/
│ ├── main/
│ │ ├── java/com/example/pj4/
│ │ │ ├── Pj4Application.java # Spring Boot 启动入口
│ │ │ ├── pojo/
│ │ │ │ └── User.java # 实体类(POJO):一张表对应一个类
│ │ │ └── mapper/
│ │ │ └── UserMapper.java # MyBatis Mapper 接口:定义数据库操作
│ │ └── resources/
│ │ └── application.properties # 数据库连接配置
│ └── test/java/com/example/pj4/
│ └── Pj4ApplicationTests.java # 单元测试
└── target/ # 编译输出目录(自动生成,不用管)
四、各文件详解
4.1 pom.xml(Maven 依赖配置)
xml
<dependencies>
<!-- MyBatis 起步依赖:引入后就能用 MyBatis 的全部功能 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- SQL Server 驱动:Java 连接 SQL Server 必须的驱动包 -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot 测试依赖:提供 JUnit、Spring Test 等 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
说明 :mybatis-spring-boot-starter 是核心------它内部已经包含了 MyBatis 核心包和 MyBatis-Spring 整合包,引入这一个依赖就能用 MyBatis。
4.2 init.sql(数据库初始化脚本)
在 SQL Server 中执行这个脚本,它会自动创建数据库和测试数据:
sql
-- 创建数据库 mybatis
CREATE DATABASE [mybatis];
-- 切换到该数据库
USE [mybatis];
-- 创建 user 表(ID 自增)
CREATE TABLE [user] (
id INT IDENTITY(1,1) PRIMARY KEY, -- 自增主键,从 1 开始每次 +1
name NVARCHAR(100) NULL, -- 支持中文的字符串
age TINYINT NULL,
gender TINYINT NULL, -- 1=男, 2=女
phone NVARCHAR(11) NULL
);
-- 插入 6 条测试数据(《倚天屠龙记》角色)
INSERT INTO [user] (name, age, gender, phone) VALUES (N'白眉鹰王', 55, 1, '18800000000');
INSERT INTO [user] (name, age, gender, phone) VALUES (N'金毛狮王', 45, 1, '18800000001');
INSERT INTO [user] (name, age, gender, phone) VALUES (N'青翼蝠王', 38, 1, '18800000002');
INSERT INTO [user] (name, age, gender, phone) VALUES (N'紫衫龙王', 42, 2, '18800000003');
INSERT INTO [user] (name, age, gender, phone) VALUES (N'光明左使', 37, 1, '18800000004');
INSERT INTO [user] (name, age, gender, phone) VALUES (N'光明右使', 48, 1, '18800000005');
4.3 application.properties(数据库连接配置)
properties
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=mybatis;encrypt=false;trustServerCertificate=true
spring.datasource.username=sa
spring.datasource.password=qwe061003
每一行的含义:
| 配置项 | 含义 |
|---|---|
driver-class-name |
告诉 Java 用哪个数据库驱动(这里是 SQL Server 驱动) |
url |
数据库地址:localhost:1433 是本机 1433 端口,databaseName=mybatis 是数据库名。encrypt=false 和 trustServerCertificate=true 是开发环境关闭 SSL 加密,生产环境不能这样写 |
username / password |
数据库登录账号密码 |
你不需要写任何获取连接的代码。Spring Boot 读到这些配置后会自动创建数据库连接池(默认 HikariCP),MyBatis 会从这个池里拿连接。
4.4 Pj4Application.java(启动类)
java
@SpringBootApplication // 标记这是 Spring Boot 应用
public class Pj4Application {
public static void main(String[] args) {
SpringApplication.run(Pj4Application.class, args); // 启动应用
}
}
@SpringBootApplication 是干什么的? 它是三个注解的"快捷方式":
@Configuration:允许定义 Bean(Spring 管理的对象)@EnableAutoConfiguration:自动配置------Spring Boot 读到 pom.xml 里引入了 MyBatis,就自动帮你配好 MyBatis@ComponentScan:自动扫描当前包及子包下的所有组件(包括你的@Mapper接口)
4.5 User.java(实体类 / POJO)
java
public class User {
private Integer id; // 数据库 int → Java Integer
private String name; // 数据库 nvarchar → Java String
private Short age; // 数据库 tinyint → Java Short
private Short gender;
private String phone;
// 无参构造方法(MyBatis 反射创建对象时需要)
// getter / setter(MyBatis 通过它们读写属性值)
// toString()(打印输出用)
}
重要概念 :数据库的"一行"对应 Java 的"一个 User 对象",数据库的"列"对应 User 对象的"属性"。这个类就叫 POJO(Plain Old Java Object),也叫实体类(Entity)。
4.6 UserMapper.java(MyBatis Mapper 接口)------ 核心重点
java
@Mapper // ① 告诉 MyBatis:这个接口我来接管
public interface UserMapper {
@Select("SELECT id, name, age, gender, phone FROM [user]")
List<User> list(); // ② 查询全部
@Select("SELECT id, name, age, gender, phone FROM [user] WHERE id = #{id}")
User getById(Integer id); // ③ 按 ID 查询
@Insert("INSERT INTO [user] (name, age, gender, phone) VALUES (#{name}, #{age}, #{gender}, #{phone})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(User user); // ④ 新增
@Update("UPDATE [user] SET name=#{name}, age=#{age}, gender=#{gender}, phone=#{phone} WHERE id=#{id}")
void update(User user); // ⑤ 更新
@Delete("DELETE FROM [user] WHERE id=#{id}")
void delete(Integer id); // ⑥ 删除
}
逐行详解
① @Mapper
告诉 Spring + MyBatis:"别看我只有一个接口声明,我会在运行时自动生成一个实现类来代理你。"
② 查询全部
@Select:里面写 SQL 语句- 返回类型
List<User>:MyBatis 自动把查出来的多行数据映射成User对象的列表 - 因为数据库的列名(
id,name,age,gender,phone)和User类的属性名完全一致,所以 MyBatis 会自动映射,不需要额外配置
③ 按 ID 查询
#{id}:这是 MyBatis 的参数占位符 ,相当于 JDBC 的?。MyBatis 会把方法参数Integer id的值填到这里- 安全 :
#{ }使用的是PreparedStatement参数绑定,不会有 SQL 注入风险
④ 新增
#{name},#{age}等:参数是一个User对象,MyBatis 会自动从对象里取getName()、getAge()等@Options(useGeneratedKeys = true, keyProperty = "id"):因为[user]表的id是自增列,插入时不需要提供 id。插入成功后,数据库生成的 id 值会自动回填 到传入的User对象的id属性上
⑤ 更新
- 根据
User对象的id找到对应行,把其他字段更新为对象中的值
⑥ 删除
- 根据
id删除对应行
MyBatis 注解速查表
| 注解 | 用途 | 对应 SQL |
|---|---|---|
@Select |
查询 | SELECT ... |
@Insert |
插入 | INSERT INTO ... |
@Update |
更新 | UPDATE ... |
@Delete |
删除 | DELETE FROM ... |
@Options |
附加选项(如主键回填) | --- |
#{} vs ${}(重要!)
| 写法 | 底层原理 | 安全性 | 使用场景 |
|---|---|---|---|
#{xxx} |
用 PreparedStatement 的 ? 占位符 |
安全,防 SQL 注入 | 始终优先使用 |
${xxx} |
直接拼字符串到 SQL 里 | 不安全,可能被注入 | 只在需要动态表名/列名时用 |
MyBatis 是怎么帮你的?(对比 JDBC)
你写的代码: MyBatis 帮你做的:
───────────────────────────────────────────────────
import org.apache.ibatis. → 不需要 import JDBC 任何东西
annotations.*;
@Mapper → 不需要 Class.forName 加载驱动
public interface UserMapper { → 不需要 DriverManager.getConnection
→ 不需要 try-finally 关闭连接
@Select("SELECT ...") → 不需要 PreparedStatement
List<User> list(); → 不需要 ResultSet
→ 不需要 while(rs.next())
} → 不需要 rs.getInt("id") ...
→ MyBatis 通过"反射"调用 setId/getName
自动完成列→属性的映射
一句话:你只需定义一个接口、写一个 SQL 注解,MyBatis 自动生成实现类,帮你完成从"接口调用"到"返回 Java 对象"的全过程。
4.7 Pj4ApplicationTests.java(单元测试)
java
@SpringBootTest // 启动完整的 Spring 上下文,会连接真实数据库
@Transactional // 每个测试方法执行后自动回滚,数据库不留痕迹
public class Pj4ApplicationTests {
@Autowired
private UserMapper userMapper; // Spring 自动注入 MyBatis 生成的代理对象
@Test
public void testList() { ... } // 测试查询全部
@Test
public void testGetById() { ... } // 测试按 ID 查询
@Test
public void testInsert() { ... } // 测试新增(看 id 是否回填)
@Test
public void testUpdate() { ... } // 测试更新(看修改是否生效)
@Test
public void testDelete() { ... } // 测试删除(看总数是否减少)
}
@Autowired 干了什么?
UserMapper 明明是个接口(没有实现类),但 MyBatis 在启动时:
- 用**动态代理(JDK Proxy)**生成了一个代理对象
- 代理对象里封装了 "根据注解拿 SQL → 获取连接 → 执行 → 映射结果" 的全套逻辑
- Spring 把这个代理对象注入到
userMapper变量里 - 你调用
userMapper.list(),实际调用的是代理对象
@Transactional 的作用 :
每个测试方法执行时会开启一个数据库事务,方法结束后自动回滚。所以测试中的新增/更新/删除不会真正修改数据库数据。
五、运行结果
执行 ./mvnw test 后的输出:
text
=== 查询全部 ===
User{id=1, name='白眉鹰王', age=55, gender=1, phone='18800000000'}
User{id=2, name='金毛狮王', age=45, gender=1, phone='18800000001'}
User{id=3, name='青翼蝠王', age=38, gender=1, phone='18800000002'}
User{id=4, name='紫衫龙王', age=42, gender=2, phone='18800000003'}
User{id=5, name='光明左使', age=37, gender=1, phone='18800000004'}
User{id=6, name='光明右使', age=48, gender=1, phone='18800000005'}
=== 按ID查询 ===
User{id=1, name='白眉鹰王', age=55, gender=1, phone='18800000000'}
=== 新增用户 ===
插入后返回的ID: 8 ← 注意:ID 被自动回填了!
User{id=8, name='张无忌', age=22, gender=1, phone='19900000000'}
=== 更新前 ===
User{id=1, name='白眉鹰王', age=55, gender=1, phone='18800000000'}
=== 更新后 ===
User{id=1, name='殷天正', age=60, gender=1, phone='18800000000'} ← 名字和年龄均已更新
=== 删除前,总数: 7 ===
=== 删除后,总数: 6 ===
text
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
六、数据流转全景图
┌──────────────────────────────────────────────────────────────────┐
│ 测试代码 │
│ userMapper.list() ← 你只写了一行方法调用 │
└─────────────────────┬────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ MyBatis 动态代理对象(框架自动生成) │
│ 1. 从 @Select 拿到 SQL: "SELECT id,name,age,gender,phone FROM │
│ [user]" │
│ 2. 从连接池拿一个 Connection(HikariCP 管理) │
│ 3. 创建 PreparedStatement,填入 #{参数} 的值 │
│ 4. 执行 executeQuery() │
│ 5. 遍历 ResultSet,每行创建一个 User 对象 │
│ 6. 列名 "id" → 调用 user.setId(),列名 "name" → user.setName()... │
│ 7. 归还 Connection 到连接池 │
│ 8. 返回 List<User> │
└─────────────────────┬────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ SQL Server 数据库 │
│ [user] 表 → 6行数据 → 通过网络返回给 Java │
└──────────────────────────────────────────────────────────────────┘
你只需关心 写 SQL 注解 和 调用方法 两个动作,中间所有 JDBC 的脏活累活 MyBatis 全部代劳。
七、常见问题
Q1:@Mapper 和 @Repository 有什么区别?
@Mapper 是 MyBatis 专属注解,告诉 MyBatis 为这个接口生成代理实现。@Repository 是 Spring 的通用注解,MyBatis 不识别它。
Q2:如果数据库列名和 Java 属性名不一致怎么办?
比如数据库叫 user_name,Java 叫 userName。两种解决方式:
- 在
application.properties中设置mybatis.configuration.map-underscore-to-camel-case=true(自动下划线转驼峰) - 在 SQL 中取别名:
SELECT user_name AS userName FROM [user]
Q3:注解方式和 XML 方式用哪个?
- 注解:适合简单 SQL,代码紧凑,本项目用的就是这种方式
- XML:适合复杂 SQL(多表联合、动态条件),SQL 与 Java 代码分离
- 两种方式可以混用
Q4:我怎么知道 MyBatis 真的执行了哪些 SQL?
在 application.properties 中加一行:
properties
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
这样 MyBatis 执行的每条 SQL 和参数都会打印在控制台。
