MyBatis 作为一款优秀的持久层框架,凭借其灵活的 SQL 映射、强大的动态 SQL 能力、轻量级的架构设计,在企业级 Java 应用中占据重要地位。它消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索,通过简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs 映射成数据库中的记录。本文将系统全面地梳理 MyBatis 的核心知识点,从基础概念到高级特性,帮助初学者建立完整的知识体系,也为有经验的开发者提供深入的技术参考。
一、MyBatis 概述
1.1 什么是 MyBatis
MyBatis 是一款优秀的持久层框架,支持自定义 SQL、存储过程以及高级映射。它通过 XML 或注解的方式,将 Java 对象与 SQL 语句进行映射,简化了 JDBC 编程的复杂性。
核心特性:
消除 JDBC 冗余代码,简化持久层开发
支持动态 SQL,灵活构建查询条件
提供强大的映射器,支持复杂对象关系映射
支持与 Spring 等框架无缝集成
支持缓存机制,提升查询性能
支持存储过程调用
轻量级,学习成本低
1.2 架构原理
MyBatis 架构层次:
┌─────────────────────────────────────────┐
│ Interface Layer │
│ (SqlSession, Mapper Interface) │
├─────────────────────────────────────────┤
│ Data Processing Layer │
│ (Parameter Mapping, SQL Execution) │
├─────────────────────────────────────────┤
│ SQL Mapping Layer │
│ (XML Mapping, Annotation Mapping) │
├─────────────────────────────────────────┤
│ Base Support Layer │
│ (Transaction, Cache, Connection) │
└─────────────────────────────────────────┘
工作流程:
读取配置文件(mybatis-config.xml)
创建 SqlSessionFactory
通过 SqlSessionFactory 创建 SqlSession
通过 SqlSession 获取 Mapper 代理对象
执行 Mapper 方法,执行 SQL
返回结果映射到 Java 对象
1.3 环境搭建
Maven 依赖:
<dependencies>
<!-- MyBatis 核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
</dependencies>
核心配置文件 mybatis-config.xml:
<?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="jdbc.properties"/>
<!-- 配置全局设置 -->
<settings>
<!-- 开启驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 日志实现 -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<!-- 类型别名 -->
<typeAliases>
<package name="com.example.entity"/>
</typeAliases>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 映射器配置 -->
<mappers>
<package name="com.example.mapper"/>
</mappers>
</configuration>
jdbc.properties:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
username=root
password=123456
初始化 SqlSessionFactory:
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.InputStream;
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
public static SqlSession getSqlSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
}
二、核心组件
2.1 SqlSessionFactory 与 SqlSession
public class SqlSessionDemo {
// SqlSessionFactory 是线程安全的,应该作为单例
private SqlSessionFactory sqlSessionFactory;
// 获取 SqlSession
public void testSqlSession() {
// 默认不自动提交
try (SqlSession session = sqlSessionFactory.openSession()) {
// 执行操作
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);
System.out.println(user);
// 手动提交
session.commit();
} catch (Exception e) {
// 回滚
session.rollback();
}
// 自动提交事务
try (SqlSession session = sqlSessionFactory.openSession(true)) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insert(new User("张三", 25));
}
// 设置执行器类型
try (SqlSession session = sqlSessionFactory.openSession(
ExecutorType.BATCH)) { // SIMPLE, REUSE, BATCH
UserMapper mapper = session.getMapper(UserMapper.class);
// 批量操作
for (int i = 0; i < 1000; i++) {
mapper.insert(new User("user" + i, i));
}
session.commit();
}
}
}
2.2 Mapper 接口与 XML 映射
实体类:
package com.example.entity;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Long id;
private String username;
private String password;
private Integer age;
private String email;
private Date createTime;
private Date updateTime;
// 构造函数、getter、setter
public User() {}
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
// getter/setter 省略
}
Mapper 接口:
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
public interface UserMapper {
// 基本 CRUD 方法
User selectById(@Param("id") Long id);
List<User> selectAll();
int insert(User user);
int update(User user);
int deleteById(Long id);
// 多条件查询
List<User> selectByCondition(@Param("username") String username,
@Param("age") Integer age);
// 使用 Map 参数
List<User> selectByMap(Map<String, Object> params);
// 返回 Map
Map<String, Object> selectMapById(Long id);
// 分页查询
List<User> selectByPage(@Param("offset") int offset,
@Param("limit") int limit);
// 批量插入
int batchInsert(@Param("list") List<User> users);
}
XML 映射文件 UserMapper.xml:
<?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">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="userResultMap" type="com.example.entity.User">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- 基础字段列表 -->
<sql id="baseColumns">
id, username, password, age, email, create_time, update_time
</sql>
<!-- 根据ID查询 -->
<select id="selectById" resultMap="userResultMap" parameterType="long">
SELECT <include refid="baseColumns"/>
FROM user
WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="selectAll" resultType="com.example.entity.User">
SELECT <include refid="baseColumns"/>
FROM user
ORDER BY id DESC
</select>
<!-- 插入用户 -->
<insert id="insert" parameterType="com.example.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, age, email, create_time, update_time)
VALUES (#{username}, #{password}, #{age}, #{email},
NOW(), NOW())
</insert>
<!-- 更新用户 -->
<update id="update" parameterType="com.example.entity.User">
UPDATE user
SET username = #{username},
password = #{password},
age = #{age},
email = #{email},
update_time = NOW()
WHERE id = #{id}
</update>
<!-- 删除用户 -->
<delete id="deleteById" parameterType="long">
DELETE FROM user WHERE id = #{id}
</delete>
<!-- 条件查询 -->
<select id="selectByCondition" resultMap="userResultMap">
SELECT <include refid="baseColumns"/>
FROM user
WHERE 1 = 1
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
<!-- Map参数查询 -->
<select id="selectByMap" resultMap="userResultMap" parameterType="map">
SELECT <include refid="baseColumns"/>
FROM user
WHERE 1 = 1
<if test="username != null">
AND username = #{username}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</select>
<!-- 返回Map -->
<select id="selectMapById" resultType="map" parameterType="long">
SELECT <include refid="baseColumns"/>
FROM user
WHERE id = #{id}
</select>
<!-- 分页查询 -->
<select id="selectByPage" resultMap="userResultMap">
SELECT <include refid="baseColumns"/>
FROM user
ORDER BY id DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="list">
INSERT INTO user (username, age, create_time, update_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.age}, NOW(), NOW())
</foreach>
</insert>
</mapper>
三、动态 SQL
3.1 if 标签
<!-- if 条件判断 -->
<select id="findUsers" resultType="User">
SELECT * FROM user
WHERE 1 = 1
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</select>
<!-- 更优雅的写法,避免 WHERE 1=1 -->
<select id="findUsers" resultType="User">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
<if test="maxAge != null">
AND age <= #{maxAge}
</if>
</where>
</select>
3.2 choose/when/otherwise 标签
<!-- choose 相当于 switch-case -->
<select id="findUsersByChoice" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="username != null and username != ''">
username LIKE CONCAT('%', #{username}, '%')
</when>
<when test="age != null">
age = #{age}
</when>
<otherwise>
status = 1
</otherwise>
</choose>
</where>
</select>
3.3 where 标签
<!-- where 标签会自动处理 AND/OR 前缀 -->
<select id="findUsers" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
</select>
3.4 set 标签
<!-- set 标签用于动态更新语句 -->
<update id="updateUser" parameterType="User">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>
3.5 foreach 标签
<!-- 遍历集合 -->
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO user (username, age, create_time)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.age}, NOW())
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdate">
<foreach collection="list" item="user" separator=";">
UPDATE user
SET username = #{user.username},
age = #{user.age}
WHERE id = #{user.id}
</foreach>
</update>
<!-- 遍历 Map 参数 -->
<select id="selectByMap" resultType="User">
SELECT * FROM user
WHERE 1 = 1
<foreach collection="params" index="key" item="value">
<if test="key == 'username'">
AND username = #{value}
</if>
<if test="key == 'age'">
AND age = #{value}
</if>
</foreach>
</select>
3.6 trim 标签
<!-- trim 标签可以自定义前缀、后缀和去除的字符 -->
<select id="findUsers" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null">
AND email = #{email}
</if>
</trim>
</select>
<!-- trim 用于更新 -->
<update id="updateUser">
UPDATE user
<trim prefix="SET" suffixOverrides=",">
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
update_time = NOW(),
</trim>
WHERE id = #{id}
</update>
3.7 bind 标签
<!-- bind 用于创建绑定变量 -->
<select id="findUsers" resultType="User">
<bind name="pattern" value="'%' + username + '%'"/>
SELECT * FROM user
WHERE username LIKE #{pattern}
</select>
<!-- 复杂绑定 -->
<select id="findUsers" resultType="User">
<bind name="start" value="(page - 1) * size"/>
<bind name="end" value="page * size"/>
SELECT * FROM user
ORDER BY id DESC
LIMIT #{start}, #{size}
</select>
3.8 sql 与 include
<!-- 定义可重用的 SQL 片段 -->
<sql id="userColumns">
id, username, password, age, email, create_time, update_time
</sql>
<sql id="userWhere">
<where>
<if test="username != null">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</sql>
<!-- 引用 SQL 片段 -->
<select id="selectUsers" resultType="User">
SELECT <include refid="userColumns"/>
FROM user
<include refid="userWhere"/>
</select>
<!-- 带参数的 SQL 片段 -->
<sql id="userColumnsWithAlias">
${alias}.id, ${alias}.username, ${alias}.age
</sql>
<select id="selectUsersWithAlias" resultType="User">
SELECT <include refid="userColumnsWithAlias">
<property name="alias" value="u"/>
</include>
FROM user u
</select>