跳到主要内容

Spring Boot 整合

MyBatis 与 Spring Boot 的整合是现代 Java 开发的标准配置。通过 mybatis-spring-boot-starter,可以快速搭建 MyBatis 应用,几乎零配置即可运行。本章将详细介绍整合过程和最佳实践。

为什么需要整合?

在纯 MyBatis 应用中,开发者需要手动管理 SqlSessionFactorySqlSession 的创建和关闭,以及事务的提交和回滚。这些重复的样板代码不仅繁琐,还容易出错。

Spring Boot 整合后的优势:

  • 自动配置:自动创建和注入 SqlSessionFactorySqlSessionTemplate
  • 自动扫描:自动扫描并注册 Mapper 接口
  • 事务管理:与 Spring 事务无缝集成,支持声明式事务
  • 简化配置:通过 application.yml 即可完成大部分配置

快速开始

添加依赖

pom.xml 中添加依赖:

<dependencies>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Lombok(可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

版本兼容性

MyBatis-Spring-Boot-StarterMyBatis-SpringSpring BootJava
3.03.03.0+17+
2.32.12.78+

配置数据源

application.yml 中配置数据库连接:

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: your_password

mybatis:
# Mapper XML 文件位置
mapper-locations: classpath:mapper/*.xml
# 实体类别名包路径
type-aliases-package: com.example.demo.entity
# 配置项
configuration:
# 开启驼峰命名映射
map-underscore-to-camel-case: true
# 开启二级缓存
cache-enabled: true
# 打印 SQL 日志(开发环境)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

创建实体类

package com.example.demo.entity;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class User {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

创建 Mapper 接口

package com.example.demo.mapper;

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper // 标注为 MyBatis Mapper 接口
public interface UserMapper {

@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);

@Select("SELECT * FROM user ORDER BY id DESC")
List<User> selectAll();

@Insert("INSERT INTO user (username, password, email, phone, status) " +
"VALUES (#{username}, #{password}, #{email}, #{phone}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);

@Update("UPDATE user SET username = #{username}, email = #{email} WHERE id = #{id}")
int update(User user);

@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(Long id);
}

创建 Service 层

package com.example.demo.service;

import com.example.demo.entity.User;
import java.util.List;

public interface UserService {
User findById(Long id);
List<User> findAll();
void save(User user);
void update(User user);
void delete(Long id);
}
package com.example.demo.service.impl;

import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.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
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Override
public User findById(Long id) {
return userMapper.selectById(id);
}

@Override
public List<User> findAll() {
return userMapper.selectAll();
}

@Override
@Transactional // 声明式事务
public void save(User user) {
userMapper.insert(user);
}

@Override
@Transactional
public void update(User user) {
userMapper.update(user);
}

@Override
@Transactional
public void delete(Long id) {
userMapper.deleteById(id);
}
}

创建 Controller 层

package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.findById(id);
}

@GetMapping
public List<User> list() {
return userService.findAll();
}

@PostMapping
public String save(@RequestBody User user) {
userService.save(user);
return "success";
}

@PutMapping
public String update(@RequestBody User user) {
userService.update(user);
return "success";
}

@DeleteMapping("/{id}")
public String delete(@PathVariable Long id) {
userService.delete(id);
return "success";
}
}

启动类

package com.example.demo;

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

@SpringBootApplication
@MapperScan("com.example.demo.mapper") // 扫描 Mapper 接口
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

配置详解

application.yml 完整配置

spring:
datasource:
# 数据源配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: your_password
# HikariCP 连接池配置(Spring Boot 默认)
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 30000
pool-name: MyBatisHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1

mybatis:
# MyBatis 配置文件位置(可选)
# config-location: classpath:mybatis-config.xml

# Mapper XML 文件位置
mapper-locations: classpath:mapper/**/*.xml

# 实体类别名包路径
type-aliases-package: com.example.demo.entity

# 类型处理器包路径
type-handlers-package: com.example.demo.handler

# 执行器类型:SIMPLE, REUSE, BATCH
executor-type: SIMPLE

# 配置项(与 mybatis-config.xml 中的 settings 对应)
configuration:
# 驼峰命名映射
map-underscore-to-camel-case: true
# 二级缓存
cache-enabled: true
# 延迟加载
lazy-loading-enabled: true
# 主键回填
use-generated-keys: true
# 日志实现
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 本地缓存作用域
local-cache-scope: session
# 空值 JDBC 类型
jdbc-type-for-null: null

配置属性说明

属性说明
mybatis.config-locationMyBatis 配置文件位置
mybatis.mapper-locationsMapper XML 文件位置,支持通配符
mybatis.type-aliases-package实体类别名包路径
mybatis.type-handlers-package类型处理器包路径
mybatis.executor-type执行器类型:SIMPLE、REUSE、BATCH
mybatis.configuration.*MyBatis 配置项

使用 Java 配置

当 YAML 配置无法满足需求时,可以使用 Java 配置:

@Configuration
public class MyBatisConfig {

/**
* 自定义 SqlSessionFactory 配置
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);

// 设置 Mapper XML 位置
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/**/*.xml")
);

// 设置类型别名包
factoryBean.setTypeAliasesPackage("com.example.demo.entity");

// 设置配置项
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
configuration.setLazyLoadingEnabled(true);
factoryBean.setConfiguration(configuration);

// 设置插件
factoryBean.setPlugins(new MyInterceptor());

return factoryBean.getObject();
}

