1. Spring Boot与MyBatis整合概述
在现代Java企业级应用开发中,Spring Boot和MyBatis的组合已经成为主流技术选型方案之一。作为一名长期使用这套技术栈的开发者,我想分享一些实战经验和深度配置技巧。
Spring Boot通过自动配置和约定优于配置的原则,极大地简化了Spring应用的初始搭建和开发过程。而MyBatis作为一款半自动化的ORM框架,在SQL灵活性和对象映射之间取得了很好的平衡。两者的结合既保留了Spring生态的便利性,又提供了对SQL的精细控制能力。
在实际项目中,这种组合特别适合以下场景:
- 需要精细控制SQL性能优化的项目
- 遗留数据库系统迁移项目
- 需要同时处理复杂SQL和简单CRUD的混合场景
- 对JPA的某些特性不够满意,希望获得更多灵活性的项目
2. 环境准备与项目初始化
2.1 项目创建最佳实践
使用Spring Initializr创建项目时,我通常会做以下选择:
- 打包方式:优先选择Maven(在企业环境中兼容性更好)
- Java版本:根据团队规范选择LTS版本(目前推荐JDK 17)
- 依赖项:初期只选Spring Web(即使需要Web功能),其他依赖后续手动添加
提示:不要在一开始就添加太多依赖,这可能导致依赖冲突且难以排查。MyBatis的依赖应该后续手动添加以保证版本可控。
2.2 依赖管理策略
在pom.xml中添加依赖时,建议采用以下方式管理版本:
xml复制<properties>
<mybatis-spring-boot-starter.version>2.2.2</mybatis-spring-boot-starter.version>
<mysql-connector-java.version>8.0.28</mysql-connector-java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
这种管理方式的好处是:
- 版本号集中管理,便于统一升级
- 明确指定每个依赖的版本,避免隐式依赖带来的冲突
- 数据库驱动设为runtime scope,避免在编译期不必要地引入
3. 数据库与MyBatis深度配置
3.1 数据源高级配置
在application.yml中,我通常会扩展以下配置:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
username: app_user
password: ${DB_PASSWORD:default_password}
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
关键配置说明:
- 连接URL中添加了字符集和时区参数,避免中文乱码问题
- 密码使用环境变量注入,提高安全性
- 配置了HikariCP连接池(Spring Boot默认使用)的详细参数
3.2 MyBatis配置优化
完整的MyBatis配置示例:
yaml复制mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.demo.entity
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
cache-enabled: true
lazy-loading-enabled: true
multiple-result-sets-enabled: true
use-column-label: true
use-generated-keys: true
重要参数解析:
map-underscore-to-camel-case:自动将下划线列名映射为驼峰属性名default-statement-timeout:设置SQL执行超时时间(秒)use-generated-keys:启用自动主键回填功能
4. 开发模式与最佳实践
4.1 实体类设计技巧
对于User实体类,我通常会进行以下增强:
java复制@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
@JsonIgnore
private String password;
private LocalDateTime createTime;
private LocalDateTime updateTime;
@Transient
private String token;
}
注解说明:
@Data:Lombok注解,自动生成getter/setter@JsonIgnore:序列化时忽略敏感字段@Transient:标记非持久化字段
4.2 Mapper接口设计
进阶的Mapper接口设计示例:
java复制@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(@Param("id") Integer id);
@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(UserQuery query);
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("INSERT INTO user(username,password) VALUES(#{username},#{password})")
int insert(User user);
@Update("UPDATE user SET username=#{username} WHERE id=#{id}")
int updateUsername(@Param("id") Integer id, @Param("username") String username);
@Delete("DELETE FROM user WHERE id=#{id}")
int deleteById(Integer id);
}
高级特性:
@SelectProvider:动态SQL构建@Options:配置主键回填@Param:明确参数命名
4.3 动态SQL构建
创建UserSqlProvider类:
java复制public class UserSqlProvider {
public String selectByCondition(UserQuery query) {
return new SQL() {{
SELECT("*");
FROM("user");
if (query.getUsername() != null) {
WHERE("username LIKE #{username} || '%'");
}
if (query.getStartTime() != null) {
WHERE("create_time >= #{startTime}");
}
if (query.getEndTime() != null) {
WHERE("create_time <= #{endTime}");
}
ORDER_BY("id DESC");
}}.toString();
}
}
5. 事务管理与性能优化
5.1 事务配置实践
在Service层使用事务:
java复制@Service
@Transactional(readOnly = true)
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public int createUser(User user) {
// 业务校验逻辑
if (user.getUsername() == null || user.getUsername().length() < 4) {
throw new IllegalArgumentException("用户名不合法");
}
return userMapper.insert(user);
}
public User getById(Integer id) {
return userMapper.selectById(id);
}
}
事务要点:
- 类级别设置
@Transactional(readOnly = true)作为默认值 - 写操作方法单独使用
@Transactional覆盖默认设置 - 在事务方法中抛出RuntimeException会触发回滚
5.2 二级缓存配置
完整的二级缓存配置示例:
xml复制<cache type="org.mybatis.caches.ehcache.EhcacheCache"
eviction="LRU"
flushInterval="60000"
size="1000"
readOnly="false"
blocking="true"/>
缓存策略选择:
- LRU:最近最少使用(默认)
- FIFO:先进先出
- SOFT:软引用
- WEAK:弱引用
6. 常见问题排查
6.1 典型问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动时报Mapper找不到 | 1. 未加@Mapper注解 2. 扫描路径配置错误 |
1. 添加@Mapper注解 2. 检查@MapperScan配置 |
| SQL执行参数为null | 参数未正确使用@Param注解 | 在接口方法参数前添加@Param |
| 中文乱码 | 1. 数据库字符集不匹配 2. 连接字符串未指定编码 |
1. 检查数据库字符集 2. 添加useUnicode=true&characterEncoding=utf-8 |
| 主键未回填 | 未配置useGeneratedKeys | 在@Options或XML中配置useGeneratedKeys |
6.2 性能优化建议
-
SQL优化:
- 避免SELECT *,只查询需要的字段
- 复杂查询考虑使用@SelectProvider构建动态SQL
- 合理使用索引
-
连接池配置:
- 根据系统负载调整maximum-pool-size
- 设置合理的超时时间
-
缓存策略:
- 读多写少的数据启用二级缓存
- 频繁变更的数据禁用缓存
-
批量操作:
- 使用BatchExecutor执行批量插入
- 考虑使用MyBatis-Plus的批量操作方法
7. 进阶功能实现
7.1 插件开发示例
实现一个简单的SQL执行时间统计插件:
java复制@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
String sqlId = ((MappedStatement) invocation.getArgs()[0]).getId();
System.out.printf("SQL [%s] executed in %d ms%n", sqlId, cost);
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可接收配置参数
}
}
注册插件:
java复制@Configuration
public class MyBatisConfig {
@Bean
public SqlCostInterceptor sqlCostInterceptor() {
return new SqlCostInterceptor();
}
}
7.2 多数据源配置
在实际项目中,经常需要配置多个数据源:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return sessionFactory.getObject();
}
@Bean
public DataSourceTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
8. 测试策略
8.1 单元测试配置
使用Spring Boot Test进行Mapper层测试:
java复制@SpringBootTest
@Transactional
@Rollback
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testInsert() {
User user = User.builder().username("test").password("123456").build();
int affected = userMapper.insert(user);
assertEquals(1, affected);
assertNotNull(user.getId());
}
@Test
void testSelectById() {
User user = userMapper.selectById(1);
assertNotNull(user);
assertEquals("admin", user.getUsername());
}
}
测试要点:
@Transactional和@Rollback确保测试不会污染数据库- 测试数据可以使用
@Sql注解预先加载 - 考虑使用H2内存数据库进行快速测试
8.2 集成测试建议
- 测试事务回滚行为
- 验证缓存生效情况
- 测试批量操作的性能
- 验证多数据源切换是否正确
9. 生产环境建议
9.1 监控配置
建议添加以下监控指标:
- 数据源连接池使用情况
- SQL执行时间统计
- 慢SQL报警
- 缓存命中率监控
9.2 安全实践
-
SQL注入防护:
- 永远不要拼接SQL语句
- 使用#{}而不是${}进行参数替换
- 对用户输入进行严格校验
-
敏感数据保护:
- 密码字段使用加密存储
- 在实体类中使用@JsonIgnore避免敏感信息泄露
- 考虑使用Jasypt加密配置文件中的密码
-
权限控制:
- 数据库用户使用最小权限原则
- 不同环境使用不同的数据库账号
10. 项目结构建议
推荐的项目结构:
code复制src/main/java
├── com.example.demo
│ ├── config/ # 配置类
│ ├── controller/ # 控制器
│ ├── service/ # 服务层
│ ├── mapper/ # Mapper接口
│ ├── entity/ # 实体类
│ ├── dto/ # 数据传输对象
│ ├── vo/ # 视图对象
│ ├── util/ # 工具类
│ └── DemoApplication.java
src/main/resources
├── mapper/ # XML映射文件
├── static/ # 静态资源
├── templates/ # 模板文件
├── application.yml # 主配置文件
└── application-dev.yml # 开发环境配置
这种结构的好处是:
- 各层职责清晰
- 便于团队协作
- 易于扩展和维护
- 符合Spring Boot的约定
11. 性能调优实战
11.1 批量插入优化
使用BatchExecutor进行批量插入:
java复制@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public void batchInsert(List<User> users) {
sqlSessionTemplate.execute(SqlSession.Batch, session -> {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insert(user);
}
return null;
});
}
性能对比:
- 普通插入:1000条记录约5秒
- 批量插入:1000条记录约0.5秒
11.2 复杂查询优化
对于复杂分页查询,建议使用以下模式:
java复制public PageInfo<User> searchUsers(UserQuery query, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectByCondition(query);
return new PageInfo<>(users);
}
配合XML中的优化SQL:
xml复制<select id="selectByCondition" resultMap="userResultMap">
SELECT u.*, d.name AS dept_name
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
<where>
<if test="username != null">
AND u.username LIKE CONCAT(#{username}, '%')
</if>
<if test="deptId != null">
AND u.dept_id = #{deptId}
</if>
</where>
ORDER BY u.create_time DESC
</select>
12. 与Spring Boot特性整合
12.1 配置热加载
结合Spring Boot的@ConfigurationProperties实现MyBatis配置热加载:
java复制@Configuration
@ConfigurationProperties(prefix = "mybatis.configuration")
public class MyBatisConfig {
private boolean cacheEnabled;
private boolean lazyLoadingEnabled;
// 其他配置属性及setter方法
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> {
configuration.setCacheEnabled(cacheEnabled);
configuration.setLazyLoadingEnabled(lazyLoadingEnabled);
// 应用其他配置
};
}
}
12.2 健康检查
自定义健康检查指示器监控MyBatis状态:
java复制@Component
public class MyBatisHealthIndicator implements HealthIndicator {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Override
public Health health() {
try {
sqlSessionFactory.openSession().close();
return Health.up().build();
} catch (Exception e) {
return Health.down().withException(e).build();
}
}
}
13. 迁移与升级策略
13.1 从XML迁移到注解
渐进式迁移步骤:
- 新开发的Mapper直接使用注解方式
- 逐步将简单SQL的XML迁移到注解
- 复杂SQL暂时保留XML方式
- 最终统一代码风格
13.2 版本升级指南
从MyBatis 2.x升级到3.x的注意事项:
- 检查插件兼容性
- 验证缓存配置变化
- 测试动态SQL行为差异
- 检查TypeHandler的变化
14. 团队协作规范
14.1 开发约定
-
命名规范:
- Mapper接口:XxxMapper
- XML文件:XxxMapper.xml
- 方法名:selectXxx, insertXxx, updateXxx, deleteXxx
-
SQL风格:
- 关键字大写
- 缩进一致
- 复杂SQL添加注释
-
版本控制:
- XML文件与接口文件同步提交
- SQL变更记录在提交信息中
14.2 代码审查要点
审查MyBatis相关代码时应关注:
- SQL注入风险
- N+1查询问题
- 事务边界是否合理
- 缓存使用是否恰当
- 批量操作性能
15. 扩展与替代方案
15.1 MyBatis-Plus整合
MyBatis-Plus提供了更多便利功能:
java复制public interface UserMapper extends BaseMapper<User> {
// 自动获得CRUD方法
@Select("SELECT * FROM user WHERE age > #{age}")
List<User> selectByAge(@Param("age") Integer age);
}
优势:
- 通用CRUD操作
- 强大的条件构造器
- 分页插件
- 性能分析插件
15.2 与JPA混合使用
在某些场景下可以混合使用MyBatis和JPA:
java复制@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
@Transient
private String extraInfoFromMyBatis;
}
使用策略:
- 简单CRUD使用JPA
- 复杂查询使用MyBatis
- 通过@Transient字段共享数据
16. 实际项目经验分享
在电商项目中应用MyBatis的几个实用技巧:
-
商品搜索优化:
- 使用
@SelectProvider构建动态搜索条件 - 实现多字段模糊搜索
- 添加搜索建议功能
- 使用
-
订单分页查询:
- 使用PageHelper实现物理分页
- 添加常用查询条件的索引
- 优化count查询性能
-
数据权限控制:
- 通过拦截器自动添加数据权限条件
- 基于部门/角色过滤数据
- 实现行级安全控制
-
审计日志记录:
- 使用插件记录数据变更
- 保存修改前后的数据快照
- 记录操作人信息
17. 未来演进方向
随着项目发展,可以考虑以下演进路径:
-
多租户支持:
- 基于Schema的隔离
- 动态数据源切换
- 共享数据库独立Schema
-
读写分离:
- 主从数据源配置
- 读操作路由到从库
- 写操作路由到主库
-
分库分表:
- 使用ShardingSphere
- 按业务垂直拆分
- 按数据量水平拆分
-
弹性数据源:
- 动态添加/移除数据源
- 运行时配置变更
- 数据源健康检查
18. 疑难问题解决方案
18.1 大字段处理
处理CLOB/BLOB字段的最佳实践:
java复制public interface ContentMapper {
@Insert("INSERT INTO article(content) VALUES(#{content})")
int insert(@Param("content") String content);
@Select("SELECT content FROM article WHERE id=#{id}")
String getContentById(Integer id);
}
使用Stream方式处理大字段:
java复制@Select("SELECT content FROM large_content WHERE id=#{id}")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(String.class)
void streamContent(Integer id, ResultHandler<String> handler);
18.2 存储过程调用
调用存储过程示例:
java复制@Select("{CALL get_user_by_dept(#{deptId,mode=IN}, #{result,mode=OUT,jdbcType=CURSOR,resultMap=userResultMap})}")
@Options(statementType = StatementType.CALLABLE)
void getUserByDept(@Param("deptId") Integer deptId, @Param("result") List<User> result);
19. 监控与诊断
19.1 慢SQL监控
配置慢SQL阈值:
yaml复制mybatis:
configuration:
default-statement-timeout: 3
结合Spring Boot Actuator暴露端点:
java复制@Endpoint(id = "slow-sql")
@Component
public class SlowSqlEndpoint {
@Autowired
private List<DataSource> dataSources;
@ReadOperation
public Map<String, Object> slowSql() {
// 实现慢SQL统计逻辑
}
}
19.2 性能分析
使用P6Spy记录真实SQL:
xml复制<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
配置spy.properties:
code复制driverlist=com.mysql.cj.jdbc.Driver
dateformat=yyyy-MM-dd HH:mm:ss
outagedetection=true
outagedetectioninterval=30
20. 持续集成与部署
20.1 Flyway集成
结合Flyway管理数据库变更:
java复制@Configuration
public class FlywayConfig {
@Bean
public Flyway flyway(DataSource dataSource) {
return Flyway.configure()
.dataSource(dataSource)
.locations("classpath:db/migration")
.baselineOnMigrate(true)
.load();
}
}
SQL文件命名规范:
- V1__Initial_schema.sql
- V2__Add_user_table.sql
- V3__Alter_user_table_add_email.sql
20.2 多环境配置
环境特定配置示例:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://dev-db:3306/mydb
username: dev_user
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/mydb
username: prod_user
password: ${DB_PASSWORD}
激活环境:
bash复制java -jar app.jar --spring.profiles.active=prod