mybatis整合spring

MyBatis 整合 Spring 完整指南:从 XML 配置到 Spring Boot 实践

MyBatis 与 Spring 的整合是企业级开发的主流方案,核心是将 MyBatis 的核心组件(SqlSessionFactoryMapper 代理)交给 Spring IOC 容器管理,同时复用 Spring 的事务管理、依赖注入等能力。 Spring Boot 注解式整合(主流方案)生产级数据源配置事务深化工程规范常见问题排查,覆盖传统 Spring 与 Spring Boot 两种场景。

版本选型与依赖优化

2024 年稳定版本组合(支持 Spring 5 特性,兼容 JDK 8+):

传统 Spring(XML 配置)依赖

<dependencies>
    <!-- MyBatis 核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.16</version>
    </dependency>
    <!-- MyBatis-Spring 整合包(2.x 支持 Spring 5) -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.1.6</version>
    </dependency>

    <!-- Spring 核心依赖(5.x 稳定版) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.31</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.31</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.31</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.31</version>
        <scope>test</scope>
    </dependency>

    <!-- 生产级数据源(HikariCP,Spring 5 默认) -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>
    <!-- MySQL 驱动(8.x 支持 MySQL 8.0+) -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>

    <!-- 测试依赖 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Spring Boot(注解式)依赖

Spring Boot 提供 mybatis-spring-boot-starter,自动整合 MyBatis 与 Spring,无需手动配置 SqlSessionFactoryMapperScanner,是当前主流方案:

<dependencies>
    <!-- Spring Boot 父工程(统一版本管理) -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version> <!-- 稳定版,兼容 JDK 8-17 -->
    </parent>

    <!-- MyBatis-Spring Boot  starter(自动配置核心) -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.2</version>
    </dependency>

    <!-- Spring Boot 数据源 starter(默认集成 HikariCP) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring Boot 测试 starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

传统 Spring 整合(XML 配置)深化

1. 核心配置文件

