第 1 章 [奠基] 项目启动与首次数据库交互

第 1 章 [奠基] 项目启动与首次数据库交互

摘要: 本章节将带领您MyBatis 的初学者——踏上持久层框架的学习之旅。我们将首先探讨直接使用 JDBC 的痛点,从而理解 MyBatis 存在的 核心价值。随后,您将亲手从零开始,利用 LombokSLF4JAssertJ 等现代化工具库,高效地完成一个 MyBatis 项目的搭建,并最终执行您的第一个数据库查询操作。完成本章后,您将对 MyBatis 的基本工作流程和现代化开发实践有一个扎实且清晰的认识。


1.1. [背景] 为什么选择 MyBatis?

在我们直接投入 MyBatis 的学习之前,有必要先回顾一下它所要解决的问题。在没有持久层框架的时代,我们通常使用原生的 JDBC (Java Database Connectivity) 来与数据库交互。

原生 JDBC 开发的痛点

虽然 JDBC 是 Java 连接数据库的基石,但直接使用它会导致大量模板化的、重复的、且容易出错的代码。

1. 硬编码的 SQL 与繁琐的参数设置

SQL 语句散落在 Java 代码的各个角落,一旦需要修改,就必须深入业务代码,违反了软件设计的 开闭原则。同时,为 PreparedStatement 的占位符 ? 挨个赋值的过程也极其繁琐。

1
2
3
4
5
6
7
8
9
// 示例:原生 JDBC 的插入操作
// SQL 语句写死在 Java 程序中
String sql = "insert into t_user(username, password) values(?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
// 繁琐的赋值过程
ps.setString(1, "zhangsan");
ps.setString(2, "123456");
// 执行 SQL
int count = ps.executeUpdate();

2. 手动封装的结果集

ResultSet 查询结果手动转换(封装)为 Java 对象(POJO)的过程,充满了大量的 get/set 操作。这部分代码不仅重复度高,而且当数据库表字段增减时,维护起来非常痛苦。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 示例:原生 JDBC 的结果集封装
List<User> userList = new ArrayList<>();
while(rs.next()){
// 手动获取每一列数据
String username = rs.getString("username");
String password = rs.getString("password");
// 手动创建对象并赋值
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 添加到集合
userList.add(user);
}

MyBatis 的出现,正是为了将开发者从这些枯燥的工作中解放出来,让我们能更专注于 SQL 本身,而不是与 JDBC API 的繁琐搏斗。


1.2. [实践] 环境准备与项目搭建

现在,我们开始搭建“银行系统”项目。第一步是建立数据库表和配置项目环境。

1. 数据库初始化

首先,请连接到您的 MySQL 数据库,并执行以下 SQL 脚本来创建我们的数据库和表,并插入初始数据。

1
2
3
4
5
6
7
8
9
10
11
12
CREATE DATABASE IF NOT EXISTS `bank_db` DEFAULT CHARACTER SET utf8mb4;
USE `bank_db`;

CREATE TABLE `t_account` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`actno` varchar(255) DEFAULT NULL COMMENT '账号',
`balance` double(10,2) DEFAULT NULL COMMENT '余额',
PRIMARY KEY (`id`)
);

INSERT INTO `t_account` (id, actno, balance) VALUES (1, 'act-001', 50000.00);
INSERT INTO `t_account` (id, actno, balance) VALUES (2, 'act-002', 10000.00);

2. Maven 项目配置

我们创建一个标准的 Maven 项目。关键在于配置 pom.xml 文件,它像一个“项目说明书”,告诉 Maven 我们项目需要哪些“积木”——也就是依赖库。这包括 MyBatis 自身、数据库驱动、我们用来简化代码的 Lombok,以及强大的日志和测试工具。

文件路径: [您的项目根目录]/pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mybatis-bank-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

1.3. [配置] 核心配置文件与工具类

MyBatis 的工作离不开两个核心的 XML 文件:一个用于全局配置,另一个用于编写 SQL。

1. 全局配置文件 mybatis-config.xml

这个文件是 MyBatis 的“大本营”或“交通枢纽”。我们在这里告诉 MyBatis 三件关键事情:① 如何连接数据库 (dataSource),② 事务如何管理 (transactionManager),以及 ③ 去哪里寻找我们编写的 SQL (mappers)。

文件路径: src/main/resources/mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/bank_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/AccountMapper.xml"/>
</mappers>
</configuration>

2. 工具类封装 SqlSessionUtil

为了避免每次都重复创建重量级的 SqlSessionFactory,我们遵循单例模式的最佳实践,将其封装成一个工具类。这个类在程序启动时就初始化好 SqlSessionFactory,并提供一个简单的静态方法来获取 SqlSession。我们还会用 @Slf4j 注解来轻松地集成日志功能。

文件路径: src/main/java/com/example/bank/utils/SqlSessionUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.bank.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