/**
* 自定义 Configuration(使用 ConfigurationCustomizer)
*/
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> {
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
};
}
}

Mapper 扫描方式

方式一:@MapperScan 注解(推荐)

在启动类上添加 @MapperScan 注解:

@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

支持多个包:

@MapperScan({"com.example.demo.mapper", "com.example.demo.repository"})

方式二:@Mapper 注解

在每个 Mapper 接口上添加 @Mapper 注解:

@Mapper
public interface UserMapper {
// ...
}

方式三:Java 配置

@Configuration
public class MyBatisConfig {

@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.example.demo.mapper");
configurer.setAnnotationClass(Mapper.class); // 可选:只扫描带特定注解的接口
return configurer;
}
}

XML 映射文件配置

项目结构

src/main/resources/
├── application.yml
└── mapper/
├── UserMapper.xml
└── OrderMapper.xml

UserMapper.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.demo.mapper.UserMapper">

<resultMap id="BaseResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="email" property="email"/>
<result column="phone" property="phone"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>

<sql id="Base_Column_List">
id, username, password, email, phone, status, create_time, update_time
</sql>

<select id="selectById" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE id = #{id}
</select>

<select id="selectAll" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
ORDER BY id DESC
</select>

<select id="selectByCondition" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY id DESC
</select>

<insert id="insert" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, email, phone, status)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{status})
</insert>

<update id="update" parameterType="User">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
<if test="phone != null">phone = #{phone},</if>
</set>
WHERE id = #{id}
</update>

<delete id="deleteById">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>

事务管理

Spring Boot 整合后,MyBatis 的事务由 Spring 统一管理。

启用事务管理

在 Service 类或方法上添加 @Transactional 注解:

@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserMapper userMapper;

@Autowired
private OrderMapper orderMapper;

/**
* 声明式事务
* 默认:只对 RuntimeException 回滚
*/
@Transactional
public void createUserWithOrder(User user, Order order) {
userMapper.insert(user);
order.setUserId(user.getId());
orderMapper.insert(order);
}

/**
* 配置回滚异常类型
*/
@Transactional(rollbackFor = Exception.class)
public void createUser(User user) throws Exception {
userMapper.insert(user);
// 抛出 Exception 也会回滚
if (user.getUsername() == null) {
throw new Exception("用户名不能为空");
}
}

/**
* 只读事务(优化性能)
*/
@Transactional(readOnly = true)
public User findById(Long id) {
return userMapper.selectById(id);
}

/**
* 设置传播行为和隔离级别
*/
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30
)
public void updateUser(User user) {
userMapper.update(user);
}
}

事务传播行为

传播行为说明
REQUIRED(默认)有事务则加入,无则新建
REQUIRES_NEW总是新建事务,挂起当前事务
SUPPORTS有事务则加入,无则非事务执行
NOT_SUPPORTED非事务执行,挂起当前事务
MANDATORY必须在事务中执行,否则抛异常
NEVER必须非事务执行,否则抛异常
NESTED嵌套事务

事务隔离级别

隔离级别说明
DEFAULT使用数据库默认隔离级别
READ_UNCOMMITTED读未提交
READ_COMMITTED读已提交
REPEATABLE_READ可重复读
SERIALIZABLE串行化

多数据源配置

实际项目中经常需要连接多个数据库,下面介绍多数据源的配置方式。

添加依赖

<!-- 如果需要动态切换数据源,可以添加 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>

方式一:手动配置多数据源

# application.yml
spring:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_master
username: root
password: password
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_slave
username: root
password: password
@Configuration
public class DataSourceConfig {

@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}

@Bean
@Primary
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {

Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);

AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
};

routingDataSource.setDefaultTargetDataSource(masterDataSource);
routingDataSource.setTargetDataSources(targetDataSources);

return routingDataSource;
}

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource routingDataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(routingDataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/**/*.xml")
);
return factoryBean.getObject();
}
}

// 数据源上下文
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

public static void setDataSourceType(String type) {
contextHolder.set(type);
}

public static String getDataSourceType() {
return contextHolder.get();
}

public static void clearDataSourceType() {
contextHolder.remove();
}
}

// 数据源切换注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "master";
}

// 切面
@Aspect
@Component
public class DataSourceAspect {

@Before("@annotation(dataSource)")
public void before(JoinPoint point, DataSource dataSource) {
DataSourceContextHolder.setDataSourceType(dataSource.value());
}

@After("@annotation(dataSource)")
public void after(JoinPoint point, DataSource dataSource) {
DataSourceContextHolder.clearDataSourceType();
}
}

方式二:使用 dynamic-datasource

spring:
datasource:
dynamic:
primary: master # 默认数据源
strict: false # 严格模式
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_master
username: root
password: password
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_slave
username: root
password: password

使用 @DS 注解切换数据源:

