文章标题:🚀【MyBatis 从入门到进阶】保姆级教程 | CRUD、动态SQL、原理剖析与避坑指南,看完这篇全搞定!
标签: #Java #MyBatis #ORM框架 #数据库 #持久层 #动态SQL #源码分析 #避坑指南 #优快云
哈喽,各位在 优快云 奋斗的小伙伴们!👋 是不是在 Java 开发中,与数据库打交道是家常便饭?还在手写繁琐的 JDBC 代码,手动管理连接、处理结果集吗?🤯 那你可就 OUT 啦!今天,我们要隆重介绍一款优秀的持久层框架——MyBatis!✨
MyBatis 是一款半自动化的 ORM (Object Relational Mapping) 框架,它能让你从繁杂的 JDBC 代码中解脱出来,更专注于 SQL 语句本身,同时又提供了强大的灵活性。无论你是刚接触 MyBatis 的萌新 🌱,还是希望深入理解其原理和实践的进阶选手,这篇保姆级教程都值得你拥有!收藏⭐+关注哦!
🤔 什么是 MyBatis?为什么选择它?
MyBatis 的前身是 iBATIS,它是一个支持定制化 SQL、存储过程以及高级映射的优秀持久层框架。与 Hibernate 这样的全自动化 ORM 框架不同,MyBatis 的特点在于:
- SQL 与代码分离:SQL 语句写在 XML 文件或注解中,与 Java 代码解耦,方便维护和优化。
- 灵活性高:开发者可以完全掌控 SQL 语句,对于复杂的查询和数据库特性支持良好。你可以写出最适合业务的 SQL。
- 学习成本相对较低:相比 Hibernate,MyBatis 的概念更少,更容易上手。
- 性能优秀:MyBatis 本身开销较小,且允许开发者直接优化 SQL。
- 动态 SQL:强大的动态 SQL 功能,可以根据条件动态拼接 SQL 语句,非常实用。
- 与 Spring/Spring Boot 完美集成:是 Java EE 和微服务开发中的主流选择。
一句话总结(敲黑板!):MyBatis 帮你把 Java 对象和数据库表记录“对应”起来,让你用操作对象的方式来操作数据,同时还能让你愉快地写 SQL!🎯
🛠️ MyBatis 快速入门:一个简单的 CRUD 示例
光说不练假把式!我们来看一个简单的用户表 (users) 的增删改查示例。
1. 准备工作:
-
数据库表结构 (MySQL 为例):
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL, email VARCHAR(100) );
-
Maven 依赖 (pom.xml):
<dependencies> <!-- MyBatis核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> <!-- 请使用最新稳定版 --> </dependency> <!-- MySQL驱动 (根据你的数据库选择) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> <!-- 请使用与你MySQL版本匹配的驱动 --> </dependency> <!-- (可选) 日志实现,如SLF4J + Logback --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.32</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.10</version> </dependency> <!-- (可选) 测试框架,如 JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
2. 创建实体类 User.java
:
package com.example.mybatis.entity;
/**
* 用户实体类,对应数据库中的 users 表
*/
public class User {
private Integer id; // 用户ID,对应表中的 id 字段
private String username; // 用户名,对应表中的 username 字段
private String password; // 密码,对应表中的 password 字段
private String email; // 邮箱,对应表中的 email 字段
// 构造函数 (可以有多个,按需创建)
public User() {
}
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
// Getter 和 Setter 方法 (必须提供,MyBatis需要通过它们来设置和获取属性值)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// (可选) toString 方法,方便调试时打印对象信息
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}
}
3. 创建 Mapper 接口 UserMapper.java
:
package com.example.mybatis.mapper;
import com.example.mybatis.entity.User;
import org.apache.ibatis.annotations.Param; // 引入 Param 注解,用于多参数传递
import java.util.List;
/**
* 用户 Mapper 接口
* 定义了对 users 表的数据库操作方法
* MyBatis 会为这个接口生成动态代理实现类
*/
public interface UserMapper {
/**
* 根据 ID 查询用户
* @param id 用户ID
* @return 对应的用户对象,如果不存在则返回 null
*/
User getUserById(Integer id);
/**
* 查询所有用户
* @return 用户列表
*/
List<User> getAllUsers();
/**
* 插入一个新用户
* @param user 包含新用户信息的 User 对象 (id 通常由数据库自增生成)
* @return 影响的行数 (通常是 1 表示成功)
*/
int insertUser(User user);
/**
* 更新用户信息
* @param user 包含要更新的用户信息的 User 对象 (通常需要 id 来定位用户)
* @return 影响的行数
*/
int updateUser(User user);
/**
* 根据 ID 删除用户
* @param id 要删除的用户ID
* @return 影响的行数
*/
int deleteUserById(Integer id);
/**
* (示例) 使用 @Param 注解传递多个参数
* 根据用户名和邮箱查询用户
* @param username 用户名
* @param email 邮箱
* @return 匹配的用户对象列表
*/
List<User> getUsersByUsernameAndEmail(@Param("username") String username, @Param("email") String email);
}
4. 创建 Mapper XML 配置文件 UserMapper.xml
(通常放在 resources/com/example/mybatis/mapper/
目录下):
<?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.mybatis.mapper.UserMapper">
<!--
id: 对应 Mapper 接口中的方法名
parameterType: 输入参数的类型 (可选,MyBatis 通常能自动推断)
resultType: 返回结果的类型 (对于单条记录查询) 或集合中元素的类型 (对于多条记录查询)
这里是实体类的完整类名
-->
<!-- 根据 ID 查询用户 -->
<select id="getUserById" parameterType="int" resultType="com.example.mybatis.entity.User">
SELECT id, username, password, email FROM users WHERE id = #{id}
<!-- #{id} 是占位符,MyBatis 会用 PreparedStatement 来防止 SQL 注入 -->
</select>
<!-- 查询所有用户 -->
<select id="getAllUsers" resultType="com.example.mybatis.entity.User">
SELECT id, username, password, email FROM users
</select>
<!--
插入新用户
useGeneratedKeys="true": 表示使用数据库的自增主键
keyProperty="id": 将生成的自增主键值设置回传入的 User 对象的 id 属性
-->
<insert id="insertUser" parameterType="com.example.mybatis.entity.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, password, email)
VALUES (#{username}, #{password}, #{email})
</insert>
<!-- 更新用户信息 -->
<update id="updateUser" parameterType="com.example.mybatis.entity.User">
UPDATE users
SET username = #{username}, password = #{password}, email = #{email}
WHERE id = #{id}
</update>
<!-- 根据 ID 删除用户 -->
<delete id="deleteUserById" parameterType="int">
DELETE FROM users WHERE id = #{id}
</delete>
<!-- (示例) 根据用户名和邮箱查询用户 -->
<select id="getUsersByUsernameAndEmail" resultType="com.example.mybatis.entity.User">
SELECT id, username, password, email FROM users
WHERE username = #{username} AND email = #{email}
<!-- 注意:如果 Mapper 接口方法中使用了 @Param 注解,
这里的占位符 #{username} 和 #{email} 会直接对应注解中的名称 -->
</select>
</mapper>
5. 创建 MyBatis 全局配置文件 mybatis-config.xml
(通常放在 resources
目录下):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- (可选) 引入外部属性文件,如数据库连接信息 -->
<!-- <properties resource="db.properties"/> -->
<!-- (可选) 设置 -->
<settings>
<!-- 开启驼峰命名转换:table_column -> javaProperty (例如 user_name -> userName) -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- (可选) 配置日志实现,如果项目中没有特定日志框架,MyBatis会尝试使用自带的日志 -->
<!-- <setting name="logImpl" value="STDOUT_LOGGING" /> --> <!-- 输出到控制台 -->
</settings>
<!-- (可选) 类型别名,可以为 Java 类型设置一个简短的名字,在 XML 中使用别名 -->
<typeAliases>
<typeAlias type="com.example.mybatis.entity.User" alias="User"/>
<!-- 也可以扫描包下的所有类作为别名,别名默认为类名(首字母小写) -->
<!-- <package name="com.example.mybatis.entity"/> -->
</typeAliases>
<!-- 环境配置 (可以配置多个环境,如开发、测试、生产) -->
<environments default="development"> <!-- default 指定默认使用的环境 ID -->
<environment id="development">
<!-- 事务管理器类型:JDBC 表示使用 JDBC 的事务管理方式 -->
<transactionManager type="JDBC"/>
<!-- 数据源类型:POOLED 表示使用 MyBatis 自带的连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/> <!-- 数据库驱动 -->
<property name="url" value="jdbc:mysql://localhost:3306/your_database_name?useSSL=false&serverTimezone=UTC"/> <!-- 数据库连接URL -->
<property name="username" value="your_username"/> <!-- 数据库用户名 -->
<property name="password" value="your_password"/> <!-- 数据库密码 -->
</dataSource>
</environment>
</environments>
<!-- Mapper 注册 -->
<mappers>
<!-- 方式一:注册单个 Mapper XML 文件 -->
<mapper resource="com/example/mybatis/mapper/UserMapper.xml"/>
<!-- 方式二:注册指定包下的所有 Mapper 接口 (推荐,前提是 Mapper 接口和 XML 文件在同一目录下且同名) -->
<!-- <package name="com.example.mybatis.mapper"/> -->
</mappers>
</configuration>
请务必将 your_database_name
, your_username
, your_password
替换成你自己的数据库信息!
6. 编写测试代码 MyBatisTest.java
:
package com.example.mybatis.test;
import com.example.mybatis.entity.User;
import com.example.mybatis.mapper.UserMapper;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
private UserMapper userMapper;
/**
* JUnit 的 @Before 注解,在每个测试方法执行前运行
* 用于初始化 SqlSessionFactory 和 SqlSession
*/
@Before
public void setUp() throws IOException {
// 1. 读取 MyBatis 全局配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2. 创建 SqlSessionFactory 对象
// SqlSessionFactoryBuilder 用于构建 SqlSessionFactory
// 一旦创建,SqlSessionFactoryBuilder 实例就不再需要了
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close(); // 关闭输入流
// 3. 从 SqlSessionFactory 获取 SqlSession 对象
// SqlSession 包含了执行 SQL 命令所需的所有方法,是 MyBatis工作的核心接口
// 默认情况下,SqlSession 不是线程安全的,每个线程都应该有它自己的 SqlSession 实例
// 参数 true 表示开启自动提交事务,如果为 false 或不传,则需要手动 session.commit()
this.sqlSession = sqlSessionFactory.openSession(true); // 开启自动提交
// 4. 获取 Mapper 接口的代理对象
// MyBatis 会为 UserMapper 接口创建一个动态代理对象
this.userMapper = sqlSession.getMapper(UserMapper.class);
}
/**
* JUnit 的 @After 注解,在每个测试方法执行后运行
* 用于关闭 SqlSession
*/
@After
public void tearDown() {
if (sqlSession != null) {
sqlSession.close(); // 关闭 SqlSession,释放资源
}
}
@Test
public void testInsertUser() {
User newUser = new User("Alice", "alice123", "alice@example.com");
int result = userMapper.insertUser(newUser);
System.out.println("插入结果 (影响行数): " + result);
System.out.println("插入后 User 的 ID: " + newUser.getId()); // 因为配置了 useGeneratedKeys 和 keyProperty
// 断言 (根据实际情况添加更严格的断言)
// Assert.assertEquals(1, result);
// Assert.assertNotNull(newUser.getId());
}
@Test
public void testGetUserById() {
// 假设 ID 为 1 的用户存在
User user = userMapper.getUserById(1);
if (user != null) {
System.out.println("查询到的用户: " + user);
} else {
System.out.println("未找到 ID 为 1 的用户。");
}
}
@Test
public void testGetAllUsers() {
List<User> users = userMapper.getAllUsers();
System.out.println("所有用户列表:");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testUpdateUser() {
// 假设要更新 ID 为 1 的用户
User userToUpdate = userMapper.getUserById(1);
if (userToUpdate != null) {
userToUpdate.setEmail("updated_alice@example.com");
userToUpdate.setPassword("new_password_for_alice");
int result = userMapper.updateUser(userToUpdate);
System.out.println("更新结果 (影响行数): " + result);
User updatedUser = userMapper.getUserById(1);
System.out.println("更新后的用户: " + updatedUser);
} else {
System.out.println("未找到要更新的用户。");
}
}
@Test
public void testDeleteUserById() {
// 假设要删除 ID 为 2 的用户 (请确保这个 ID 存在或用于测试)
int userIdToDelete = 2;
User userBeforeDelete = userMapper.getUserById(userIdToDelete);
if (userBeforeDelete == null) {
System.out.println("ID 为 " + userIdToDelete + " 的用户不存在,无法执行删除测试。请先插入该用户。");
return;
}
int result = userMapper.deleteUserById(userIdToDelete);
System.out.println("删除结果 (影响行数): " + result);
User userAfterDelete = userMapper.getUserById(userIdToDelete);
if (userAfterDelete == null) {
System.out.println("ID 为 " + userIdToDelete + " 的用户已成功删除。");
} else {
System.out.println("删除失败,用户依然存在: " + userAfterDelete);
}
}
@Test
public void testGetUsersByUsernameAndEmail() {
// 假设存在用户名为 "Alice",邮箱为 "updated_alice@example.com" 的用户
List<User> users = userMapper.getUsersByUsernameAndEmail("Alice", "updated_alice@example.com");
System.out.println("根据用户名和邮箱查询到的用户:");
for (User user : users) {
System.out.println(user);
}
}
}
运行测试代码,你就能看到 MyBatis 如何帮你轻松完成数据库操作啦! 🎉
💡 MyBatis 深度与广度:不止于 CRUD
MyBatis 的强大远不止于此,我们来探索一下它的更多特性和原理:
1. 动态 SQL:让 SQL “活” 起来!
动态 SQL 是 MyBatis 的一大亮点,它允许你根据不同的条件动态地构建 SQL 语句。常用的动态 SQL 标签有:
<if>
:条件判断<choose>
,<when>
,<otherwise>
:类似 Java 的switch
语句<trim>
,<where>
,<set>
:用于处理 SQL 前缀、后缀或去除多余的AND
,OR
,,
<foreach>
:用于遍历集合,常用于IN
子句或批量插入/更新
示例:使用 <if>
和 <where>
进行动态条件查询
修改 UserMapper.xml
,增加一个动态查询方法:
<!-- 动态条件查询用户 -->
<select id="findUsersByCriteria" parameterType="com.example.mybatis.entity.User" resultType="com.example.mybatis.entity.User">
SELECT id, username, password, email FROM users
<where>
<!-- 如果 username 不为空且不为空字符串,则添加 username 条件 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<!-- 如果 email 不为空且不为空字符串,则添加 email 条件 -->
<if test="email != null and email != ''">
AND email = #{email}
</if>
<!-- 注意:<where> 标签会自动处理第一个 AND 或 OR,
如果内部条件都不满足,则不生成 WHERE 子句。 -->
</where>
</select>
在 UserMapper.java
中添加对应方法:
List<User> findUsersByCriteria(User criteria);
测试时,你可以传入一个 User
对象,只设置你想要查询的条件字段,其他字段为 null
或空字符串。
2. 结果映射 (<resultMap>
): 复杂关系的桥梁
当数据库表字段名与 Java 实体类属性名不一致(即使开启了驼峰转换也无法满足),或者需要处理一对一、一对多、多对多等复杂关联关系时,<resultMap>
就派上用场了。
示例:字段名不一致的映射
假设 users
表有一个字段 user_nickname
,而 User
实体类属性是 nickname
。
<resultMap id="userResultMap" type="com.example.mybatis.entity.User">
<id property="id" column="id"/> <!-- 主键映射 -->
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="nickname" column="user_nickname"/> <!-- 将 user_nickname 映射到 nickname -->
</resultMap>
<select id="getUserWithNickname" parameterType="int" resultMap="userResultMap">
SELECT id, username, password, email, user_nickname FROM users WHERE id = #{id}
</select>
关联查询 (<association>
和 <collection>
) 更是处理复杂对象图的利器,这里不展开细讲,但它是 MyBatis 强大映射能力的体现。
3. 缓存机制:提升查询效率
MyBatis 内置了两级缓存:
- 一级缓存 (Local Cache):默认开启,作用域是
SqlSession
级别。在同一个SqlSession
内,对同一个Mapper
的同一个方法的相同参数的查询,第一次会从数据库查,然后将结果放入缓存;后续相同的查询会直接从缓存中获取,不再访问数据库。当执行CUD
操作(增删改)或者手动调用sqlSession.clearCache()
时,一级缓存会清空。 - 二级缓存 (Global Cache):作用域是
Mapper Namespace
级别(即同一个XxxMapper.xml
)。需要手动在mybatis-config.xml
中开启<setting name="cacheEnabled" value="true"/>
,并在XxxMapper.xml
中使用<cache/>
标签来配置。多个SqlSession
可以共享同一个Mapper
的二级缓存。当一个SqlSession
执行了CUD
操作并提交后,会清空该Namespace
下的二级缓存。
注意: 缓存虽好,但也可能带来数据不一致的问题(脏读),特别是在分布式环境或涉及多个应用操作同一数据库时,需要谨慎使用和配置二级缓存。通常建议优先考虑数据库层面的缓存或专门的缓存中间件(如 Redis)。
4. MyBatis 实现原理浅析 (面试常问!)
理解 MyBatis 的工作流程和核心组件,有助于我们更好地使用它并排查问题。
核心组件:
- SqlSessionFactoryBuilder: 根据 MyBatis 配置文件 (XML 或 Java 代码) 构建
SqlSessionFactory
。它的生命周期很短。 - SqlSessionFactory: 工厂模式的体现,用于创建
SqlSession
。一旦创建,应在应用运行期间一直存在,是线程安全的。 - SqlSession: MyBatis 工作的核心接口,包含了执行 SQL 命令、获取 Mapper 代理、管理事务等方法。每个线程都应该有它自己的
SqlSession
实例,非线程安全。 - Executor:
SqlSession
底层依赖Executor
来执行 SQL。Executor
负责 SQL 的动态拼装、参数处理、结果集映射、缓存处理等。有多种实现,如SimpleExecutor
(默认)、ReuseExecutor
(重用PreparedStatement
)、BatchExecutor
(批量执行)。 - MappedStatement: MyBatis 将 Mapper XML 文件中的每一个
<select>
,<insert>
,<update>
,<delete>
标签解析为一个MappedStatement
对象,包含了 SQL 语句、输入参数类型、输出结果类型、缓存配置等所有信息。 - Configuration: MyBatis 的所有配置信息都保存在
Configuration
对象中,包括环境配置、类型别名、Mapper 注册、MappedStatement
等。
工作流程(简化版):
- 加载配置:读取
mybatis-config.xml
和 Mapper XML 文件,解析配置信息,创建Configuration
对象。 - 创建 SqlSessionFactory:
SqlSessionFactoryBuilder
根据Configuration
创建SqlSessionFactory
。 - 创建 SqlSession:从
SqlSessionFactory
获取SqlSession
。 - 获取 Mapper 代理:调用
sqlSession.getMapper(XxxMapper.class)
时,MyBatis 使用 JDK 动态代理或 CGLIB 为XxxMapper
接口生成一个代理对象。 - 执行 Mapper 方法:当调用 Mapper 代理对象的方法时,实际上会执行代理逻辑(如
MapperProxy
的invoke
方法)。 - 定位 MappedStatement:代理逻辑会根据调用的方法名(接口全限定名 + 方法名)从
Configuration
中找到对应的MappedStatement
。 - 执行 SQL:
SqlSession
将请求传递给Executor
。Executor
根据MappedStatement
中的信息,进行参数处理、动态 SQL 解析、执行 SQL 语句(通过 JDBC),并将结果集进行映射。 - 结果映射与返回:
Executor
将数据库返回的结果集,根据<resultMap>
或resultType
的配置,映射成 Java 对象或集合,并返回给调用者。 - 关闭 SqlSession:释放资源。
MyBatis 广度应用:
- 与 Spring/Spring Boot 集成:通过
mybatis-spring
和mybatis-spring-boot-starter
依赖,可以非常方便地将 MyBatis 集成到 Spring 生态中,由 Spring 管理SqlSessionFactory
、SqlSession
(通过SqlSessionTemplate
)和事务。 - 插件 (Interceptor):MyBatis 提供了强大的插件机制,允许你在
Executor
、ParameterHandler
、ResultSetHandler
、StatementHandler
这四个核心组件的方法执行前后进行拦截和扩展,常用于实现分页、数据权限、日志记录等功能。 - 代码生成器 (MyBatis Generator):可以根据数据库表结构自动生成实体类、Mapper 接口和 Mapper XML 文件,提高开发效率。
- 注解方式:除了 XML,MyBatis 也支持使用注解(如
@Select
,@Insert
等)来编写 SQL,对于简单的 SQL 操作比较方便,但复杂 SQL 还是推荐 XML。
⚠️ MyBatis 避坑指南:这些点要注意!
-
#{}
和${}
的区别 (SQL 注入风险!)#{}
:预编译占位符,MyBatis 会使用PreparedStatement
,将参数安全地设置进去,能有效防止 SQL 注入。强烈推荐使用!${}
:字符串替换,直接将参数拼接到 SQL 语句中,有 SQL 注入的风险。仅在特殊场景下使用,如动态指定表名、列名或ORDER BY
子句(且必须确保参数来源可靠或进行严格校验)。
-
parameterType
通常可以省略,但有时会出问题- MyBatis 大部分情况下能推断出参数类型。但如果传递的是
Map
且Map
中有多个参数,或者有复杂嵌套对象时,显式指定parameterType
有时能避免一些奇怪的问题。
- MyBatis 大部分情况下能推断出参数类型。但如果传递的是
-
resultType
vs<resultMap>
- 简单的结果映射(字段名与属性名一致或符合驼峰规则)用
resultType
即可。 - 复杂映射(字段名不一致、关联查询、集合嵌套等)必须使用
<resultMap>
。
- 简单的结果映射(字段名与属性名一致或符合驼峰规则)用
-
N+1 查询问题
- 在进行一对多或多对多关联查询时,如果配置不当(比如使用嵌套查询
select
属性且没有合理使用延迟加载或一次性加载),很容易产生 N+1 查询问题,即先查询主对象 (1次),然后根据每个主对象再去查询关联的子对象 (N次),导致大量低效的 SQL 执行。 - 解决方法:
- 使用
JOIN
查询,在<resultMap>
中通过<association>
和<collection>
的javaType
和ofType
配合columnPrefix
等属性进行映射。 - 合理使用延迟加载 (
lazyLoadingEnabled
和<association/collection>
的fetchType="lazy"
),按需加载关联数据。
- 使用
- 在进行一对多或多对多关联查询时,如果配置不当(比如使用嵌套查询
-
Mapper 接口方法重载的限制
- 由于 Mapper XML 中的
id
是与 Mapper 接口方法名对应的,MyBatis 不支持 Mapper 接口中方法名相同但参数不同的重载(除非使用不同的@Param
注解来区分,但 XML 中的id
仍然需要唯一)。通常建议为每个不同的操作定义一个唯一的方法名。
- 由于 Mapper XML 中的
-
XML 文件路径或
namespace
写错- 这是新手常犯的错误,会导致找不到 Mapper 或方法。请仔细检查
mybatis-config.xml
中 Mapper 的注册路径以及UserMapper.xml
中的namespace
是否与接口全限定名完全一致。
- 这是新手常犯的错误,会导致找不到 Mapper 或方法。请仔细检查
-
事务管理
- 单独使用 MyBatis 时,需要手动控制事务(
sqlSession.commit()
和sqlSession.rollback()
),或者在openSession()
时传入true
开启自动提交。 - 与 Spring 集成后,通常由 Spring 的声明式事务管理。
- 单独使用 MyBatis 时,需要手动控制事务(
-
动态 SQL 拼接时空格和逗号问题
- 使用
<if>
等标签时,要注意 SQL 语句中可能多余的空格或逗号。<trim>
,<where>
,<set>
标签能很好地解决这些问题。
- 使用
-
缓存导致的数据不一致
- 如前所述,二级缓存需要谨慎使用,确保了解其作用域和刷新机制,避免脏读。
总结一下:
MyBatis 是一款功能强大且灵活的持久层框架,它通过将 SQL 与代码分离,提供了高度的 SQL 控制权,同时通过动态 SQL、结果映射、缓存等机制,极大地简化了数据库操作。
- 入门:掌握基本的 CRUD、配置文件和 Mapper 编写。
- 进阶:熟练运用动态 SQL、
<resultMap>
进行复杂映射、理解缓存机制、了解其核心原理。 - 实践:注意避免常见的坑点,结合 Spring/Spring Boot 使用,发挥其最大效能。
学习 MyBatis 不仅仅是学会用一个工具,更是理解持久化思想、SQL 优化和框架设计的一个好机会!💪
好啦,关于 MyBatis 的超详细讲解就到这里! 希望这篇从入门到进阶,覆盖原理与避坑的教程,能让你对 MyBatis 有一个全面而深入的理解!
觉得这篇教程对你有巨大帮助?请务必 点赞👍 + 收藏⭐ + 关注我👀 ! 你的支持是我持续输出高质量技术博文的源泉!💖
在使用 MyBatis 过程中,你遇到过哪些印象深刻的问题?或者有什么独到的使用技巧?欢迎在评论区分享你的经验,我们一起交流,共同进步!💬
Happy Coding!🚀