1. 项目背景与核心价值
去年接手一个电商后台系统重构项目时,我面临一个关键决策:在Spring Boot 3的新技术栈下,如何设计持久层架构。当时团队内部就JPA与MyBatis的选择争论不休,最终我们选择了MyBatis方案——这个决定让系统在应对复杂业务查询时节省了40%的开发时间。今天我就来完整还原这套技术组合的实战配置过程。
现代Java后端开发中,Spring Boot 3.x与MyBatis的组合堪称黄金搭档。Spring Boot提供了开箱即用的项目脚手架和自动配置能力,而MyBatis则保留了SQL的灵活性与直观性。二者的结合既享受了现代框架的便利,又不会牺牲对SQL的精细控制权。特别适合以下场景:
- 需要复杂动态SQL的业务系统
- 遗留数据库表结构复杂的改造项目
- 对SQL性能有极致要求的OLTP场景
2. 环境准备与项目初始化
2.1 基础环境配置
推荐使用IDEA 2023+作为开发环境,配合JDK 17及以上版本。新建项目时务必注意:
bash复制Spring Initializr配置要点:
- Packaging: Jar
- Java: 17
- Dependencies:
- Spring Web
- Lombok
- MyBatis Framework
踩坑提示:Spring Boot 3.x默认使用Jakarta EE 9+,与旧版javax包不兼容。若遇到类找不到错误,检查依赖是否使用了正确版本。
2.2 数据库驱动选择
以MySQL 8.x为例,pom.xml需要显式声明驱动版本:
xml复制<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
为什么不用Spring Boot自动管理的版本?在金融级项目中,我们需要固定驱动版本以避免兼容性问题。曾经因为自动升级导致批量插入性能下降30%的事故让我记忆犹新。
3. MyBatis核心配置详解
3.1 数据源配置策略
application.yml的标准配置模板:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: Safepass123!
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
connection-timeout: 30000
关键参数解析:
useSSL=false:开发环境禁用SSL加速连接serverTimezone:必须显式设置避免时区问题maximum-pool-size= CPU核心数 * 2 + 1(公式来自HikariCP官方推荐)
3.2 MyBatis配置定制
创建mybatis-config.xml实现高级配置:
xml复制<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
<typeAliases>
<package name="com.example.entity"/>
</typeAliases>
</configuration>
这个配置实现了两个重要特性:
- 自动下划线转驼峰命名(user_name → userName)
- 明确指定NULL值的JDBC类型,避免TypeException
4. 数据库操作实战
4.1 注解式开发模式
基础CRUD示例(UserMapper.java):
java复制@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(@Param("id") Long id);
@Insert("INSERT INTO users(name,email) VALUES(#{name},#{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET name=#{name} WHERE id=#{id}")
int updateName(@Param("id") Long id, @Param("name") String name);
}
经验之谈:@Options注解的useGeneratedKeys在MySQL自增主键场景下特别有用,插入后能自动回填主键到实体对象。
4.2 XML映射文件进阶
复杂查询示例(OrderMapper.xml):
xml复制<select id="selectOrdersWithItems" resultMap="orderResultMap">
SELECT o.*, i.item_name, i.price
FROM orders o
JOIN order_items i ON o.id = i.order_id
WHERE o.user_id = #{userId}
<if test="status != null">
AND o.status = #{status}
</if>
ORDER BY o.create_time DESC
</select>
<resultMap id="orderResultMap" type="Order">
<id property="id" column="id"/>
<collection property="items" ofType="OrderItem">
<result property="name" column="item_name"/>
<result property="price" column="price"/>
</collection>
</resultMap>
这种配置方式完美解决了N+1查询问题,一次SQL获取主订单和所有明细项。在千万级数据量的系统中,比JPA的关联查询性能提升显著。
5. 事务管理与性能优化
5.1 声明式事务配置
Service层事务示范:
java复制@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public void createUserWithProfile(User user, UserProfile profile) {
userMapper.insert(user);
profile.setUserId(user.getId());
profileMapper.insert(profile);
}
}
关键点说明:
@Transactional默认只回滚RuntimeException- 添加
rollbackFor = Exception.class确保所有异常都触发回滚 - 方法内调用的private方法不会生效事务
5.2 批量操作优化
MyBatis批量插入的三种方案对比:
| 方案 | 万条数据耗时 | 内存占用 | 适用场景 |
|---|---|---|---|
| 循环单次插入 | 45s | 低 | 小批量简单操作 |
| BatchExecutor | 8s | 中 | 常规批量插入 |
| 批量SQL拼接 | 3s | 高 | 大数据量导入 |
性能最佳的批量SQL拼接实现:
java复制@Insert("<script>" +
"INSERT INTO users(name,age) VALUES " +
"<foreach collection='list' item='user' separator=','>" +
"(#{user.name},#{user.age})" +
"</foreach>" +
"</script>")
void batchInsert(@Param("list") List<User> users);
6. 生产环境必备配置
6.1 SQL监控与慢查询
引入p6spy实现SQL日志美化:
xml复制<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId>
<version>1.9.0</version>
</dependency>
application.yml配置:
yaml复制spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/db
输出示例:
sql复制2023-07-20 14:30:22|0|statement|connection 5|SELECT * FROM users WHERE id=1
6.2 多数据源配置
多租户系统的典型配置:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.db1", sqlSessionTemplateRef = "db1SqlSessionTemplate")
public class Db1DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.db1")
public DataSource db1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/db1/*.xml"));
return bean.getObject();
}
}
7. 常见问题排查指南
7.1 典型异常解决方案
| 异常现象 | 可能原因 | 解决方案 |
|---|---|---|
| Invalid bound statement | Mapper接口与XML未正确绑定 | 检查namespace是否匹配接口全限定名 |
| Parameter 'xxx' not found | @Param注解缺失 | 多参数方法必须添加@Param注解 |
| Could not autowire SqlSessionFactory | MyBatis依赖缺失 | 确认spring-boot-starter-mybatis已引入 |
| 中文乱码 | 连接参数缺少characterEncoding | url追加useUnicode=true&characterEncoding=utf8 |
7.2 性能调优经验
-
连接池配置:监控HikariCP的活跃连接数,建议设置:
yaml复制hikari: maximum-pool-size: 20 minimum-idle: 5 idle-timeout: 600000 -
二级缓存陷阱:在分布式环境中,默认的PerpetualCache会导致数据不一致,建议:
- 开发环境禁用缓存方便调试
- 生产环境使用Redis等集中式缓存
-
分页优化:避免使用MyBatis-PageHelper的内存分页,推荐:
sql复制SELECT * FROM users ORDER BY id LIMIT #{offset}, #{size}
这套技术栈在我们订单系统中支撑了日均300万+的数据库操作,通过合理的配置和优化,平均响应时间控制在50ms以内。特别是在处理复杂报表查询时,直接编写优化后的SQL相比JPA的Criteria API有显著性能优势。