1. 为什么需要Spring整合MyBatis?
在企业级Java开发中,Spring和MyBatis是两个不可或缺的框架。Spring提供了强大的IoC容器和事务管理能力,而MyBatis则是优秀的持久层框架。但原生MyBatis存在几个痛点:
- SqlSessionFactory需要手动创建和管理
- Mapper接口需要手动注册
- 事务管理需要额外编码
Spring整合MyBatis的核心价值就在于解决这些问题。通过整合,我们可以:
- 让Spring容器管理SqlSessionFactory的生命周期
- 自动扫描和注册Mapper接口
- 与Spring声明式事务无缝集成
实际项目中,90%的MyBatis使用场景都会选择与Spring整合。这不仅减少了样板代码,更重要的是获得了Spring生态的强大支持。
2. 环境准备与基础配置
2.1 依赖管理
使用Maven构建项目时,需要以下核心依赖:
xml复制<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.28</version>
</dependency>
<!-- MyBatis-Spring整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.1</version>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
2.2 数据源配置
在Spring配置文件中配置数据源:
xml复制<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
</bean>
3. MyBatis核心组件配置
3.1 SqlSessionFactory配置
SqlSessionFactory是MyBatis的核心入口,Spring通过FactoryBean方式创建:
xml复制<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 指定MyBatis全局配置文件 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 指定mapper.xml文件位置 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
3.2 Mapper接口扫描
传统方式需要为每个Mapper接口配置MapperFactoryBean,Spring提供了自动扫描机制:
xml复制<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
4. 事务管理集成
4.1 声明式事务配置
Spring的声明式事务可以与MyBatis完美集成:
xml复制<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
4.2 事务使用示例
在Service层使用@Transactional注解:
java复制@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void createUser(User user) {
userMapper.insert(user);
// 其他数据库操作
}
}
5. Junit单元测试实战
5.1 测试环境配置
Spring-test提供了专门的测试支持:
xml复制<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
5.2 测试类编写
使用SpringJUnit4ClassRunner运行测试:
java复制@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
@Transactional
@Rollback(true)
public void testInsert() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
int result = userMapper.insert(user);
assertEquals(1, result);
}
}
5.3 测试中的常见问题
- NoSuchBeanDefinitionException:检查Mapper接口是否被正确扫描
- 事务不回滚:确保测试方法上有@Transactional和@Rollback注解
- SQL语法错误:测试前初始化数据库脚本
6. 高级配置与优化
6.1 批量操作优化
MyBatis提供了批量执行器:
java复制SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insert(user);
}
sqlSession.commit();
} finally {
sqlSession.close();
}
6.2 多数据源配置
实际项目中可能需要配置多个数据源:
xml复制<!-- 主数据源 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 配置省略 -->
</bean>
<!-- 从数据源 -->
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 配置省略 -->
</bean>
<!-- 主SqlSessionFactory -->
<bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="masterDataSource"/>
</bean>
<!-- 从SqlSessionFactory -->
<bean id="slaveSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="slaveDataSource"/>
</bean>
7. 实际项目中的经验分享
7.1 动态数据源切换
在读写分离场景下,可以通过AOP实现动态数据源切换:
java复制@Aspect
@Component
public class DataSourceAspect {
@Before("execution(* com.example.service..*.query*(..)) || " +
"execution(* com.example.service..*.get*(..)) || " +
"execution(* com.example.service..*.find*(..))")
public void setReadDataSource() {
DataSourceContextHolder.setDataSourceType("slave");
}
@Before("execution(* com.example.service..*.insert*(..)) || " +
"execution(* com.example.service..*.update*(..)) || " +
"execution(* com.example.service..*.delete*(..))")
public void setWriteDataSource() {
DataSourceContextHolder.setDataSourceType("master");
}
}
7.2 MyBatis缓存配置
合理配置缓存可以显著提升性能:
xml复制<!-- 二级缓存配置 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
<!-- 局部缓存设置 -->
<select id="selectUser" resultType="User" useCache="true">
select * from user where id = #{id}
</select>
8. 常见问题排查
8.1 Mapper接口注入失败
可能原因及解决方案:
- 包扫描路径错误:检查MapperScannerConfigurer的basePackage配置
- SqlSessionFactory未正确注入:检查sqlSessionFactoryBeanName属性
- Mapper接口没有@Repository或@Component注解:MyBatis Mapper接口不需要这些注解
8.2 事务不生效
排查步骤:
- 检查方法是否为public
- 检查是否在同一个类中调用
- 检查异常类型是否被捕获未抛出
- 检查@Transactional的rollbackFor配置
8.3 性能问题优化
常见优化手段:
- 使用连接池(如Druid)
- 合理配置MyBatis缓存
- 批量操作使用Batch执行器
- SQL语句优化
9. Spring Boot下的简化配置
在Spring Boot中,配置更加简单:
properties复制# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.entity
只需添加starter依赖:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
10. 单元测试进阶技巧
10.1 使用内存数据库
测试时可以使用H2等内存数据库:
java复制@TestConfiguration
public class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}
10.2 Mock测试
对于复杂依赖可以使用Mockito:
java复制@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserServiceImpl userService;
@Test
public void testGetUser() {
when(userMapper.selectById(1L)).thenReturn(new User(1L, "test"));
User user = userService.getUser(1L);
assertEquals("test", user.getUsername());
}
}
11. 项目结构最佳实践
推荐的项目结构:
code复制src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ ├── config/ # Spring配置类
│ │ ├── controller/ # 控制器
│ │ ├── service/ # 服务层
│ │ ├── mapper/ # Mapper接口
│ │ └── entity/ # 实体类
│ └── resources/
│ ├── mapper/ # MyBatis映射文件
│ ├── applicationContext.xml # Spring配置
│ └── mybatis-config.xml # MyBatis配置
└── test/
└── java/ # 测试代码
12. 性能监控与调优
12.1 Druid监控
配置Druid监控:
xml复制<bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="1000"/>
<property name="logSlowSql" value="true"/>
</bean>
<bean id="wallFilter" class="com.alibaba.druid.wall.WallFilter"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 其他配置 -->
<property name="filters" value="stat,wall"/>
<property name="proxyFilters">
<list>
<ref bean="statFilter"/>
<ref bean="wallFilter"/>
</list>
</property>
</bean>
12.2 MyBatis日志
开启MyBatis SQL日志:
properties复制logging.level.com.example.mapper=DEBUG
13. 动态SQL实践
MyBatis提供了强大的动态SQL能力:
xml复制<select id="findUsers" resultType="User">
SELECT * FROM user
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="email != null">
AND email = #{email}
</if>
<choose>
<when test="orderBy == 'name'">
ORDER BY username
</when>
<otherwise>
ORDER BY id
</otherwise>
</choose>
</where>
</select>
14. 类型处理器扩展
自定义类型处理器:
java复制@MappedTypes(String.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class StringTrimmerTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) {
ps.setString(i, parameter.trim());
}
// 其他方法实现...
}
配置使用:
xml复制<typeHandlers>
<typeHandler handler="com.example.handler.StringTrimmerTypeHandler"/>
</typeHandlers>
15. 插件开发实战
MyBatis允许通过插件拦截核心方法:
java复制@Intercepts({
@Signature(type= Executor.class, method="update",
args={MappedStatement.class,Object.class})
})
public class MyBatisPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
Object result = invocation.proceed();
// 后置处理
return result;
}
}
注册插件:
xml复制<plugins>
<plugin interceptor="com.example.plugin.MyBatisPlugin"/>
</plugins>
16. 多租户实现方案
通过MyBatis插件实现多租户:
java复制public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前租户ID
String tenantId = TenantContext.getCurrentTenant();
// 修改SQL
BoundSql boundSql = (BoundSql) invocation.getArgs()[1];
String newSql = boundSql.getSql() + " AND tenant_id = '" + tenantId + "'";
// 使用反射修改SQL
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, newSql);
return invocation.proceed();
}
}
17. 与Spring Security集成
结合Spring Security实现数据权限控制:
java复制@Service
public class DataPermissionService {
public String getDataPermissionSQL() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getAuthorities().contains("ROLE_ADMIN")) {
return "";
} else {
return " AND created_by = '" + auth.getName() + "'";
}
}
}
在Mapper中使用:
xml复制<select id="selectList" resultType="User">
SELECT * FROM user
WHERE 1=1 ${@dataPermissionService.getDataPermissionSQL()}
</select>
18. 分布式事务考虑
对于跨数据源操作,可以考虑使用Seata:
java复制@GlobalTransactional
public void crossDatabaseOperation() {
// 操作数据库A
dbAService.update();
// 操作数据库B
dbBService.update();
}
19. 代码生成器使用
MyBatis Generator可以自动生成基础代码:
xml复制<generatorConfiguration>
<context id="mysql" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test"
userId="root"
password="123456"/>
<javaModelGenerator targetPackage="com.example.entity"
targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources"/>
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.example.mapper"
targetProject="src/main/java"/>
<table tableName="user"/>
</context>
</generatorConfiguration>
20. 未来演进方向
随着技术发展,可以考虑:
- 使用MyBatis-Plus增强功能
- 迁移到Spring Data JPA(适合简单CRUD)
- 探索响应式编程整合
- 与云原生技术结合
在实际项目中,Spring整合MyBatis的方案已经非常成熟稳定。关键是根据项目特点选择合适的配置方式和扩展点,既保持框架的灵活性,又能满足业务需求。
