MyBatis 基础入门与核心概念详解
引言
在现代企业级 Java 应用开发中,与数据库进行交互是不可或缺的一环。为了简化这一过程,各种持久层框架应运而生。在众多框架中,MyBatis 凭借其灵活性、高性能以及对 SQL 的高度可控性,赢得了众多开发者的青睐。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs (Plain Old Java Objects,普通 Java 对象)映射成数据库中的记录。
与传统的 ORM (Object-Relational Mapping) 框架(如 Hibernate、JPA)相比,MyBatis 并不是一个全自动的 ORM。它更专注于 SQL 映射,允许开发者编写原生的 SQL 语句。这种设计理念让开发者能够更好地控制 SQL 的执行,从而在性能调优方面拥有更大的空间,尤其适合那些需要处理复杂查询、遗留数据库结构或者追求极致性能的应用场景。
本文将带你深入了解 MyBatis 的基础知识,包括它的核心概念、工作原理、环境搭建以及最基本的 CRUD 操作。无论你是一个 MyBatis 初学者,还是希望巩固基础知识的开发者,相信本文都能为你提供有价值的参考。
第一部分:MyBatis 是什么?为何选择 MyBatis?
1.1 MyBatis 的定义与定位
MyBatis 是一个 SQL Mapper 框架,它将 Java 方法调用与 SQL 语句绑定起来,并负责将 SQL 执行的结果映射到 Java 对象上。它的核心思想是将 SQL 语句从 Java 代码中分离出来,通过 XML 或注解的方式进行管理。
MyBatis 介于传统的 JDBC 操作和全功能 ORM 之间。
- JDBC: 原始的数据库连接和操作方式,需要手动编写大量的样板代码(加载驱动、建立连接、创建 Statement/PreparedStatement、设置参数、执行 SQL、处理结果集、关闭资源)。代码繁琐、可读性差、维护困难。
- 全功能 ORM (如 Hibernate/JPA): 提供了对象与关系数据库之间的自动映射。开发者主要操作对象,框架负责生成 SQL。优点是开发效率高(对于简单的 CRUD),缺点是可能隐藏 SQL 细节,难以进行细粒度的性能调优,有时会遇到”对象-关系阻抗不匹配”的问题。
- MyBatis: 提供了 SQL 映射的功能,但 SQL 本身需要开发者编写。它封装了 JDBC 的底层细节,提供了参数映射和结果集映射的功能。它提供了比 JDBC 更高的开发效率,同时保留了对 SQL 的完全控制权。
因此,MyBatis 被定位为一个灵活的、专注于 SQL 映射的持久层框架。
1.2 为何选择 MyBatis?
选择 MyBatis 通常基于以下几点优势:
- 灵活性和对 SQL 的完全控制: 这是 MyBatis 最显著的特点。开发者可以编写任何复杂的 SQL 语句,包括存储过程、自定义函数等,这对于复杂的业务需求和性能优化至关重要。不需要担心框架生成低效的 SQL。
- 性能优越: 由于开发者直接控制 SQL,可以根据具体数据库和业务场景进行精细调优。MyBatis 自身的开销非常小,性能通常优于全功能 ORM。
- 易于学习和使用: 如果熟悉 SQL,学习 MyBatis 会非常快。它的概念相对简单,配置清晰明了。
- 与现有系统的集成: MyBatis 可以很容易地集成到各种 Java 应用程序中,无论是传统的 Servlet/JSP 应用,还是基于 Spring、Spring Boot 等框架的应用。它不需要强制依赖特定的应用服务器或环境。
- 可维护性高: 将 SQL 集中在 XML 或注解中管理,使得 SQL 与业务逻辑分离,提高了代码的可读性和可维护性。
- 强大的结果集映射: 支持复杂对象、一对一、一对多等多种结果集映射方式,能够轻松应对各种数据结构。
- 丰富的特性: 除了基本的映射,MyBatis 还提供了动态 SQL、缓存、延迟加载、插件机制等高级特性,能够满足更复杂的需求。
1.3 MyBatis 的适用场景
- 对性能要求较高,需要精细调优 SQL 的项目。
- 需要使用数据库特有功能(如特定函数、语法、存储过程)的项目。
- 遗留系统改造,需要兼容大量现有 SQL 的项目。
- 开发团队对 SQL 比较熟悉,希望保留 SQL 控制权的项目。
- 不希望引入全功能 ORM 带来的复杂性或学习曲线的项目。
第二部分:MyBatis 的核心组件与工作流程
理解 MyBatis 的核心组件及其协作方式,是掌握其基础的关键。
2.1 核心组件
MyBatis 的核心组件主要包括:
SqlSessionFactoryBuilder
: 构建器类,用于根据配置信息构建SqlSessionFactory
。它的作用是一次性的,一旦SqlSessionFactory
构建完成,SqlSessionFactoryBuilder
就可以被丢弃。SqlSessionFactory
:SqlSession
的工厂。它是重量级的对象,通常在应用程序启动时创建一次,并作为单例存在于整个应用程序生命周期中。它的职责是创建SqlSession
实例。SqlSession
: MyBatis 工作单元,是与数据库进行交互的最主要接口。它包含了所有执行 SQL、提交或回滚事务以及关闭连接的方法。SqlSession
是轻量级对象,不是线程安全的,每个线程都应该有自己的SqlSession
实例。它的生命周期应该与请求或操作的生命周期一致,使用完毕后必须关闭。- Mapper 接口 (Mapper Interface): 由开发者定义的接口,接口中的方法对应着要执行的 SQL 语句。MyBatis 会为这些接口生成代理实现类。通过调用接口方法,即可执行相应的 SQL。
- Mapper XML 文件 (Mapper XML): 定义 SQL 语句、参数映射和结果集映射的 XML 文件。每个
<mapper>
元素通常对应一个 Mapper 接口。也可以使用注解直接在 Mapper 接口上定义 SQL,但对于复杂 SQL 推荐使用 XML。 - MyBatis Configuration 文件 (
mybatis-config.xml
): 全局配置文件,包含数据库连接信息、事务管理器、类型别名、设置、插件以及注册 Mapper XML 文件或 Mapper 接口等配置。
2.2 工作流程
MyBatis 的基本工作流程如下:
- 加载配置: 通过
SqlSessionFactoryBuilder
读取mybatis-config.xml
配置文件。 - 构建
SqlSessionFactory
:SqlSessionFactoryBuilder
根据配置信息构建SqlSessionFactory
对象。通常一个应用程序只有一个SqlSessionFactory
。 - 创建
SqlSession
: 应用程序从SqlSessionFactory
获取SqlSession
实例。每个数据库操作请求或事务都应该拥有自己的SqlSession
。 - 获取 Mapper 实例: 通过
SqlSession
获取 Mapper 接口的代理实现类。 - 执行 SQL: 调用 Mapper 接口中的方法。MyBatis 会根据方法名、参数等找到对应的 SQL 语句(在 Mapper XML 或注解中定义)。
- 参数映射: 将方法参数映射到 SQL 语句中的占位符。
- 执行 JDBC 调用: MyBatis 调用 JDBC API 执行 SQL 语句。
- 结果集映射: 将 JDBC 返回的结果集映射到 Java 对象。
- 返回结果: 将映射后的 Java 对象返回给应用程序。
- 关闭
SqlSession
: 在操作完成后,必须关闭SqlSession
实例,释放数据库连接等资源。
(注:此处放置一个简化的 MyBatis 工作流程图示,例如:https://cdn.jsdelivr.net/gh/itcast/MyBatis/MyBatis-day01/img/mybatis_core_architecture.jpg 如果外部链接不稳定,可自行绘制或描述)
这个流程体现了 MyBatis 的核心思想:将配置和 SQL 外置,通过工厂模式管理连接资源,通过代理模式简化数据库操作。
第三部分:搭建 MyBatis 基础环境
本节将介绍如何搭建一个最简单的 MyBatis 项目环境,以便进行后续的示例演示。我们将使用 Maven 作为项目管理工具。
3.1 创建 Maven 项目
创建一个标准的 Maven Java 项目。
bash
mvn archetype:generate -DgroupId=com.example -DartifactId=mybatis-basic -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
3.2 添加 MyBatis 依赖
在项目的 pom.xml
文件中,添加 MyBatis 核心库的依赖。同时,为了连接数据库,还需要添加数据库驱动(例如 MySQL、H2 或 PostgreSQL)。这里以 MySQL 驱动为例。为了便于测试,我们再引入 JUnit 依赖。
“`xml
<groupId>com.example</groupId>
<artifactId>mybatis-basic</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- MyBatis 核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version> <!-- 使用最新稳定版本 -->
</dependency>
<!-- MySQL 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version> <!-- 使用与你的MySQL版本兼容的版本 -->
</dependency>
<!-- 日志框架依赖 (可选,但强烈推荐,方便查看SQL) -->
<!-- 这里使用slf4j + logback -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- JUnit 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
“`
请注意,依赖版本可能会更新,请根据实际情况选择合适的版本。
3.3 创建数据库和表
创建一个简单的数据库表用于测试。例如,创建一个 user
表:
“`sql
CREATE DATABASE mybatis_db;
USE mybatis_db;
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT
);
INSERT INTO user (name, age) VALUES (‘Alice’, 25);
INSERT INTO user (name, age) VALUES (‘Bob’, 30);
“`
3.4 创建 MyBatis 配置文件 (mybatis-config.xml
)
在 src/main/resources
目录下创建 mybatis-config.xml
文件。这个文件是 MyBatis 的全局配置文件。
“`xml
<!-- environments: 配置数据库环境 -->
<environments default="development">
<environment id="development">
<!-- transactionManager: 事务管理器类型 -->
<!-- JDBC: 使用 JDBC 的事务管理 -->
<!-- MANAGED: 使用容器的事务管理 (如 Spring) -->
<transactionManager type="JDBC"/>
<!-- dataSource: 数据源类型 -->
<!-- POOLED: 使用 MyBatis 内置的连接池 -->
<!-- UNPOOLED: 不使用连接池 -->
<!-- JNDI: 从 JNDI 获取数据源 (如在应用服务器中) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_db?serverTimezone=UTC&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="your_password"/> <!-- 替换为你的数据库密码 -->
</dataSource>
</environment>
</environments>
<!-- mappers: 注册 Mapper 文件或接口 -->
<mappers>
<!-- 通过 classpath 注册 Mapper XML 文件 -->
<!-- 注意: resource 路径是相对于 src/main/resources -->
<!-- <mapper resource="com/example/mapper/UserMapper.xml"/> -->
<!-- 或者通过包名注册 Mapper 接口 (推荐,需要 Mapper XML 文件名与接口名一致且在同一目录下) -->
<!-- <package name="com.example.mapper"/> -->
</mappers>
``
url
**重要提示:** 请将中的
localhost:3306、
username和
password` 替换为你实际的数据库连接信息。
3.5 创建实体类 (POJO)
创建一个 Java 类来对应数据库表中的记录。例如 User.java
:
“`java
package com.example.model;
public class User {
private Integer id;
private String name;
private Integer age;
// 无参构造函数
public User() {
}
// 带参构造函数 (可选)
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
// Getters and Setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
“`
3.6 创建 Mapper 接口
创建一个 Java 接口来定义对 User
表的操作方法。例如 UserMapper.java
:
“`java
package com.example.mapper;
import com.example.model.User;
import java.util.List;
public interface UserMapper {
User selectUserById(Integer id);
List<User> selectAllUsers();
void insertUser(User user);
void updateUser(User user);
void deleteUser(Integer id);
}
``
id` 对应。
注意:这里的接口方法名需要与 Mapper XML 文件中的 SQL 语句的
3.7 创建 Mapper XML 文件
在与 UserMapper.java
相同的包路径下(src/main/resources/com/example/mapper
)创建 UserMapper.xml
文件。文件名需要与接口名一致。
“`xml
<!-- selectUserById: 查询用户 by ID -->
<!-- id: 必须与 Mapper 接口中的方法名一致 -->
<!-- parameterType: 方法参数类型 -->
<!-- resultType: 返回结果类型 (如果返回单个对象) -->
<select id="selectUserById" parameterType="java.lang.Integer" resultType="com.example.model.User">
SELECT id, name, age
FROM user
WHERE id = #{id}
</select>
<!-- selectAllUsers: 查询所有用户 -->
<!-- resultType: 返回结果类型 (如果返回多个对象,则指定列表中元素的类型) -->
<select id="selectAllUsers" resultType="com.example.model.User">
SELECT id, name, age
FROM user
</select>
<!-- insertUser: 插入用户 -->
<!-- parameterType: 方法参数类型 -->
<!-- useGeneratedKeys="true" keyProperty="id": 获取数据库自动生成的主键并设置到 user 对象的 id 属性 -->
<insert id="insertUser" parameterType="com.example.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age)
VALUES (#{name}, #{age})
</insert>
<!-- updateUser: 更新用户 -->
<update id="updateUser" parameterType="com.example.model.User">
UPDATE user
SET name = #{name}, age = #{age}
WHERE id = #{id}
</update>
<!-- deleteUser: 删除用户 -->
<delete id="deleteUser" parameterType="java.lang.Integer">
DELETE FROM user
WHERE id = #{id}
</delete>
``
**注意:** 在
mybatis-config.xml中的
部分,如果你使用了
的方式注册 Mapper,那么
UserMapper.xml文件必须与
UserMapper.java在同一个包下,并且文件名需要一致。如果你使用
的方式,则只需要 resource 路径正确即可。推荐使用
package方式,更简洁。请确保在
mybatis-config.xml` 中配置了正确的 Mapper 注册方式。
3.8 添加日志配置 (logback.xml)
在 src/main/resources
目录下创建 logback.xml
文件,以便查看 MyBatis 执行的 SQL 语句。
“`xml
<!-- MyBatis logger -->
<!-- 设置 MyBatis 的日志级别为 DEBUG,可以打印出执行的SQL -->
<logger name="com.example.mapper" level="DEBUG"/>
<logger name="org.apache.ibatis" level="DEBUG"/>
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
``
com.example.mapper
这里配置了将日志输出到控制台,并将包下的日志以及
org.apache.ibatis` 包下的日志级别设置为 DEBUG,这样就能看到 MyBatis 打印出的 SQL 语句和参数。
第四部分:MyBatis 基本 CRUD 操作详解
有了上述环境,我们就可以编写代码来执行数据库操作了。
4.1 获取 SqlSessionFactory
加载配置文件并构建 SqlSessionFactory
。由于 SqlSessionFactory
是重量级且线程安全的,通常只需要构建一次。
“`java
package com.example;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 指定核心配置文件的路径
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建 SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
System.out.println("SqlSessionFactory initialized successfully.");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Error initializing SqlSessionFactory.", e);
}
}
// 提供获取 SqlSessionFactory 的静态方法
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
``
SqlSessionFactory`。
将这段代码放在一个工具类中,方便在任何地方获取
4.2 执行 Select 操作 (通过 Mapper 接口)
获取 SqlSession
,然后获取 Mapper 实例,调用方法执行查询。
“`java
package com.example.test;
import com.example.MyBatisUtil;
import com.example.mapper.UserMapper;
import com.example.model.User;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void testSelectUserById() {
// 获取 SqlSession
SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession();
try {
// 获取 Mapper 接口的代理实现类
UserMapper userMapper = session.getMapper(UserMapper.class);
// 调用 Mapper 方法执行查询
Integer userId = 1;
User user = userMapper.selectUserById(userId);
// 打印结果
System.out.println("查询结果: " + user);
// 可以在 log 中看到执行的 SQL 语句
} finally {
// 务必关闭 SqlSession
session.close();
}
}
@Test
public void testSelectAllUsers() {
SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAllUsers();
System.out.println("所有用户:");
for (User user : userList) {
System.out.println(user);
}
} finally {
session.close();
}
}
}
“`
运行这两个 JUnit 测试方法,你应该能看到查询结果和 MyBatis 打印出的 SQL 语句。
4.3 执行 Insert 操作
插入新的用户记录。
“`java
@Test
public void testInsertUser() {
SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession(); // 默认不自动提交事务
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
User newUser = new User("Charlie", 28);
userMapper.insertUser(newUser);
// 默认情况下,MyBatis 不会自动提交事务,需要手动提交
session.commit();
System.out.println("插入成功,新用户ID: " + newUser.getId()); // newUser.getId() 会包含自动生成的主键
} catch (Exception e) {
session.rollback(); // 发生异常时回滚事务
e.printStackTrace();
} finally {
session.close();
}
}
``
session.commit()
**注意:** 对于 INSERT, UPDATE, DELETE 操作,MyBatis 默认情况下不会自动提交事务。你需要手动调用方法来提交事务,或者在获取
SqlSession时使用
openSession(true)开启自动提交。在实际应用中,通常由服务层或框架(如 Spring)来管理事务。同时,要记得在发生异常时调用
session.rollback()。
useGeneratedKeys和
keyProperty的作用是在插入成功后,将数据库自动生成的主键值设置回传入的
user对象的
id` 属性中。
4.4 执行 Update 操作
更新现有用户记录。
“`java
@Test
public void testUpdateUser() {
SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
// 先查询出要更新的用户 (或者直接构建一个User对象,只要id有值)
User userToUpdate = userMapper.selectUserById(1);
if (userToUpdate != null) {
userToUpdate.setName("Alice Updated");
userToUpdate.setAge(26);
userMapper.updateUser(userToUpdate);
session.commit();
System.out.println("更新成功!");
} else {
System.out.println("未找到要更新的用户");
}
} catch (Exception e) {
session.rollback();
e.printStackTrace();
} finally {
session.close();
}
}
“`
4.5 执行 Delete 操作
删除用户记录。
“`java
@Test
public void testDeleteUser() {
SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
Integer userIdToDelete = 2;
userMapper.deleteUser(userIdToDelete);
session.commit();
System.out.println("删除成功!");
} catch (Exception e) {
session.rollback();
e.printStackTrace();
} finally {
session.close();
}
}
“`
4.6 SqlSession
的生命周期和资源管理
SqlSession
是一个重要的资源,它的生命周期应该尽可能短,通常与一次请求或一个业务操作对应。并且,使用完毕后必须关闭。使用 try-finally
或者 Java 7+ 的 try-with-resources
语句是确保 SqlSession
被关闭的推荐方式。
java
// 使用 try-with-resources 自动关闭 SqlSession (推荐)
@Test
public void testSelectUserByIdWithTryResources() {
// getSqlSessionFactory().openSession() 返回一个实现了 AutoCloseable 的 SqlSession
try (SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
Integer userId = 1;
User user = userMapper.selectUserById(userId);
System.out.println("查询结果 (try-with-resources): " + user);
// 在 try-with-resources 块结束时,session 会自动关闭
// 如果是写操作,需要在 try 块内手动 session.commit() 或 session.rollback()
} catch (Exception e) {
e.printStackTrace();
// 这里无需 session.rollback() 因为 session 会在 finally 块或 try-with-resources 结束时处理
// 但如果是更复杂的事务管理,你可能需要在业务层处理异常并决定回滚
}
}
try-with-resources
语法简洁且安全,可以避免忘记关闭资源的问题。
第五部分:参数处理 #{} vs ${}
在 MyBatis 的 SQL 映射文件中,有两种常用的方式来处理动态参数:#{}
和 ${}
。理解它们的区别至关重要,尤其涉及到安全性问题。
5.1 #{} 占位符
- 作用:
#{}
是一个参数占位符,它会将对应的参数值作为 PreparedStatement 的参数设置进去。 - 原理: MyBatis 会将
#{} 替换成问号 (?)
,然后在预编译语句中设置参数值。例如SELECT * FROM user WHERE id = #{id}
会被转换为SELECT * FROM user WHERE id = ?
,然后 MyBatis 将id
的值设置给这个问号。 - 优点:
- 安全性: 能够有效地防止 SQL 注入攻击,因为参数值是独立于 SQL 语句传递的。
- 类型转换: MyBatis 会根据参数类型进行自动转换。
- 更强的缓存能力: 由于 SQL 语句模板是固定的(只有占位符),数据库可以更好地缓存执行计划。
- 使用场景: 绝大多数情况下都应该使用
#{}
来传递参数值。
5.2 ${} 字符串替换
- 作用:
${}
是一个简单的字符串替换,它会将对应的参数值直接拼接到 SQL 语句中。 - 原理: MyBatis 会将
${}
直接替换成参数值的字符串形式。例如SELECT * FROM user WHERE name = '${name}'
(注意这里的引号,字符串参数需要手动加引号) 会被替换成SELECT * FROM user WHERE name = 'Alice'
。 - 缺点:
- 安全性问题: 存在 SQL 注入风险。如果参数值来自用户输入且未经严格校验,恶意用户可以构造特定的字符串来改变 SQL 的执行逻辑。例如,如果用户输入
' OR '1'='1
,则 SQL 可能变成SELECT * FROM user WHERE name = '' OR '1'='1'
,导致查询出所有用户。 - 性能问题: 每次参数不同,SQL 语句模板就会不同,数据库难以缓存执行计划。
- 手动处理引号: 对于字符串类型的参数,需要手动在 SQL 中添加引号。
- 安全性问题: 存在 SQL 注入风险。如果参数值来自用户输入且未经严格校验,恶意用户可以构造特定的字符串来改变 SQL 的执行逻辑。例如,如果用户输入
- 使用场景:
- 用于动态地指定 SQL 关键字、表名、列名或排序规则等不应作为参数值的部分。例如:
- 动态排序:
ORDER BY ${columnName} ${sortOrder}
(这里的columnName
和sortOrder
不应该来自用户输入,或者需要经过非常严格的白名单校验)。 - 动态表名:
SELECT * FROM ${tableName}
(同样需要非常小心)。 - 动态 SQL 片段:在某些高级动态 SQL 场景中可能会用到。
- 动态排序:
- 用于动态地指定 SQL 关键字、表名、列名或排序规则等不应作为参数值的部分。例如:
总结: 永远优先使用 #{}
。只有当确实需要将参数值直接作为 SQL 的一部分(而不是参数值)时,才考虑使用 ${}
,并且务必确保这些值是安全的,例如来自代码常量、配置或者经过严格的白名单过滤。切勿将用户直接输入的、未经校验的数据使用 ${}
拼接进 SQL。
第六部分:结果集映射 <resultMap>
当数据库表的列名与实体类的属性名不完全一致,或者需要映射复杂的对象关系(一对一、一对多)时,仅靠 resultType
就不够了。这时就需要使用 <resultMap>
进行更灵活的结果集映射。
6.1 resultType
的局限性
resultType
只能将结果集的列值直接映射到指定类型的对象属性上,要求:
* 列名与属性名一致(忽略大小写)。
* 如果是复合类型(如 POJO),其属性必须是基本类型或可以自动转换的类型。
* 不支持复杂的嵌套对象或关联关系映射。
6.2 <resultMap>
的作用
<resultMap>
允许你定义一个详细的映射规则,将数据库结果集的列映射到 Java 对象的属性上,解决 resultType
的局限性。
6.3 <resultMap>
基本结构
<resultMap>
定义在 Mapper XML 文件中,<mapper>
标签内。
“`xml
<!-- 复杂关联关系的映射,将在高级部分讲解 -->
<!-- <association property="..." column="..." javaType="..." select="..."/> -->
<!-- <collection property="..." column="..." ofType="..." select="..."/> -->
“`
6.4 在 <select>
中使用 <resultMap>
在 <select>
标签中,将 resultType
属性替换为 resultMap
属性,并引用之前定义的 <resultMap>
的 id
。
“`xml
“`
在 Mapper 接口中添加对应的方法:
java
public interface UserMapper {
// ... 其他方法 ...
User selectUserByIdWithMap(Integer id);
List<User> selectAllUsersWithMap();
}
通过使用 <resultMap>
,即使数据库列名与实体属性名不一致(如 user_name
vs name
),MyBatis 也能正确地将结果映射到 User
对象。<resultMap>
是 MyBatis 进行复杂结果集映射的基础。
第七部分:MyBatis 基础设置与日志
7.1 mybatis-config.xml
中的 <settings>
<settings>
标签提供了许多全局配置选项,可以调整 MyBatis 的行为。一些常用的设置包括:
cacheEnabled
: 全局启用或禁用二级缓存 (boolean)。lazyLoadingEnabled
: 全局启用或禁用延迟加载 (boolean)。aggressiveLazyLoading
: 当启用延迟加载时,是否加载对象的所有属性 (boolean)。logImpl
: 指定 MyBatis 使用的日志实现。常用的有SLF4J
(org.apache.ibatis.logging.slf4j.Slf4jImpl
)、LOG4J2
(org.apache.ibatis.logging.log4j2.Log4j2Impl
)、LOG4J
(org.apache.ibatis.logging.log4j.Log4jImpl
) 等。mapUnderscoreToCamelCase
: 开启驼峰命名自动映射,即数据库列名user_name
自动映射到 Java 属性userName
(boolean)。如果开启了这个设置,并且列名与属性名符合驼峰/下划线转换规则,就可以省略简单的<result>
映射,但<id>
仍建议明确映射。useGeneratedKeys
: 全局设置是否允许JDBC支持获取由数据库生成的主键 (boolean)。defaultStatementTimeout
: 设置 JDBC statement 超时时间 (秒)。defaultFetchSize
: 设置 JDBC 的 fetch size。
例如,在 mybatis-config.xml
中开启驼峰命名自动映射:
xml
<settings>
<setting name="logImpl" value="org.apache.ibatis.logging.slf4j.Slf4jImpl"/>
<setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启驼峰命名映射 -->
</settings>
7.2 配置日志输出 SQL
查看 MyBatis 执行的 SQL 是调试过程中非常重要的一步。如前文所述,通过在 mybatis-config.xml
的 <settings>
中配置 logImpl
,并配合日志框架(如 Logback),可以将 MyBatis 执行的 SQL 打印到控制台或其他地方。
当 logImpl
配置为 Slf4jImpl
并且日志框架配置正确时,MyBatis 会将执行的 SQL 语句、参数以及结果集信息打印到日志中,级别通常为 DEBUG。
例如,在使用 Logback 时,确保 logback.xml
中有如下配置:
xml
<logger name="com.example.mapper" level="DEBUG"/> <!-- 针对特定 Mapper 包 -->
<logger name="org.apache.ibatis" level="DEBUG"/> <!-- 针对MyBatis核心 -->
这样,每次调用 com.example.mapper
包中的 Mapper 方法时,MyBatis 就会打印出对应的 SQL。
第八部分:总结与进阶方向
本文详细介绍了 MyBatis 的基础知识,包括:
* MyBatis 的定义、定位和优势,以及与 JDBC、ORM 的比较。
* MyBatis 的核心组件和工作流程。
* 如何搭建一个基本的 MyBatis 项目环境。
* 使用 Mapper 接口和 XML 文件实现基本的 CRUD 操作。
* 深入理解 #{} 与 ${}
参数处理的区别和注意事项。
* 使用 <resultMap>
进行灵活的结果集映射。
* MyBatis 的基础配置和日志设置。
通过阅读本文并动手实践,你应该已经掌握了使用 MyBatis 进行基本数据库操作的能力。
MyBatis 还有许多更强大的功能等待你去探索,例如:
- 动态 SQL: 使用
<if>
,<where>
,<set>
,<foreach>
等标签根据条件动态生成 SQL 语句。 - 关联关系映射: 使用
<association>
(一对一) 和<collection>
(一对多) 标签映射复杂的对象关系。 - 缓存机制: MyBatis 提供一级缓存(SqlSession 级别)和二级缓存(SqlSessionFactory 级别),可以有效提升查询性能。
- 事务管理: 在 Spring 等框架中,通常由框架来管理 MyBatis 的事务。
- 类型处理器 (Type Handlers): 自定义 Java 类型与 JDBC 类型之间的转换。
- 插件机制 (Plugins): 在 MyBatis 核心流程中插入自定义逻辑,实现拦截、日志、分页等功能。
- 注解配置: 除了 XML 方式,MyBatis 也支持使用注解在 Mapper 接口上直接定义 SQL 和映射规则。
掌握了基础,进一步学习这些高级特性将帮助你在更复杂的场景下更有效地使用 MyBatis。希望本文能成为你 MyBatis 学习之路上的坚实起点!