@Service
public class UserService {

@Autowired
private UserMapper userMapper;

// 使用 master 数据源
@DS("master")
public void save(User user) {
userMapper.insert(user);
}

// 使用 slave 数据源
@DS("slave")
public User findById(Long id) {
return userMapper.selectById(id);
}
}

分页整合

使用 PageHelper

添加依赖:

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>

配置:

pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql

使用:

@Service
public class UserService {

@Autowired
private UserMapper userMapper;

public PageInfo<User> findByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> list = userMapper.selectAll();
return new PageInfo<>(list);
}
}

日志配置

打印 SQL 日志

方式一:配置 MyBatis 日志实现

mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

方式二:配置 Logback

<!-- logback-spring.xml -->
<configuration>
<!-- 开发环境配置 -->
<springProfile name="dev">
<!-- 打印 MyBatis SQL -->
<logger name="com.example.demo.mapper" level="DEBUG"/>
</springProfile>

<!-- 生产环境配置 -->
<springProfile name="prod">
<logger name="com.example.demo.mapper" level="INFO"/>
</springProfile>
</configuration>

方式三:使用 MyBatis Log Plugin

安装 IDEA 插件 MyBatis Log Plugin,可以将 MyBatis 日志转换为完整可执行的 SQL。

常见问题

1. Mapper 接口无法注入

问题:启动报错 Mapper not foundNoSuchBeanDefinitionException

解决方案

// 方式一:在启动类添加 @MapperScan
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication { }

// 方式二:在每个 Mapper 接口添加 @Mapper
@Mapper
public interface UserMapper { }

2. XML 文件找不到

问题:启动报错 BindingException: Invalid bound statement

解决方案

检查 mapper-locations 配置是否正确:

mybatis:
mapper-locations: classpath:mapper/**/*.xml

检查 XML 文件的 namespace 是否与接口全限定名一致:

<mapper namespace="com.example.demo.mapper.UserMapper">

3. 字段映射失败

问题:查询结果的字段为 null

解决方案

开启驼峰命名映射:

mybatis:
configuration:
map-underscore-to-camel-case: true

或在 Java 配置中:

@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setMapUnderscoreToCamelCase(true);
}

4. 事务不生效

问题@Transactional 注解不生效

解决方案

  • 确保启动类或配置类上有 @EnableTransactionManagement
  • 确保方法是 public 的
  • 确保异常类型在回滚范围内(默认只回滚 RuntimeException)
  • 确保不是同一个类内部调用(绕过了代理)
// 错误:内部调用
@Service
public class UserService {
public void methodA() {
this.methodB(); // 事务不生效
}

@Transactional
public void methodB() { }
}

// 正确:注入自身或使用 AopContext
@Service
public class UserService {
@Autowired
private UserService self; // 注入自身

public void methodA() {
self.methodB(); // 事务生效
}

@Transactional
public void methodB() { }
}

5. 多数据源事务问题

问题:跨数据源事务无法正常回滚

解决方案

使用分布式事务(如 Seata)或将操作拆分到独立事务中。

6. 连接池问题

问题:连接超时或连接池耗尽

解决方案

spring:
datasource:
hikari:
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1

最佳实践

1. 项目结构

src/main/java/com/example/demo/
├── config/ # 配置类
│ └── MyBatisConfig.java
├── controller/ # 控制器
│ └── UserController.java
├── service/ # 服务层
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
├── mapper/ # Mapper 接口
│ └── UserMapper.java
├── entity/ # 实体类
│ └── User.java
├── dto/ # 数据传输对象
│ └── UserDTO.java
├── vo/ # 视图对象
│ └── UserVO.java
└── DemoApplication.java

src/main/resources/
├── mapper/ # Mapper XML
│ └── UserMapper.xml
├── application.yml
└── application-dev.yml

2. 分环境配置

# application.yml
spring:
profiles:
active: dev

---
# application-dev.yml
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

---
# application-prod.yml
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

3. 类型处理器配置

@Component
@MappedTypes(Status.class)
public class StatusTypeHandler extends BaseTypeHandler<Status> {

@Override
public void setNonNullParameter(PreparedStatement ps, int i, Status parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter.getCode());
}

@Override
public Status getNullableResult(ResultSet rs, String columnName) throws SQLException {
int code = rs.getInt(columnName);
return Status.of(code);
}

// ... 其他方法
}

4. 插件配置

@Component
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlLogInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();

// 记录 SQL 执行时间
log.info("SQL 执行耗时: {}ms", end - start);

return result;
}
}

小结

本章介绍了 MyBatis 与 Spring Boot 的整合:

  • 快速开始:依赖、配置、基本使用
  • 配置详解:YAML 配置、Java 配置
  • Mapper 扫描:三种扫描方式
  • 事务管理:声明式事务配置
  • 多数据源:手动配置和动态数据源
  • 分页整合:PageHelper 使用
  • 日志配置:SQL 日志打印
  • 常见问题:问题排查和解决方案

整合后的 MyBatis 应用更加简洁,开发者可以专注于业务逻辑,框架会自动处理底层的连接管理、事务管理等繁琐工作。