MyBatis 集成
约 1344 字大约 4 分钟
布欧-Lewyon
2026-05-15
首页 › Spring Boot › 数据访问 › MyBatis 集成
MyBatis 是 Java 生态中最流行的半自动化 ORM 框架之一——开发人员手写 SQL、框架只负责参数映射与结果映射,不替开发者生成 SQL。本节覆盖 Spring Boot 集成 MyBatis 的完整流程。
起步依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>Spring Boot 3.x 使用 mybatis-spring-boot-starter 的 3.x 版本(包名 jakarta.* 兼容)。该 starter 自动配置 SqlSessionFactory 和 SqlSessionTemplate。
基础配置
mybatis:
mapper-locations: classpath:mapper/*.xml # XML 映射文件路径
type-aliases-package: com.example.demo.model # 实体类别名包
configuration:
map-underscore-to-camel-case: true # 下划线→驼峰自动映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志Mapper 接口与 XML
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") Long id);
List<User> findAll();
int insert(User user);
int update(User user);
int deleteById(@Param("id") Long id);
}<!-- src/main/resources/mapper/UserMapper.xml -->
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="created_at" property="createdAt"/>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM users ORDER BY id
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users(user_name, age) VALUES(#{userName}, #{age})
</insert>
<update id="update">
UPDATE users SET user_name = #{userName}, age = #{age} WHERE id = #{id}
</update>
<delete id="deleteById">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>纯注解模式(无 XML)
@Mapper
public interface UserMapper {
@Results(id = "userMap", value = {
@Result(column = "id", property = "id"),
@Result(column = "user_name", property = "userName"),
@Result(column = "created_at", property = "createdAt")
})
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);
@Select("SELECT * FROM users")
@ResultMap("userMap")
List<User> findAll();
@Insert("INSERT INTO users(user_name, age) VALUES(#{userName}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
}生产意识:纯注解模式在 SQL 较复杂时(多表 JOIN、子查询)可读性急剧下降。推荐复杂 SQL 用 XML(利于 DBA review),简单 CRUD 用注解。XML 中的 SQL 支持
${}(直接替换,有 SQL 注入风险)和#{}(预编译占位),一律使用#{},禁止${}拼接用户输入。
与 JPA 选型对比
| 对比维度 | MyBatis | Spring Data JPA |
|---|---|---|
| SQL 控制 | 完全手写 SQL | JPQL 或方法名推导 |
| 复杂查询 | 强——原生 SQL、多表 JOIN、子查询 | 弱——JPQL 复杂查询难维护 |
| 动态 SQL | <if>/<foreach>/<where> 标签 | @Query + SpEL 或 Specification |
| 实体映射 | ResultMap 手写 | 自动字段映射 |
| 学习曲线 | 中等(需学 XML 标签语义) | 低(基础 CRUD 无需写 SQL) |
| 维护性 | SQL 变更需改 XML | 实体变更自动适配 |
| 批量操作 | <foreach> 或 BatchExecutor | saveAll() 内置 |
选型建议:SQL 复杂、查询多变、DBA 主导 → MyBatis;实体关系简单、CRUD 为主 → JPA。也可以在同一个项目中混合使用:JPA 为主,MyBatis 负责复杂统计查询。
源码与架构:MyBatis-Spring 集成原理
MyBatis 与 Spring 集成的核心围绕三个关键组件:
mybatis-spring-boot-starter
│
├── MybatisAutoConfiguration
│ ├── 自动创建 SqlSessionFactory
│ │ └── 读取 mybatis.mapper-locations → 解析 XML
│ │ └── 读取 mybatis.type-aliases-package → 注册别名
│ └── 自动创建 SqlSessionTemplate(线程安全)
│
├── @MapperScan / @Mapper
│ └── MapperScannerRegistrar
│ └── 扫描接口 → MapperFactoryBean
│
└── MapperFactoryBean.getObject()
└── 返回 JDK 动态代理 → MapperProxySqlSessionFactory 与 SqlSessionTemplate
SqlSessionFactory 是 MyBatis 的核心,每个数据库操作最终都是通过 SqlSession 执行的。但在 Spring 管理下,不会直接使用 SqlSessionFactory.openSession()——而是使用 SqlSessionTemplate。
// SqlSessionTemplate 线程安全设计(源码简化)
public class SqlSessionTemplate implements SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSessionProxy sqlSessionProxy; // JDK 动态代理
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor() // ★ 核心拦截器
);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
return sqlSessionProxy.selectOne(statement, parameter);
}
// SqlSessionInterceptor → 每个方法调用时
// ① 从 Spring 事务同步管理器获取当前 SqlSession
// ② 有事务 → 复用事务范围内的 SqlSession
// ③ 无事务 → 新建 SqlSession,用完后关闭
}线程安全的关键:SqlSessionTemplate 将所有调用委托给代理,代理内部判断当前是否有 Spring 事务——有则复用事务范围内的 SqlSession(同一事务内共用一个数据库连接),无则每次新建并在调用后关闭。这保证了一个线程中的多个 Mapper 调用在同一事务下共享同一个数据库连接。
MapperProxy 动态代理
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 判断是否 Object 方法 → 直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 根据方法签名 → 查找对应的 SQL 语句
// ① 找 @Select / @Insert / @Update / @Delete 注解
// ② 或 XML namespace.methodId 映射
// ③ 执行 sqlSession.selectOne / insert / update / delete...
return sqlSession.selectOne(methodId, args);
}
}生产意识:MyBatis 的
SqlSessionFactory可以在同一个 Spring 上下文中配置多个(多数据源场景),需要分别为每个DataSource声明SqlSessionFactoryBean。此时不能使用@MapperScan的默认配置——需要指定sqlSessionFactoryRef:@MapperScan(basePackages = "com.example.demo.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory") @MapperScan(basePackages = "com.example.demo.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")多数据源下的事务管理需要用
@Transactional(transactionManager = "primaryTransactionManager")显式指定事务管理器。
小结
mybatis-spring-boot-starter自动配置 SqlSessionFactory + SqlSessionTemplate。- Mapper 接口 + XML(推荐)或纯注解两种编程模型。
- 与 JPA 的选型:复杂查询选 MyBatis,CRUD 为主选 JPA。
- MyBatis 线程安全:SqlSessionTemplate 用代理模式实现 SqlSession 的事务范围复用。
- 易错:XML 映射文件路径必须与
mybatis.mapper-locations匹配;resultMap的id字段必须与实体主键对应;#{}预编译防注入,${}有注入风险。Mapper 接口的方法不能重载(重名方法导致 XML 映射冲突)。 - 思考任务:创建
UserMapper接口 + XML,实现完整 CRUD;对比用纯注解模式改写,体验两种方式在 SQL 可读性上的差异。
上一节:Spring Data JPA
下一节:MyBatis-Plus
