第 1 章 [奠基] 项目启动与首次数据库交互
摘要: 本章节将带领您MyBatis 的初学者——踏上持久层框架的学习之旅。我们将首先探讨直接使用 JDBC 的痛点,从而理解 MyBatis 存在的 核心价值。随后,您将亲手从零开始,利用 Lombok、SLF4J、AssertJ 等现代化工具库,高效地完成一个 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
|
String sql = "insert into t_user(username, password) values(?,?)"; PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "zhangsan"); ps.setString(2, "123456");
int count = ps.executeUpdate();
|
2. 手动封装的结果集
将 ResultSet 查询结果手动转换(封装)为 Java 对象(POJO)的过程,充满了大量的 get/set 操作。这部分代码不仅重复度高,而且当数据库表字段增减时,维护起来非常痛苦。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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/setter 或 toString 方法,代码会非常整洁。
文件路径: 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">
<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-else 或 System.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()) { AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class); Account account = accountMapper.selectByActno("act-001"); 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 的入门第一步,并学会了如何结合现代化工具库来提升开发效率。接下来,我们将基于这个项目,逐步实现更复杂的业务功能。