(1)数据库配置文件(jdbc.properties
# HikariCP 连接池配置(生产级参数)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456

# HikariCP 连接池参数(关键优化)
spring.datasource.hikari.maximum-pool-size=10  # 最大连接数(根据并发调整)
spring.datasource.hikari.minimum-idle=2       # 最小空闲连接数
spring.datasource.hikari.idle-timeout=300000  # 空闲连接超时时间(5分钟)
spring.datasource.hikari.connection-timeout=30000  # 连接超时时间(30秒)
(2)Spring 配置文件(applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 1. 扫描业务组件(Service/Controller) -->
    <context:component-scan base-package="com.zhanghe.study.mybatis.service"/>

    <!-- 2. 加载数据库配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 3. 配置生产级数据源(HikariCP)替代 DriverManagerDataSource -->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
        <property name="driverClassName" value="${spring.datasource.driver-class-name}"/>
        <property name="jdbcUrl" value="${spring.datasource.url}"/>
        <property name="username" value="${spring.datasource.username}"/>
        <property name="password" value="${spring.datasource.password}"/>
        <!-- HikariCP 特有参数 -->
        <property name="maximumPoolSize" value="${spring.datasource.hikari.maximum-pool-size}"/>
        <property name="minimumIdle" value="${spring.datasource.hikari.minimum-idle}"/>
        <property name="idleTimeout" value="${spring.datasource.hikari.idle-timeout}"/>
        <property name="connectionTimeout" value="${spring.datasource.hikari.connection-timeout}"/>
    </bean>

    <!-- 4. 配置事务管理器(Spring  JDBC 事务) -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 5. 开启事务注解驱动(支持 @Transactional) -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

    <!-- 6. 配置 SqlSessionFactory(MyBatis 核心) -->
    <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"/>
        <!-- 简化配置:直接在此处配置 MyBatis 全局参数,无需单独 mybatis-config.xml -->
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <!-- 开启驼峰命名映射 -->
                <property name="mapUnderscoreToCamelCase" value="true"/>
                <!-- 开启 SQL 日志 -->
                <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
                <!-- 开启二级缓存 -->
                <property name="cacheEnabled" value="true"/>
            </bean>
        </property>
        <!-- 配置类型别名(无需在 mybatis-config.xml 中重复配置) -->
        <property name="typeAliasesPackage" value="com.zhanghe.study.mybatis.model"/>
    </bean>

    <!-- 7. 扫描 Mapper 接口,生成代理对象并交给 Spring 管理 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- Mapper 接口所在包 -->
        <property name="basePackage" value="com.zhanghe.study.mybatis.mapper"/>
        <!-- 关联 SqlSessionFactory(Spring 5 需用 beanName 而非直接引用,避免循环依赖) -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
</beans>
(3)简化 MyBatis 全局配置(mybatis-config.xml

由于大部分配置已在 SqlSessionFactoryBean 中完成,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>
    <!-- 数据库厂商标识(多数据库适配) -->
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle"/>
    </databaseIdProvider>
</configuration>

2. 事务管理深化

Spring 事务管理是整合的核心优势之一,需明确 事务传播行为隔离级别 的使用场景:

(1)Service 层事务示例
@Service
public class UserService {
    // 注入 Mapper(Spring 自动生成代理对象)
    @Autowired
    private UserMapper userMapper;

    // 声明事务:默认传播行为 REQUIRED,隔离级别 DEFAULT
    @Transactional
    public void addUserWithRole(User user, List<Integer> roleIds) {
        // 1. 插入用户
        userMapper.insertUser(user);
        // 2. 插入用户-角色关联(若此处抛异常,用户插入会回滚)
        for (Integer roleId : roleIds) {
            userMapper.insertUserRole(user.getId(), roleId);
        }
    }

    // 只读事务(优化查询性能,避免事务日志开销)
    @Transactional(readOnly = true, timeout = 5) // 超时时间 5 秒
    public User getUserById(Integer id) {
        return userMapper.selectUser(id);
    }

    // 自定义事务传播行为(如 REQUIRES_NEW:新建事务,与父事务独立)
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void logUserOperation(UserOperateLog log) {
        userMapper.insertOperateLog(log);
    }
}
(2)事务关键属性说明
属性 作用 常用值
propagation 事务传播行为(如何处理嵌套事务) REQUIRED(默认)、REQUIRES_NEWSUPPORTS
isolation 事务隔离级别(解决脏读、不可重复读、幻读) DEFAULT(默认,跟随数据库)、READ_COMMITTED
readOnly 是否为只读事务(查询操作建议开启,优化性能) false(默认)、true
timeout 事务超时时间(超过时间自动回滚) 整数(秒),默认 -1(无超时)
rollbackFor 触发回滚的异常类型(默认仅回滚运行时异常) Exception.class(所有异常回滚)

3. 测试类优化(Spring 测试框架)

使用 SpringJUnit4ClassRunner 自动加载 Spring 容器,避免手动创建 ApplicationContext

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 com.zhanghe.study.mybatis.mapper.UserMapper;
import com.zhanghe.study.mybatis.model.User;

// 整合 Spring 测试框架
@RunWith(SpringJUnit4ClassRunner.class)
// 加载 Spring 配置文件
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class MyBatisSpringTest {

    // 自动注入 Mapper(Spring 管理的代理对象)
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectUser() {
        User user = userMapper.selectUser(8);
        System.out.println(user); // 直接使用 Mapper,无需手动创建 SqlSession
    }
}

Spring Boot 整合(注解式,主流方案)

Spring Boot 通过 自动配置 简化整合,无需 XML 配置,核心是 application.yml 配置和注解扫描。

1. 核心配置文件(application.yml

# 服务器配置(可选)
server:
  port: 8080

# 数据库与数据源配置(HikariCP 自动集成)
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    # HikariCP 连接池参数
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2
      idle-timeout: 300000
      connection-timeout: 30000

# MyBatis 配置(替代 mybatis-config.xml)
mybatis:
  # Mapper 映射文件位置
  mapper-locations: classpath:mapper/**/*.xml
  # 类型别名扫描包
  type-aliases-package: com.zhanghe.study.mybatis.model
  # 全局配置(对应 mybatis-config.xml 的 settings)
  configuration:
    map-underscore-to-camel-case: true # 驼峰命名映射
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志
    cache-enabled: true # 开启二级缓存

# 日志配置(可选,细化日志级别)
logging:
  level:
    com.zhanghe.study.mybatis.mapper: debug # Mapper 接口日志级别设为 debug,打印 SQL

2. 启动类配置(MyBatisApplication.java

通过 @MapperScan 扫描 Mapper 接口,替代传统 XML 中的 MapperScannerConfigurer

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// Spring Boot 应用入口
@SpringBootApplication
// 扫描 Mapper 接口(无需在每个 Mapper 上加 @Mapper 注解)
@MapperScan("com.zhanghe.study.mybatis.mapper")
public class MyBatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisApplication.class, args);
    }
}

3. 组件实现(与传统 Spring 一致)

(1)实体类(User.java
package com.zhanghe.study.mybatis.model;

import java.io.Serializable;
import java.util.Date;

// 实现 Serializable 用于 MyBatis 二级缓存(readOnly=false 时需序列化)
public class User implements Serializable {
    private Integer id;
    private String userName; // 对应数据库 user_name(驼峰映射生效)
    private Integer age;
    private Date createTime;

    // getter/setter、toString
}
(2)Mapper 接口(UserMapper.java
package com.zhanghe.study.mybatis.mapper;

import com.zhanghe.study.mybatis.model.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    // 插入用户(返回自增主键)
    int insertUser(User user);

    // 多参数查询(@Param 绑定参数名)
    User selectUserByUsernameAndAge(@Param("userName") String userName, @Param("age") Integer age);

    // 根据 ID 查询用户
    User selectUser(@Param("id") Integer id);
}
(3)Mapper 映射文件(UserMapper.xml

放在 src/main/resources/mapper/ 目录下:

<?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.zhanghe.study.mybatis.mapper.UserMapper">
    <!-- 二级缓存配置(可选) -->
    <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="false"/>

    <!-- 插入用户,返回自增主键 -->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (user_name, age, create_time)
        VALUES (#{userName}, #{age}, #{createTime})
    </insert>

    <!-- 根据用户名和年龄查询 -->
    <select id="selectUserByUsernameAndAge" resultType="User">
        SELECT id, user_name, age, create_time
        FROM user
        WHERE user_name = #{userName} AND age = #{age}
    </select>

    <!-- 根据 ID 查询 -->
    <select id="selectUser" resultType="User">
        SELECT id, user_name, age, create_time
        FROM user
        WHERE id = #{id}
    </select>
</mapper>

4. 测试类(Spring Boot Test)

使用 @SpringBootTest 自动加载 Spring Boot 上下文:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.zhanghe.study.mybatis.mapper.UserMapper;
import com.zhanghe.study.mybatis.model.User;
import java.util.Date;

// Spring Boot 测试注解(自动加载启动类)
@SpringBootTest
public class MyBatisBootTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testInsertUser() {
        User user = new User();
        user.setUserName("Spring Boot");
        user.setAge(3);
        user.setCreateTime(new Date());

        int rows = userMapper.insertUser(user);
        System.out.println("插入行数:" + rows);
        System.out.println("自增主键:" + user.getId()); // 自动获取主键
    }

    @Test
    public void testSelectUser() {
        User user = userMapper.selectUser(1);
        System.out.println(user); // 输出用户信息,SQL 日志自动打印
    }
}

整合核心原理(简化版)

分析 SqlSessionFactoryBeanMapperScannerConfigurer 的源码,以下提炼核心逻辑,帮助理解整合本质:

1. SqlSessionFactoryBean:构建 SqlSessionFactory

  • 作用:替代 MyBatis 原生的 SqlSessionFactoryBuilder,将 Spring 管理的 DataSource 与 MyBatis 配置整合,生成 SqlSessionFactory
  • 核心流程:
    1. 读取 dataSource(Spring 数据源)、mapperLocations(Mapper 映射文件)等配置;
    2. 构建 MyBatis 的 Configuration 对象(封装全局配置、Mapper 信息);
    3. 调用 SqlSessionFactoryBuilder.build(configuration) 生成 SqlSessionFactory
    4. 通过 FactoryBean 接口的 getObject() 方法,将 SqlSessionFactory 交给 Spring 容器。

2. MapperScannerConfigurer:生成 Mapper 代理对象

  • 作用:扫描指定包下的 Mapper 接口,为每个接口生成 JDK 动态代理对象,并注册到 Spring 容器。
  • 核心流程:
    1. 扫描 basePackage 下的所有接口(如 UserMapper);
    2. 为每个接口创建 MapperFactoryBean(实现 FactoryBean);
    3. MapperFactoryBean 通过 SqlSession 获取 Mapper 代理对象(sqlSession.getMapper(xxx.class));
    4. Spring 容器中注入的 UserMapper 实际是 MapperFactoryBean 生成的代理对象,调用方法时底层执行 SQL。

常见问题与解决方案

1. Mapper 注入失败(No qualifying bean of type UserMapper available

  • 原因 1:未扫描 Mapper 接口(传统 Spring 未配置 MapperScannerConfigurer,Spring Boot 未加 @MapperScan);
  • 原因 2:Mapper 接口与映射文件 不同包同名(如接口在 com.mapper,映射文件需在 resources/com/mapper 下,且文件名一致);
  • 解决方案:
    • 传统 Spring:确保 MapperScannerConfigurerbasePackage 正确;
    • Spring Boot:在启动类加 @MapperScan("com.zhanghe.study.mybatis.mapper")

2. 事务不生效(@Transactional 注解无效)

  • 原因 1:未开启事务驱动(传统 Spring 未配置 <tx:annotation-driven>,Spring Boot 需确保引入 spring-boot-starter-tx);

  • 原因 2:事务方法为 非 public 修饰(Spring 事务仅对 public 方法生效);

  • 原因 3:异常类型未纳入回滚(默认仅回滚 RuntimeException,需配置 rollbackFor = Exception.class);

  • 解决方案:

    @Transactional(rollbackFor = Exception.class) // 所有异常回滚
    public void addUser(User user) throws Exception { ... }

3. 连接池配置无效(传统 Spring 仍用默认数据源)

  • 原因:依赖冲突或数据源配置错误(如 HikariCP 依赖未引入,或 jdbcUrl 拼写错误);
  • 解决方案:
    • 确保引入 HikariCP 依赖;
    • 传统 Spring 中 HikariDataSource 的属性是 jdbcUrl,而非 url(区别于 DriverManagerDataSource)。

4. 二级缓存不生效(跨 SqlSession 未命中)

  • 原因 1:实体类未实现 SerializablereadOnly=false 时需序列化克隆对象);
  • 原因 2:未在 Mapper 映射文件中配置 <cache> 标签;
  • 原因 3:查询标签配置 useCache="false"(禁用当前查询的二级缓存);
  • 解决方案:
    • 实体类实现 Serializable
    • UserMapper.xml 中添加 <cache/>

总结

MyBatis 与 Spring 整合的核心是 “Spring 管理 MyBatis 组件”,两种方案各有适用场景:

  • 传统 Spring(XML 配置):适合维护旧项目,配置灵活但繁琐;
  • Spring Boot(注解式):适合新项目,自动配置简化开发,是当前主流