详细介绍:Spring 整合 MyBatis 深度详解(原理 + 实操 + 源码级解析)
引言
在 Java 后端开发领域,Spring 与 MyBatis 的整合(SSM 架构核心组成)是企业级应用的标准技术方案,也是湖南大学计算机科学与技术专业 “Java EE 框架开发”“企业级应用设计” 等核心课程的重点实践内容。MyBatis 专注于数据访问层(DAO)的 SQL 映射与执行,Spring 则擅长依赖注入(DI)、面向切面编程(AOP)、声明式事务等企业级特性,二者的整合本质是让 Spring 的 IOC 容器接管 MyBatis 的核心组件生命周期,通过标准化配置实现 “数据访问层与业务层解耦、开发效率与可维护性双提升” 。
本文将从 “整合原理→环境搭建→分步实操(XML / 注解双方案)→源码解析→性能优化→问题排查” 六个维度,进行 7000 字以上的深度拆解,不仅覆盖企业开发全流程,还融入源码级原理分析,适配高校课程深度学习与职场技术沉淀需求。
一、整合核心原理与设计思想
1. 整合的核心矛盾与解决思路
MyBatis 原生开发的痛点的:
- 组件创建繁琐:需手动加载配置文件、创建
SqlSessionFactory、获取SqlSession、生成 Mapper 代理对象,代码冗余且耦合度高; - 事务管理混乱:需手动控制
SqlSession的提交 / 回滚,多数据源场景下一致性难以保障; - 资源管理低效:数据库连接需手动维护,无连接池复用机制,性能瓶颈明显。
Spring 的解决方案:
- 组件托管:通过 IOC 容器接管
DataSource、SqlSessionFactory、Mapper代理对象,自动管理对象创建、依赖注入与销毁; - 事务统一管控:基于 AOP 实现声明式事务,通过注解或 XML 配置即可完成事务规则定义,无需侵入业务代码;
- 资源池化:整合第三方连接池(Druid/C3P0),优化数据库连接的创建与复用,提升系统并发能力。
2. 整合的核心组件与依赖关系
整合后核心组件的依赖链:DataSource → SqlSessionFactory → SqlSession → Mapper代理对象 → Service,各组件职责与 Spring 接管逻辑如下:
| 组件 | 核心职责 | MyBatis 原生方式 | Spring 整合方式 |
|---|---|---|---|
| DataSource | 管理数据库连接(连接池核心) | 手动配置environments标签 | Spring IOC 容器管理,支持连接池参数优化 |
| SqlSessionFactory | 创建 SqlSession(MyBatis 核心工厂) | 手动加载mybatis-config.xml创建 | 通过SqlSessionFactoryBean自动创建,依赖 DataSource |
| SqlSession | 数据库操作会话(执行 SQL、管理事务) | 手动调用openSession()获取 | Spring 通过SqlSessionTemplate管理,线程安全 |
| Mapper 代理对象 | 映射 SQL 语句到接口方法 | 手动通过SqlSession.getMapper()获取 | MapperScannerConfigurer自动扫描接口,生成代理对象并注入 IOC |
| PlatformTransactionManager | 事务管理器(提交 / 回滚) | 无原生支持,需手动控制 | Spring 提供DataSourceTransactionManager,基于 AOP 实现声明式事务 |
3. 整合的关键桥梁:mybatis-spring.jar
mybatis-spring是官方提供的整合中间件,核心作用是 “打通 Spring 与 MyBatis 的组件通信”,其核心类如下:
SqlSessionFactoryBean:替代 MyBatis 原生的SqlSessionFactoryBuilder,将DataSource、MyBatis 配置、Mapper 映射文件等参数封装,由 Spring IOC 容器初始化时创建SqlSessionFactory;SqlSessionTemplate:Spring 管理的SqlSession实现类,线程安全(原生SqlSession非线程安全),通过SqlSessionFactory获取SqlSession,并整合事务管理逻辑;MapperScannerConfigurer:基于 Spring 的BeanDefinitionRegistryPostProcessor,扫描指定包下的 Mapper 接口,为每个接口生成 MyBatis 动态代理对象,并注册到 IOC 容器;MapperFactoryBean:针对单个 Mapper 接口的工厂类,可手动配置单个 Mapper 的代理对象(适用于少量 Mapper 场景)。
二、环境准备(Maven 依赖 + 项目结构)
1. 项目结构设计(标准 Maven 工程)
plaintext
src/
├── main/
│ ├── java/
│ │ └── com/hnu/it/ssm/
│ │ ├── config/ // 配置类(注解方案)
│ │ ├── controller/ // 控制层(接收请求)
│ │ ├── service/ // 业务层(接口+实现)
│ │ ├── mapper/ // Mapper接口(数据访问层)
│ │ ├── pojo/ // 实体类(与数据库表映射)
│ │ ├── exception/ // 全局异常处理
│ │ └── util/ // 工具类
│ └── resources/
│ ├── applicationContext.xml // Spring核心配置(XML方案)
│ ├── mybatis-config.xml // MyBatis全局配置
│ ├── mapper/ // Mapper映射文件(XML方案)
│ ├── jdbc.properties // 数据库连接配置
│ └── logback.xml // 日志配置(SLF4J+Logback)
└── test/
└── java/
└── com/hnu/it/ssm/
└── MapperTest.java // 整合测试类
2. 完整 Maven 依赖配置(pom.xml)
需引入 Spring 核心、MyBatis 核心、整合中间件、数据库驱动、连接池、日志等依赖,版本需保持兼容(Spring 5.x 搭配 MyBatis 3.5.x、mybatis-spring 2.0.x):
xml
4.0.0
com.hnu.it
spring-mybatis-integration
1.0-SNAPSHOT
jar
5.3.28
3.5.13
2.0.10
8.0.36
1.2.20
4.13.2
1.4.11
2.0.7
org.springframework
spring-context
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-aop
${spring.version}
org.springframework
spring-aspects
${spring.version}
org.mybatis
mybatis
${mybatis.version}
org.mybatis
mybatis-spring
${mybatis-spring.version}
mysql
mysql-connector-java
${mysql.version}
runtime
com.alibaba
druid
${druid.version}
org.slf4j
slf4j-api
${slf4j.version}
ch.qos.logback
logback-classic
${logback.version}
ch.qos.logback
logback-core
${logback.version}
org.springframework
spring-test
${spring.version}
test
junit
junit
${junit.version}
test
org.projectlombok
lombok
1.18.30
provided
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
1.8
1.8
UTF-8
src/main/resources
**/*.xml
**/*.properties
true
src/main/java
**/*.xml
true
3. 基础配置文件准备
(1)数据库连接配置(jdbc.properties)
将数据库连接信息抽离为独立配置文件,便于维护:
properties
# 数据库驱动(MySQL8必须使用com.mysql.cj.jdbc.Driver)
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库URL(指定时区、编码、SSL配置)
jdbc.url=jdbc:mysql://localhost:3306/ssm_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8&allowPublicKeyRetrieval=true
# 数据库用户名
jdbc.username=root
# 数据库密码
jdbc.password=123456
# Druid连接池参数优化
# 初始连接数
druid.initialSize=5
# 最大活跃连接数
druid.maxActive=20
# 最大等待时间(毫秒)
druid.maxWait=60000
# 最小空闲连接数
druid.minIdle=3
# 连接检测间隔(毫秒)
druid.timeBetweenEvictionRunsMillis=60000
# 连接最小生存时间(毫秒)
druid.minEvictableIdleTimeMillis=300000
# 连接有效性检测SQL
druid.validationQuery=SELECT 1 FROM DUAL
# 空闲时检测连接有效性
druid.testWhileIdle=true
# 申请连接时检测有效性(建议关闭,影响性能)
druid.testOnBorrow=false
# 归还连接时检测有效性(建议关闭,影响性能)
druid.testOnReturn=false
# 支持PSCache(提升查询性能)
druid.poolPreparedStatements=true
# PSCache最大缓存数
druid.maxPoolPreparedStatementPerConnectionSize=20
# 连接属性配置
druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
(2)MyBatis 全局配置(mybatis-config.xml)
MyBatis 全局配置文件仅保留 “非数据源相关” 的全局设置(数据源由 Spring 管理),核心配置包括驼峰命名映射、日志实现、别名配置等:
xml
(3)日志配置(logback.xml)
通过日志框架打印 SQL 执行细节,便于开发调试:
xml
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
logs/ssm-log.log
logs/ssm-log.%d{yyyy-MM-dd}.log
30
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
二、XML 配置方案:传统整合流程(企业主流)
XML 配置方案的核心是通过applicationContext.xml文件,将 Spring 与 MyBatis 的组件逐一整合,步骤清晰、易于维护,是企业开发的主流选择。
步骤 1:配置 Spring 读取外部属性文件
通过context:property-placeholder标签加载jdbc.properties,便于在配置中通过${key}引用数据库连接信息:
xml
步骤 2:配置数据源(Druid 连接池)
数据源是连接数据库的基础,整合后由 Spring 管理,替代 MyBatis 原生的environments配置。此处选用 Druid 连接池(企业级首选,性能优于 Spring 自带的 BasicDataSource):
xml
原理解析:DruidDataSource 通过init-method="init"初始化连接池,destroy-method="close"销毁时释放资源,连接池参数(如 maxActive、minIdle)通过 Spring 的依赖注入赋值,确保连接池的高效运行。
步骤 3:配置 SqlSessionFactory(MyBatis 核心工厂)
SqlSessionFactory是 MyBatis 的核心工厂,负责创建SqlSession(数据库操作会话)。整合后,通过mybatis-spring提供的SqlSessionFactoryBean由 Spring 管理,无需手动调用SqlSessionFactoryBuilder创建:
xml
核心原理:
SqlSessionFactoryBean实现了 Spring 的FactoryBean接口,Spring 初始化时会调用其getObject()方法创建SqlSessionFactory实例;dataSource是必填依赖,SqlSessionFactory通过数据源获取数据库连接;mapperLocations指定 Mapper 映射文件路径,Spring 会自动扫描并加载这些文件,无需在 MyBatis 配置中重复配置mappers标签;configuration属性可直接配置 MyBatis 的全局参数,优先级高于mybatis-config.xml中的settings。
步骤 4:配置 Mapper 接口扫描(自动生成代理对象)
MyBatis 原生开发中,需通过SqlSession.getMapper(XXXMapper.class)获取 Mapper 代理对象,整合后可通过MapperScannerConfigurer让 Spring 自动扫描 Mapper 接口,生成代理对象并注册到 IOC 容器,后续可通过依赖注入直接使用:
xml
深度解析:
MapperScannerConfigurer实现了 Spring 的BeanDefinitionRegistryPostProcessor接口,在 Spring 容器初始化时,会扫描basePackage下的所有接口,为每个接口创建BeanDefinition;- 扫描到的 Mapper 接口不会直接实例化,而是通过
SqlSessionTemplate创建 MyBatis 的动态代理对象(底层基于 JDK 动态代理); SqlSessionTemplate是线程安全的SqlSession包装类,Spring 通过它管理SqlSession的生命周期,确保每个线程使用独立的SqlSession,避免线程安全问题;- Mapper 代理对象在 IOC 容器中的 beanId 默认是接口名首字母小写(如
UserMapper→userMapper),可通过@Repository注解自定义 beanId。
步骤 5:配置 Spring 组件扫描(Service/Controller 层)
开启 Spring 的组件扫描,自动扫描@Service、@Controller、@Component等注解标记的类,将其注册到 IOC 容器:
xml
步骤 6:配置声明式事务(AOP 实现)
数据库操作需事务保障(如转账、批量操作),Spring 通过 AOP 实现声明式事务,无需手动控制SqlSession的提交 / 回滚。核心是配置事务管理器和事务规则:
xml
事务配置核心参数解析:
- 事务传播行为(propagation):
REQUIRED:如果当前存在事务,加入事务;否则创建新事务(增删改默认);SUPPORTS:如果当前存在事务,加入事务;否则以非事务方式执行(查询默认);- 其他传播行为:
REQUIRES_NEW(创建新事务,暂停当前事务)、NOT_SUPPORTED(非事务方式执行)等。
- 事务隔离级别(isolation):
READ_COMMITTED:读取已提交的数据,避免脏读,是大多数数据库默认隔离级别(如 MySQL);- 其他隔离级别:
READ_UNCOMMITTED(读取未提交数据)、REPEATABLE_READ(可重复读)、SERIALIZABLE(串行化)。
rollback-for:指定触发事务回滚的异常类型(默认仅回滚 RuntimeException 及其子类,需显式指定 Exception 确保所有异常都回滚)。read-only:查询操作设为true,Spring 会优化事务性能(关闭事务写入能力)。
步骤 7:编写核心业务组件(Pojo→Mapper→Service)
(1)实体类(Pojo):与数据库表映射
假设数据库存在t_user表,创建对应的实体类User(使用 Lombok 简化代码):
java
运行
package com.hnu.it.ssm.pojo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户实体类(与t_user表映射)
*/
@Data // Lombok注解:自动生成getter/setter/toString等方法
public class User implements Serializable {
private Integer id; // 主键ID
private String userName; // 用户名(对应数据库字段user_name)
private String password; // 密码
private Integer age; // 年龄
private String email; // 邮箱
private Date createTime; // 创建时间(对应数据库字段create_time)
private Date updateTime; // 更新时间(对应数据库字段update_time)
}
(2)Mapper 接口:数据访问层接口
定义UserMapper接口,声明数据访问方法(无需实现类,由 MyBatis 动态代理生成):
java
运行
package com.hnu.it.ssm.mapper;
import com.hnu.it.ssm.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户Mapper接口(数据访问层)
*/
public interface UserMapper {
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户实体
*/
User selectById(@Param("id") Integer id);
/**
* 根据用户名查询用户
* @param userName 用户名
* @return 用户实体
*/
User selectByUserName(@Param("userName") String userName);
/**
* 查询所有用户(支持分页)
* @param startIndex 起始索引
* @param pageSize 每页条数
* @return 用户列表
*/
List selectAll(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
/**
* 新增用户
* @param user 用户实体
* @return 影响行数
*/
int insert(User user);
/**
* 更新用户信息
* @param user 用户实体
* @return 影响行数
*/
int update(User user);
/**
* 根据ID删除用户
* @param id 用户ID
* @return 影响行数
*/
int deleteById(@Param("id") Integer id);
/**
* 批量删除用户
* @param ids 用户ID数组
* @return 影响行数
*/
int batchDelete(@Param("ids") Integer[] ids);
}
(3)Mapper 映射文件:SQL 语句定义
创建UserMapper.xml文件(放在resources/mapper目录下),编写与 Mapper 接口方法对应的 SQL 语句:
xml
INSERT INTO t_user (user_name, password, age, email, create_time, update_time)
VALUES (#{userName}, #{password}, #{age}, #{email}, NOW(), NOW())
UPDATE t_user
SET user_name = #{userName},
password = #{password},
age = #{age},
email = #{email},
update_time = NOW()
WHERE id = #{id}
DELETE FROM t_user WHERE id = #{id}
DELETE FROM t_user
WHERE id IN
#{id}
Mapper 映射文件核心规则:
namespace必须与 Mapper 接口全类名完全一致;- 标签
id必须与接口方法名完全一致; parameterType(输入参数类型)、resultType/resultMap(输出参数类型)需与方法参数、返回值类型匹配;resultMap用于解决数据库字段名与实体类属性名不一致问题(如user_name→userName),优先级高于驼峰命名映射。
(4)Service 层:业务逻辑封装
Service 层负责封装业务逻辑,通过依赖注入(@Autowired)获取 Mapper 代理对象,调用数据访问方法。
① Service 接口:
java
运行
package com.hnu.it.ssm.service;
import com.hnu.it.ssm.pojo.User;
import java.util.List;
/**
* 用户Service接口(业务层)
*/
public interface UserService {
/**
* 根据ID查询用户
* @param id 用户ID
* @return 用户实体
*/
User getUserById(Integer id);
/**
* 查询所有用户(分页)
* @param pageNum 页码(从1开始)
* @param pageSize 每页条数
* @return 用户列表
*/
List getAllUsers(Integer pageNum, Integer pageSize);
/**
* 新增用户
* @param user 用户实体
* @return 新增成功的用户ID
*/
Integer addUser(User user);
/**
* 更新用户信息
* @param user 用户实体
* @return 是否更新成功(true/false)
*/
Boolean updateUser(User user);
/**
* 批量删除用户
* @param ids 用户ID数组
* @return 删除成功的条数
*/
Integer batchDeleteUsers(Integer[] ids);
}
② Service 实现类:
java
运行
package com.hnu.it.ssm.service.impl;
import com.hnu.it.ssm.mapper.UserMapper;
import com.hnu.it.ssm.pojo.User;
import com.hnu.it.ssm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 用户Service实现类(业务层实现)
*/
@Service // 标记为Spring服务组件,被组件扫描到并注册到IOC
@Transactional // 类级事务注解:所有方法默认应用事务规则
public class UserServiceImpl implements UserService {
// 依赖注入Mapper代理对象(Spring自动从IOC容器中获取)
@Autowired
private UserMapper userMapper;
@Override
@Transactional(readOnly = true) // 方法级事务注解:覆盖类级配置,查询设为只读
public User getUserById(Integer id) {
// 直接调用Mapper接口方法(代理对象自动执行SQL)
return userMapper.selectById(id);
}
@Override
@Transactional(readOnly = true)
public List getAllUsers(Integer pageNum, Integer pageSize) {
// 计算分页起始索引(pageNum从1开始)
Integer startIndex = (pageNum - 1) * pageSize;
return userMapper.selectAll(startIndex, pageSize);
}
@Override
public Integer addUser(User user) {
// 调用Mapper新增方法
userMapper.insert(user);
// 新增成功后,实体类id会被MyBatis自动赋值(useGeneratedKeys="true")
return user.getId();
}
@Override
public Boolean updateUser(User user) {
// 调用Mapper更新方法,返回影响行数
int rows = userMapper.update(user);
return rows > 0;
}
@Override
@Transactional(rollbackFor = Exception.class) // 显式指定回滚异常类型
public Integer batchDeleteUsers(Integer[] ids) {
// 模拟业务异常:若ids为空,抛出异常,事务回滚
if (ids == null || ids.length == 0) {
throw new IllegalArgumentException("删除ID数组不能为空");
}
// 调用Mapper批量删除方法
return userMapper.batchDelete(ids);
}
}
Service 层核心要点:
- 用
@Service注解标记实现类,确保被 Spring 组件扫描到; - 用
@Autowired注入 Mapper 代理对象,无需手动创建; - 事务注解可加在类上(所有方法默认应用)或方法上(覆盖类级配置),查询方法建议设为
readOnly=true提升性能; - 业务逻辑中可加入参数校验、异常处理等逻辑,异常抛出后事务会自动回滚(需配置
rollbackFor)。
步骤 8:整合测试(JUnit+Spring Test)
使用 Spring Test 整合 JUnit,直接从 IOC 容器中获取 Service 对象,测试整合效果:
java
运行
package com.hnu.it.ssm;
import com.hnu.it.ssm.pojo.User;
import com.hnu.it.ssm.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* Spring整合MyBatis测试类
*/
// 指定Spring测试运行器
@RunWith(SpringJUnit4ClassRunner.class)
// 加载Spring核心配置文件
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class UserServiceTest {
// 注入UserService对象(Spring IOC容器管理)
@Autowired
private UserService userService;
/**
* 测试根据ID查询用户
*/
@Test
public void testFindUserById() {
User user = userService.getUserById(1);
System.out.println("查询到的用户:" + user);
// 断言:验证查询结果不为空
assert user != null;
}
/**
* 测试分页查询所有用户
*/
@Test
public void testFindAllUsers() {
List userList = userService.getAllUsers(1, 5);
System.out.println("第1页用户列表(5条):" + userList);
assert userList.size() <= 5;
}
/**
* 测试新增用户
*/
@Test
public void testAddUser() {
User user = new User();
user.setUserName("test_user");
user.setPassword("123456");
user.setAge(25);
user.setEmail("test@hnu.edu.cn");
Integer userId = userService.addUser(user);
System.out.println("新增用户ID:" + userId);
assert userId != null;
}
/**
* 测试批量删除用户(含事务回滚测试)
*/
@Test(expected = IllegalArgumentException.class)
public void testBatchDeleteUsers() {
// 测试1:正常删除(IDs为[2,3])
Integer[] ids = {2, 3};
Integer deleteCount = userService.batchDeleteUsers(ids);
System.out.println("批量删除成功条数:" + deleteCount);
assert deleteCount == 2;
// 测试2:传入空IDs,触发异常,事务回滚
userService.batchDeleteUsers(null);
}
}
测试结果验证:
- 运行测试方法,控制台会打印 SQL 执行日志(如
DEBUG com.hnu.it.ssm.mapper.UserMapper.selectById - ==> Preparing: SELECT id, user_name, password, age, email, create_time, update_time FROM t_user WHERE id = ?); - 正常情况下,新增、更新、删除操作会提交事务,数据库数据发生变化;
- 当抛出
IllegalArgumentException时,事务回滚,数据库数据不变,验证事务配置生效。
三、注解配置方案:无 XML 全注解整合(Spring Boot 前奏)
随着 Spring Boot 的普及,无 XML 的注解配置方案逐渐成为趋势。该方案通过@Configuration、@Bean等注解替代 XML 配置,核心逻辑与 XML 方案一致,仅配置方式不同。
步骤 1:编写 Spring 核心配置类(SpringConfig)
java
运行
package com.hnu.it.ssm.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.IOException;
/**
* Spring核心配置类(替代applicationContext.xml)
*/
@Configuration // 标记为Spring配置类
@ComponentScan("com.hnu.it.ssm") // 组件扫描(扫描Service、Controller等)
@PropertySource("classpath:jdbc.properties") // 加载外部属性文件
@MapperScan("com.hnu.it.ssm.mapper") // Mapper接口扫描(替代MapperScannerConfigurer)
@EnableTransactionManagement // 开启声明式事务(替代tx:annotation-driven)
public class SpringConfig {
// 从jdbc.properties中读取配置(@Value注解注入)
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${druid.initialSize}")
private Integer initialSize;
@Value("${druid.maxActive}")
private Integer maxActive;
@Value("${druid.maxWait}")
private Long maxWait;
@Value("${druid.minIdle}")
private Integer minIdle;
/**
* 1. 配置Druid数据源(替代XML中的dataSource bean)
*/
@Bean(destroyMethod = "close")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
// 核心连接参数
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
// 连接池优化参数
dataSource.setInitialSize(initialSize);
dataSource.setMaxActive(maxActive);
dataSource.setMaxWait(maxWait);
dataSource.setMinIdle(minIdle);
// 其他参数可按需配置
dataSource.setTestWhileIdle(true);
dataSource.setValidationQuery("SELECT 1 FROM DUAL");
return dataSource;
}
/**
* 2. 配置SqlSessionFactory(替代XML中的sqlSessionFactory bean)
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 关联数据源
factoryBean.setDataSource(dataSource);
// 关联MyBatis全局配置文件
factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver()
.getResource("classpath:mybatis-config.xml"));
// 配置Mapper映射文件路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
// 配置事务工厂(Spring管理事务)
factoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return factoryBean;
}
/**
* 3. 配置事务管理器(替代XML中的transactionManager bean)
*/
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
步骤 2:编写 MyBatis 注解式 Mapper(无需 XML 映射文件)
MyBatis 支持通过@Select、@Insert等注解直接在 Mapper 接口中编写 SQL,无需单独的 XML 映射文件,简化配置:
java
运行
package com.hnu.it.ssm.mapper;
import com.hnu.it.ssm.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
* 注解式Mapper接口(无需XML映射文件)
*/
@Mapper // 标记为MyBatis Mapper接口(与@MapperScan配合使用)
public interface UserAnnotationMapper {
/**
* 根据ID查询用户(@Select注解)
*/
@Select("SELECT id, user_name AS userName, password, age, email, create_time AS createTime, update_time AS updateTime " +
"FROM t_user WHERE id = #{id}")
User selectById(Integer id);
/**
* 新增用户(@Insert注解)
*/
@Insert("INSERT INTO t_user (user_name, password, age, email, create_time, update_time) " +
"VALUES (#{userName}, #{password}, #{age}, #{email}, NOW(), NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id") // 返回自增主键
int insert(User user);
/**
* 更新用户(@Update注解)
*/
@Update("UPDATE t_user SET user_name = #{userName}, password = #{password}, age = #{age}, " +
"email = #{email}, update_time = NOW() WHERE id = #{id}")
int update(User user);
/**
* 删除用户(@Delete注解)
*/
@Delete("DELETE FROM t_user WHERE id = #{id}")
int deleteById(Integer id);
/**
* 分页查询所有用户(@Results注解定义结果映射)
*/
@Select("SELECT id, user_name, password, age, email, create_time, update_time " +
"FROM t_user LIMIT #{startIndex}, #{pageSize}")
@Results({
@Result(column = "id", property = "id", id = true),
@Result(column = "user_name", property = "userName"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
List selectAll(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
}
步骤 3:注解式配置测试
java
运行
package com.hnu.it.ssm;
import com.hnu.it.ssm.config.SpringConfig;
import com.hnu.it.ssm.mapper.UserAnnotationMapper;
import com.hnu.it.ssm.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 注解式配置测试类
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class) // 加载注解配置类
public class AnnotationConfigTest {
@Autowired
private UserAnnotationMapper userAnnotationMapper;
@Test
public void testSelectById() {
User user = userAnnotationMapper.selectById(1);
System.out.println("注解式查询用户:" + user);
assert user != null;
}
@Test
public void testInsert() {
User user = new User();
user.setUserName("annotation_user");
user.setPassword("654321");
user.setAge(30);
user.setEmail("annotation@hnu.edu.cn");
int rows = userAnnotationMapper.insert(user);
System.out.println("注解式新增用户影响行数:" + rows + ",用户ID:" + user.getId());
assert rows == 1;
}
}
四、源码级解析:整合的核心底层逻辑
1. SqlSessionFactoryBean 的工作原理
SqlSessionFactoryBean是整合的核心桥梁,其实现了FactoryBean<SqlSessionFactory>和InitializingBean接口,核心逻辑在afterPropertiesSet()和getObject()方法中:
java
运行
// 简化源码逻辑
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean {
private DataSource dataSource;
private Resource configLocation;
private Resource[] mapperLocations;
@Override
public void afterPropertiesSet() throws Exception {
// 校验数据源是否配置
if (this.dataSource == null) {
throw new IllegalArgumentException("Property 'dataSource' is required");
}
// 构建SqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
private SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 1. 加载MyBatis配置文件(mybatis-config.xml)
Configuration configuration = new Configuration();
if (this.configLocation != null) {
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(configLocation.getInputStream());
configuration = xmlConfigBuilder.parse();
}
// 2. 配置数据源和事务工厂
configuration.setEnvironment(new Environment(
"default",
new SpringManagedTransactionFactory(), // Spring管理的事务工厂
this.dataSource
));
// 3. 扫描并加载Mapper映射文件
if (this.mapperLocations != null) {
for (Resource mapperLocation : this.mapperLocations) {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
mapperLocation.getInputStream(), configuration,
mapperLocation.toString(), configuration.getMapperRegistry()
);
xmlMapperBuilder.parse(); // 解析Mapper映射文件,注册SQL语句
}
}
// 4. 创建并返回SqlSessionFactory
return new DefaultSqlSessionFactory(configuration);
}
}
核心流程:
afterPropertiesSet()方法在 Bean 初始化时调用,校验数据源等必填参数;buildSqlSessionFactory()方法加载 MyBatis 配置、整合数据源、扫描 Mapper 映射文件,构建Configuration对象;- 通过
DefaultSqlSessionFactory创建SqlSessionFactory实例,最终通过getObject()方法提供给 Spring IOC 容器。
2. MapperScannerConfigurer 的扫描机制
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在 Spring 容器初始化时扫描 Mapper 接口并注册 BeanDefinition:
java
运行
// 简化源码逻辑
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {
private String basePackage;
private String sqlSessionFactoryBeanName;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 创建Mapper扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 扫描basePackage下的接口
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
// ClassPathMapperScanner的scan方法核心逻辑
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 扫描接口并注册BeanDefinition
doScan(basePackages);
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
protected Set doScan(String... basePackages) {
Set beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 设置Bean的构造函数参数:Mapper接口全类名
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
// 设置Bean的类型为MapperFactoryBean(通过FactoryBean创建代理对象)
definition.setBeanClass(MapperFactoryBean.class);
// 设置SqlSessionFactoryBeanName
definition.getPropertyValues().add("sqlSessionFactoryBeanName", this.sqlSessionFactoryBeanName);
}
return beanDefinitions;
}
}
核心流程:
postProcessBeanDefinitionRegistry()在 Spring BeanDefinition 注册后调用,启动 Mapper 扫描;ClassPathMapperScanner扫描basePackage下的所有接口,排除非接口类;- 为每个 Mapper 接口创建
GenericBeanDefinition,并将 BeanClass 设置为MapperFactoryBean; MapperFactoryBean是FactoryBean的实现类,Spring 初始化时会调用其getObject()方法,通过SqlSession.getMapper()生成 Mapper 代理对象。
3. 声明式事务的 AOP 实现原理
Spring 声明式事务基于 AOP 动态代理,核心是TransactionInterceptor(事务拦截器)和TransactionAttributeSource(事务属性源):
- 事务属性解析:
TransactionAttributeSource解析@Transactional注解或 XML 中的事务规则(传播行为、隔离级别等); - 事务拦截:
TransactionInterceptor作为 AOP 切面,拦截 Service 层方法调用,在方法执行前后进行事务控制:- 方法执行前:获取数据库连接,设置事务隔离级别,开启事务;
- 方法执行后:若无异常,提交事务;若抛出指定异常,回滚事务;
- 最终:释放数据库连接到连接池。
简化源码逻辑:
java
运行
public class TransactionInterceptor implements MethodInterceptor {
private PlatformTransactionManager transactionManager;
private TransactionAttributeSource transactionAttributeSource;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 1. 获取目标方法的事务属性
TransactionAttribute txAttr = transactionAttributeSource.getTransactionAttribute(invocation.getMethod(), targetClass);
// 2. 获取事务管理器
PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 3. 生成事务名称
String joinpointIdentification = methodIdentification(invocation.getMethod(), targetClass, txAttr);
// 4. 事务执行模板
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 5. 执行目标方法(Service层业务逻辑)
retVal = invocation.proceed();
} catch (Throwable ex) {
// 6. 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
// 7. 清理事务信息
cleanupTransactionInfo(txInfo);
}
// 8. 事务提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
五、整合后的性能优化方案
1. 连接池优化
- 选择高性能连接池:优先使用 Druid 或 HikariCP(Spring Boot 默认),避免使用 Spring 自带的 BasicDataSource;
- 合理配置连接池参数:
initialSize:根据系统初始并发量设置(建议 5-10);maxActive:根据数据库最大连接数设置(建议不超过数据库max_connections的 80%);minIdle:保证最小空闲连接数(建议 3-5),避免频繁创建连接;- 关闭无效检测:
testOnBorrow和testOnReturn设为false,通过testWhileIdle定期检测空闲连接。
2. MyBatis 性能优化
- 开启二级缓存:在 Mapper 接口上添加
@CacheNamespace注解,缓存查询结果(适用于查询频繁、修改少的数据); - 优化 SQL 语句:避免
SELECT *,只查询需要的字段;使用索引优化查询条件; - 批量操作优化:使用 MyBatis 的
BatchExecutor(通过SqlSessionTemplate配置ExecutorType.BATCH),减少 SQL 执行次数; - 延迟加载:开启
lazyLoadingEnabled=true,关联查询时仅在需要时加载关联数据,减少不必要的查询。
3. 事务优化
- 细粒度事务控制:避免在类上添加
@Transactional,仅在需要事务的方法上添加,减少事务开销; - 查询操作设为只读:
@Transactional(readOnly=true),Spring 会优化事务性能,关闭事务写入能力; - 合理设置事务隔离级别:默认使用
READ_COMMITTED,避免使用SERIALIZABLE(性能极低); - 避免长事务:事务中不包含耗时操作(如 IO、网络请求),减少事务持有时间,降低锁竞争。
4. 其他优化
- 开启 MyBatis 二级缓存:在
mybatis-config.xml中设置cacheEnabled=true,并在 Mapper 接口添加@CacheNamespace; - 使用分页插件:集成 PageHelper 或 MyBatis-Plus 分页插件,避免手动编写分页 SQL;
- 日志优化:生产环境关闭 SQL 日志打印,避免日志 IO 开销;
- 数据库索引优化:为查询频繁的字段(如
user_name、id)创建索引,提升查询效率。
六、常见问题排查与解决方案
1. Mapper 代理对象注入失败(No qualifying bean of type)
原因:
MapperScannerConfigurer的basePackage配置错误,未扫描到 Mapper 接口;- Mapper 接口未放在指定包下,或接口未被 Spring 扫描到;
sqlSessionFactoryBeanName配置错误,未关联正确的SqlSessionFactory。
解决方案:
- 检查
basePackage是否准确(如com.hnu.it.ssm.mapper); - 确保 Mapper 接口在
basePackage下,且无@Service等冲突注解; - 若 IOC 中只有一个
SqlSessionFactory,可省略sqlSessionFactoryBeanName配置。
2. SQL 语句执行失败(BindingException/Invalid bound statement)
原因:
- Mapper 映射文件的
namespace与 Mapper 接口全类名不一致; - 映射文件中
id与接口方法名不一致; - 映射文件路径未被
mapperLocations正确配置,导致 MyBatis 未加载; - 参数绑定错误(如
@Param注解缺失,或参数类型不匹配)。
解决方案:
- 严格校验
namespace和id与接口的一致性; - 检查
mapperLocations配置(如classpath:mapper/*.xml),确保映射文件在指定路径下; - 多参数方法需添加
@Param注解,明确参数名; - 查看日志中的 SQL 语句,验证参数绑定是否正确。
3. 事务不生效(未提交 / 回滚)
原因:
@Transactional注解加在非 public 方法上(Spring 事务仅对 public 方法生效);- 注解加在 Controller 层或 Mapper 接口上(事务应加在 Service 层);
- 未配置
transactionManager,或transactionManager未关联数据源; - 异常类型未被
rollbackFor指定(默认仅回滚 RuntimeException); - 方法内部捕获了异常,未抛出到外层。
解决方案:
- 将
@Transactional注解加在 Service 层的 public 方法上; - 确保配置了
DataSourceTransactionManager,且关联正确的dataSource; - 显式配置
rollbackFor=Exception.class,确保所有异常都回滚; - 业务逻辑中不要捕获异常,或捕获后重新抛出(
throw new RuntimeException(ex))。
4. 数据库连接失败(CommunicationsException)
原因:
- 数据库驱动类名错误(MySQL8 应为
com.mysql.cj.jdbc.Driver,而非com.mysql.jdbc.Driver); - 数据库 URL 配置错误(如时区未指定、端口错误、数据库名不存在);
- 数据库用户名 / 密码错误;
- 数据库服务未启动,或防火墙拦截了连接。
解决方案:
- 校验驱动类名和 URL 格式(如
jdbc:mysql://localhost:3306/ssm_db?serverTimezone=Asia/Shanghai); - 确认数据库服务已启动,且用户名 / 密码正确;
- 测试数据库连接(如使用 Navicat 连接 URL,验证可用性)。
七、总结
Spring 整合 MyBatis 是 Java 后端开发的核心技能,其本质是 “组件托管 + 依赖注入 + 事务统一管控”。通过本文的详细拆解,从环境搭建、配置实操到源码解析、性能优化,完整覆盖了整合的全流程,既符合湖南大学计算机科学与技术专业的课程实践要求,也适配企业级应用的开发标准。
核心要点回顾:
- 整合的核心是让 Spring 接管 MyBatis 的
DataSource、SqlSessionFactory、Mapper代理对象,减少手动编码; - XML 配置方案步骤清晰、易于维护,是企业主流选择;注解配置方案无 XML 依赖,是 Spring Boot 的基础;
- 声明式事务是整合后的核心优势,需正确配置事务管理器和事务规则;
- 性能优化的关键在于连接池配置、SQL 优化、事务细粒度控制;
- 常见问题多源于配置不一致(如
namespace、id、包路径),需严格遵循规范。
掌握 Spring 与 MyBatis 的整合,不仅能应对高校课程的实践任务,更能为后续学习 Spring Boot、Spring Cloud 等微服务技术打下坚实基础,是 Java 后端开发者的必备技能之一。

浙公网安备 33010602011771号