@Slf4j
public class SqlSessionUtil {

private static final SqlSessionFactory sqlSessionFactory;

static {
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
log.error("初始化 SqlSessionFactory 失败!", e);
throw new ExceptionInInitializerError(e);
}
}

public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
}

1.4. [实践] 第一个查询操作

万事俱备,我们来完成第一个查询需求:根据账号查询账户信息。

1. 创建 POJO (使用 Lombok)

首先,我们需要一个 Java 类来映射数据库中的 t_account 表,这个类通常被称为 POJO。借助 Lombok 的 @Data 注解,我们无需手动编写任何 getter/settertoString 方法,代码会非常整洁。

文件路径: src/main/java/com/example/bank/pojo/Account.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.bank.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private Integer id;
private String actno;
private Double balance;
}

2. 编写 Mapper 接口与 XML

MyBatis 最优雅的工作方式就是通过 Mapper 接口。我们只需要定义一个接口,并在对应的 XML 文件中编写 SQL。MyBatis 会在运行时为我们神奇地“实现”这个接口。请注意它们之间的“契约”:XML 中的 namespace 必须精确匹配接口的全路径名,<select> 标签的 id 必须精确匹配接口中的方法名。

文件路径 (接口): src/main/java/com/example/bank/mapper/AccountMapper.java

1
2
3
4
5
6
7
package com.example.bank.mapper;

import com.example.bank.pojo.Account;

public interface AccountMapper {
Account selectByActno(String actno);
}

文件路径 (XML): src/main/resources/mappers/AccountMapper.xml (注意,您需要手动创建 mappers 文件夹)

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.bank.mapper.AccountMapper">
<!-- namespace 属性指定了 Mapper 接口的完全限定名,用于 MyBatis 查找对应的 XML 配置文件。
id 属性用于唯一标识 SQL 语句,而 resultType 指定了查询结果应该映射到的 Java 类型。 -->
<select id="selectByActno" resultType="com.example.bank.pojo.Account">
select id, actno, balance from t_account where actno = #{actno}
</select>
</mapper>

这是最核心的一点:MyBatis 的 XML 配置文件定义了 SQL 语句和 Mapper 接口方法的对应关系,使用户可以以声明式的方式编写 SQL,而无需在 Java 代码中直接编写。

3. 编写并执行单元测试 (使用 AssertJ)

现在,是时候验证我们的成果了。我们将编写一个 JUnit 5 测试。在这个测试中,我们会使用 try-with-resources 语句来确保 SqlSession 能够被自动关闭,这是一种很好的资源管理习惯。

同时,我们会用 AssertJ 提供的流式断言来验证查询结果,这比传统的 if-elseSystem.out.println 可靠且易读得多。

文件路径: src/test/java/com/example/bank/test/AccountMapperTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.example.bank.test;

import com.example.bank.mapper.AccountMapper;
import com.example.bank.pojo.Account;
import com.example.bank.utils.SqlSessionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@Slf4j
public class AccountMapperTest {
@Test
public void testSelectByActno() {
try (SqlSession sqlSession = SqlSessionUtil.openSession()) {
// 1. 创建 AccountMapper 接口的代理对象
AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
// 2. 调用 selectByActno 方法
Account account = accountMapper.selectByActno("act-001");
// 3. 判断账号信息
assertThat(account).isNotNull();
assertThat(account.getActno()).isEqualTo("act-001");
assertThat(account.getBalance()).isEqualTo(50000.00);

log.info("查询成功: {}", account);
}
}
}

4. 配置日志并查看输出

最后,为了让程序“开口说话”,我们需要配置一个日志系统。下面的 logback.xml 配置将告诉程序:将所有来自我们 mapper 包的、级别为 TRACE 的详细日志(包括执行的 SQL 和参数)都打印到控制台。

文件路径: src/main/resources/logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<logger name="com.example.bank.mapper" level="TRACE" />

<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

运行测试后,您将在控制台看到类似下面的输出,标志着您的第一个 MyBatis 查询已成功执行!

1
2
3
4
5
6
15:07:03.394 [main] DEBUG c.e.b.m.AccountMapper.selectByActno - ==>  Preparing: select id, actno, balance from t_account where actno = ?
15:07:03.451 [main] DEBUG c.e.b.m.AccountMapper.selectByActno - ==> Parameters: act-001(String)
15:07:03.513 [main] TRACE c.e.b.m.AccountMapper.selectByActno - <== Columns: id, actno, balance
15:07:03.514 [main] TRACE c.e.b.m.AccountMapper.selectByActno - <== Row: 3, act-001, 50000.0
15:07:03.517 [main] DEBUG c.e.b.m.AccountMapper.selectByActno - <== Total: 1
15:07:03.586 [main] INFO c.e.bank.test.AccountMapperTest - 查询成功: Account(id=3, actno=act-001, balance=50000.0)

恭喜!您已经成功完成了 MyBatis 的入门第一步,并学会了如何结合现代化工具库来提升开发效率。接下来,我们将基于这个项目,逐步实现更复杂的业务功能。