主题
MyBatis
MyBatis 是以 SQL 为中心的持久层框架:开发者编写 SQL,框架负责参数绑定、执行、结果映射和资源管理。它不替代数据库设计;索引、事务边界和查询性能仍由应用与数据库共同负责。
核心对象与生命周期
| 对象 | 职责 | 推荐生命周期 |
|---|---|---|
SqlSessionFactoryBuilder | 读取配置并构建工厂 | 方法内临时使用 |
SqlSessionFactory | 创建 SqlSession | 应用级单例 |
SqlSession | 执行映射语句、管理一级缓存与事务 | 方法或请求级;非线程安全 |
| Mapper | 映射 SQL 的接口代理 | 不脱离创建它的 SqlSession 使用 |
在纯 MyBatis 应用中,使用 try-with-resources 确保会话关闭:
java
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(1L);
}在 Spring 或 Spring Boot 中,不要自行保存或手动关闭 SqlSession;交由 MyBatis-Spring 的 SqlSessionTemplate 与 Spring 事务管理。
Spring Boot 集成
常规项目使用 mybatis-spring-boot-starter,并由 Spring Boot 的依赖管理统一选择兼容版本。Starter 会根据 DataSource 创建 SqlSessionFactory、SqlSessionTemplate,并注册扫描到的 Mapper。
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>配置数据源和 MyBatis:
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/app
username: app
password: ${DB_PASSWORD}
mybatis:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
default-statement-timeout: 30数据库凭据使用环境变量或密钥管理服务,不提交到仓库。map-underscore-to-camel-case 仅影响自动映射;复杂查询仍应明确字段别名或 resultMap。
Mapper 可逐个标记 @Mapper,也可在配置类集中扫描:
java
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.example.app.persistence.mapper")
class MyBatisConfig {}Mapper 与基础查询
简单、稳定的 SQL 可以写在注解中:
java
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("""
select id, display_name, email
from users
where id = #{id}
""")
User findById(@Param("id") long id);
}复杂动态条件、可复用 SQL 片段、多表嵌套映射优先使用 XML。Mapper XML 的 namespace 与 Mapper 接口全限定名对应,语句 id 与接口方法对应:
xml
<?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.app.persistence.mapper.UserMapper">
<select id="findById" resultType="com.example.app.user.User">
select id, display_name as displayName, email
from users
where id = #{id}
</select>
</mapper>resultType 适用于列名可直接映射到属性的简单结果。需要组合对象、关联集合或非标准列名时定义 resultMap;同一语句使用其一,不同时使用两者。
参数绑定与 SQL 注入
#{name}:绑定为PreparedStatement参数。值条件一律优先使用它。${name}:直接进行文本替换,不能用于不可信输入;常见于必须拼接的标识符或 SQL 片段。
例如排序字段不能用 #{} 绑定,但必须由服务端白名单映射,不能直接接收客户端字符串:
java
public enum UserSort {
CREATED_AT("created_at"),
DISPLAY_NAME("display_name");
private final String column;
UserSort(String column) {
this.column = column;
}
public String column() {
return column;
}
}xml
<select id="findPage" resultType="com.example.app.user.User">
select id, display_name as displayName, email
from users
order by ${sort.column} asc
limit #{limit} offset #{offset}
</select>上例中 ${sort.column} 的值来自受控枚举;limit、offset 等普通值仍使用 #{}。
动态 SQL
动态 SQL 用于组合条件,不用于拼接用户输入。常用标签:
<where>:自动添加WHERE,并处理开头多余的AND、OR。<set>:更新时自动处理逗号。<foreach>:展开受控集合,如IN条件。<choose>:表达互斥分支。
xml
<select id="findByCondition" resultType="com.example.app.user.User">
select id, display_name as displayName, email
from users
<where>
<if test="email != null and email != ''">
and email = #{email}
</if>
<if test="ids != null and !ids.isEmpty()">
and id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>集合为空时的业务语义应在 Service 层先确定;不要让空集合意外生成全表查询或非法 SQL。
写入、主键与事务
MySQL 等支持 JDBC 返回生成键的数据库,可在插入语句中使用 useGeneratedKeys 与 keyProperty:
xml
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into users (display_name, email)
values (#{displayName}, #{email})
</insert>事务边界放在业务方法,而不是 Mapper 方法。多个 Mapper 操作需要原子性时,由 Spring 的 @Transactional 包裹服务方法:
java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
public void create(User user) {
userMapper.insert(user);
}
}不要在 Spring 管理的事务中调用 SqlSession#commit() 或 close();这会破坏由框架统一管理的提交、回滚与资源释放。
缓存与性能
- 一级缓存属于
SqlSession,生命周期短,不应作为跨请求缓存方案。 - 二级缓存以 Mapper namespace 为单位,需要显式评估一致性、失效策略和序列化成本后再启用。
- 关联查询优先评估 N+1 问题;根据访问模式选择连接查询、批量查询或延迟加载。
- 为热点 SQL 建立索引、限制返回列、分页并设置合理超时;不要用缓存掩盖慢 SQL。
复习要点
- Mapper 接口表达参数和返回类型;简单 SQL 用注解,复杂映射与动态 SQL 用 XML。
- 值参数使用
#{};${}只允许受控白名单值。 SqlSession非线程安全;在 Spring 中由框架管理其生命周期。- 事务定义在 Service 层,Mapper 只负责数据访问。
