Spring Boot 整合
MyBatis 与 Spring Boot 的整合是现代 Java 开发的标准配置。通过 mybatis-spring-boot-starter,可以快速搭建 MyBatis 应用,几乎零配置即可运行。本章将详细介绍整合过程和最佳实践。
为什么需要整合?
在纯 MyBatis 应用中,开发者需要手动管理 SqlSessionFactory、SqlSession 的创建和关闭,以及事务的提交和回滚。这些重复的样板代码不仅繁琐,还容易出错。
Spring Boot 整合后的优势:
- 自动配置:自动创建和注入
SqlSessionFactory、SqlSessionTemplate - 自动扫描:自动扫描并注册 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-Starter | MyBatis-Spring | Spring Boot | Java |
|---|---|---|---|
| 3.0 | 3.0 | 3.0+ | 17+ |
| 2.3 | 2.1 | 2.7 | 8+ |
配置数据源
在 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-location | MyBatis 配置文件位置 |
mybatis.mapper-locations | Mapper 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 found 或 NoSuchBeanDefinitionException
解决方案:
// 方式一:在启动类添加 @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 应用更加简洁,开发者可以专注于业务逻辑,框架会自动处理底层的连接管理、事务管理等繁琐工作。