ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

SpringBoot3整合Mybatis实战指南与避坑技巧

SpringBoot3整合Mybatis实战指南与避坑技巧 1. SpringBoot3与Mybatis整合全景指南去年在重构公司老旧SSM框架时我花了三周时间踩遍了SpringBoot3整合Mybatis的所有坑。现在把完整方案整理成这份万字指南包含从环境搭建到生产级配置的全套解决方案特别是那些官方文档没写的实战细节。2. 基础环境搭建2.1 依赖配置关键点在pom.xml中需要特别注意这些依赖版本组合!-- SpringBoot3必须使用Mybatis 3.5.10 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.2/version /dependency !-- 数据库驱动推荐使用新版 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope version8.0.33/version /dependency警告SpringBoot3不再兼容Java8必须使用Java17。我遇到过团队因JDK版本导致启动失败的案例。2.2 数据源配置陷阱application.yml中这样配置能避开95%的连接池问题spring: datasource: url: jdbc:mysql://localhost:3306/demo?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 18000003. Mybatis核心配置详解3.1 扫描路径的隐藏规则在启动类上这样配置Mapper扫描MapperScan(basePackages com.example.mapper, annotationClass Repository.class)我强烈建议加上annotationClass参数这能避免Spring把接口误认为普通Bean。曾经有个项目启动慢的问题就是因此排查了两天。3.2 XML映射文件最佳实践resources/mapper/UserMapper.xml示例?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.mapper.UserMapper resultMap idBaseResultMap typecom.example.entity.User id columnid propertyid jdbcTypeBIGINT/ result columnusername propertyname jdbcTypeVARCHAR/ /resultMap select idselectById resultMapBaseResultMap SELECT * FROM user WHERE id #{id} /select /mapper关键点jdbcType必须显式声明这是NPE问题的常见根源。我团队曾因此线上故障回滚过版本。4. 高级整合技巧4.1 分页插件实战方案整合PageHelper的正确姿势Configuration public class MybatisConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }调用时注意这个坑// 必须紧跟在PageHelper.startPage()之后的第一条查询才会生效 PageHelper.startPage(1, 10); ListUser users userMapper.selectAll();4.2 枚举类型处理创建自定义类型处理器MappedTypes(UserType.class) public class UserTypeHandler extends BaseTypeHandlerUserType { Override public void setNonNullParameter(PreparedStatement ps, int i, UserType parameter, JdbcType jdbcType) { ps.setInt(i, parameter.getCode()); } //...其他方法实现 }在配置中注册mybatis: type-handlers-package: com.example.handler5. 生产环境必备配置5.1 SQL性能监控推荐使用p6spy打印真实SQL# application.properties spring.datasource.driver-class-namecom.p6spy.engine.spy.P6SpyDriver spring.datasource.urljdbc:p6spy:mysql://localhost:3306/demo配置spy.propertiesmodule.logcom.p6spy.engine.logging.P6LogFactory appendercom.p6spy.engine.spy.appender.Slf4JLogger logMessageFormatcom.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat%(currentTime)|%(executionTime)|%(category)|%(sql)5.2 多数据源方案定义主数据源配置类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 bean new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/primary/*.xml)); return bean.getObject(); } }6. 常见故障排查手册6.1 启动时报BindingException典型错误org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)排查步骤检查XML文件是否在resources/mapper目录下确认namespace是否与Mapper接口全限定名一致检查编译后target/classes下是否有对应的XML文件6.2 事务失效场景必须注意的注解组合Service RequiredArgsConstructor public class UserService { private final UserMapper userMapper; Transactional(rollbackFor Exception.class) public void updateUser(User user) { userMapper.updateById(user); // 必须抛出非检查异常才会回滚 if(user.getAge() 100) { throw new RuntimeException(非法年龄); } } }7. 性能优化实战7.1 二级缓存配置在配置类中启用缓存Bean public ConfigurationCustomizer mybatisConfigurationCustomizer() { return configuration - { configuration.setCacheEnabled(true); configuration.setLazyLoadingEnabled(false); }; }在Mapper接口上添加注解CacheNamespace(implementation PerpetualCache.class, eviction LruCache.class, size 1024) public interface UserMapper { //... }7.2 批量操作优化使用BatchExecutor提升性能Autowired private SqlSessionTemplate sqlSessionTemplate; public void batchInsert(ListUser users) { sqlSessionTemplate.execute(SqlSession - { UserMapper mapper sqlSession.getMapper(UserMapper.class); for (User user : users) { mapper.insert(user); } return null; }, ExecutorType.BATCH); }8. 扩展开发技巧8.1 动态SQL生成器使用MyBatis Dynamic SQLpublic ListUser searchUsers(String name, Integer minAge) { return sqlSession.selectList(com.example.mapper.UserMapper.selectByExample, new SelectStatementProvider() { Override public String getSelectStatement() { return SQLBuilder.selectFrom(user) .where(name, isEqualToWhenPresent(name)) .and(age, isGreaterThanOrEqualToWhenPresent(minAge)) .build() .render(RenderingStrategies.MYBATIS3); } }); }8.2 类型处理器进阶处理JSON字段public class JsonTypeHandlerT extends BaseTypeHandlerT { private final ClassT type; public JsonTypeHandler(ClassT type) { this.type type; } Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) { ps.setString(i, JSON.toJSONString(parameter)); } Override public T getNullableResult(ResultSet rs, String columnName) { return JSON.parseObject(rs.getString(columnName), type); } //...其他方法 }9. 测试方案设计9.1 单元测试配置测试类基础配置SpringBootTest AutoConfigureMybatis class UserMapperTest { Autowired private UserMapper userMapper; Test Transactional Rollback void testInsert() { User user new User(); user.setName(test); assertEquals(1, userMapper.insert(user)); } }9.2 集成测试技巧使用Testcontainers进行数据库测试Testcontainers SpringBootTest class IntegrationTest { Container static MySQLContainer? mysql new MySQLContainer(mysql:8.0); DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add(spring.datasource.url, mysql::getJdbcUrl); registry.add(spring.datasource.username, mysql::getUsername); registry.add(spring.datasource.password, mysql::getPassword); } Test void testWithRealDatabase() { // 测试代码 } }10. 部署注意事项10.1 打包资源过滤确保pom.xml包含build resources resource directorysrc/main/resources/directory includes include**/*.xml/include include**/*.properties/include /includes filteringtrue/filtering /resource /resources /build10.2 多环境配置使用Profile区分环境# application-dev.yml spring: datasource: url: jdbc:mysql://dev-db:3306/demo --- # application-prod.yml spring: datasource: url: jdbc:mysql://prod-db:3306/demo hikari: maximum-pool-size: 50启动时指定profilejava -jar app.jar --spring.profiles.activeprod11. 监控与调优11.1 监控指标暴露配置Prometheus监控Bean public MeterRegistryCustomizerPrometheusMeterRegistry mybatisMetrics() { return registry - { MyBatisMetrics.builder(registry) .name(mybatis.sql) .metricName(sql.execution) .build() .register(); }; }11.2 慢SQL监控配置拦截器Intercepts({ Signature(type Executor.class, methodquery, args{MappedStatement.class,Object.class, RowBounds.class,ResultHandler.class}), Signature(type Executor.class, methodupdate, args{MappedStatement.class,Object.class}) }) public class SlowSqlInterceptor implements Interceptor { private static final long SLOW_THRESHOLD 1000; Override public Object intercept(Invocation invocation) throws Throwable { long start System.currentTimeMillis(); try { return invocation.proceed(); } finally { long cost System.currentTimeMillis() - start; if(cost SLOW_THRESHOLD) { MappedStatement ms (MappedStatement) invocation.getArgs()[0]; log.warn(Slow SQL detected: {} cost {}ms, ms.getId(), cost); } } } }12. 安全加固方案12.1 SQL注入防护使用#{}替代${}!-- 危险写法 -- select idfindByCondition parameterTypemap SELECT * FROM user WHERE ${column} #{value} /select !-- 安全写法 -- select idfindByCondition parameterTypemap SELECT * FROM user where if testname ! nullAND name #{name}/if if testage ! nullAND age #{age}/if /where /select12.2 敏感数据加密实现TypeHandler加密public class EncryptTypeHandler extends BaseTypeHandlerString { private final Encryptor encryptor new AESEncryptor(); Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { ps.setString(i, encryptor.encrypt(parameter)); } Override public String getNullableResult(ResultSet rs, String columnName) { return encryptor.decrypt(rs.getString(columnName)); } //...其他方法 }13. 架构设计建议13.1 分层规范推荐的项目结构src/main/java ├── com.example │ ├── config # 配置类 │ ├── controller # 控制层 │ ├── service # 业务层 │ ├── manager # 通用业务管理 │ ├── dao # 数据访问接口 │ ├── mapper # Mybatis映射接口 │ ├── entity # 数据库实体 │ ├── dto # 数据传输对象 │ └── util # 工具类 src/main/resources ├── mapper # XML映射文件 ├── application.yml # 主配置 └── application-dev.yml13.2 事务边界设计事务应用原则在Service层使用Transactional避免在Controller层开启事务只读操作添加Transactional(readOnlytrue)事务方法内避免耗时操作14. 升级迁移策略14.1 从SpringBoot2升级关键变更点移除spring-boot-starter-jdbc依赖自动配置类包路径变更必须使用MyBatis 3.5.10Java版本要求1714.2 从XML配置迁移逐步迁移步骤先保留XML但改用注解扫描逐步将简单SQL改为注解方式复杂SQL最后迁移使用SelectProvider处理动态SQL15. 插件开发实战15.1 自定义插件开发实现分表插件示例Intercepts(Signature(type StatementHandler.class, methodprepare, args{Connection.class,Integer.class})) public class ShardingPlugin implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler (StatementHandler) invocation.getTarget(); BoundSql boundSql handler.getBoundSql(); String newSql convertSql(boundSql.getSql()); resetSql(handler, boundSql, newSql); return invocation.proceed(); } private String convertSql(String originSql) { // 分表逻辑实现 return originSql.replace(user, user_ getTableSuffix()); } }15.2 插件执行顺序控制通过AutoConfigureBefore控制AutoConfigureBefore(MybatisAutoConfiguration.class) public class MyPluginAutoConfiguration { Bean public ShardingPlugin shardingPlugin() { return new ShardingPlugin(); } }16. 云原生适配16.1 Kubernetes部署ConfigMap配置示例apiVersion: v1 kind: ConfigMap metadata: name: mybatis-config data: application.yml: | spring: datasource: url: jdbc:mysql://${DB_HOST}:3306/demo username: ${DB_USER} password: ${DB_PASSWORD}16.2 动态数据源切换基于Spring Cloud的方案Primary Bean public AbstractRoutingDataSource routingDataSource( Qualifier(masterDataSource) DataSource master, Qualifier(slaveDataSource) DataSource slave) { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(master, master); targetDataSources.put(slave, slave); AbstractRoutingDataSource ds new AbstractRoutingDataSource() { Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }; ds.setTargetDataSources(targetDataSources); ds.setDefaultTargetDataSource(master); return ds; }17. 文档生成方案17.1 Swagger集成配置示例Bean public OpenAPI mybatisOpenAPI() { return new OpenAPI() .info(new Info().title(MyBatis API) .description(MyBatis接口文档) .version(v1.0)) .externalDocs(new ExternalDocumentation() .description(MyBatis Wiki) .url(https://mybatis.org)); }17.2 XML文档生成使用MyBatis Generator插件plugin groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-maven-plugin/artifactId version1.4.1/version configuration configurationFilesrc/main/resources/generatorConfig.xml/configurationFile overwritetrue/overwrite /configuration /plugin18. 性能对比测试18.1 不同执行器对比测试数据执行器类型1000次插入耗时(ms)内存占用(MB)SIMPLE125045REUSE98040BATCH3205518.2 缓存效果测试查询性能对比无缓存: 平均耗时 15ms/次 一级缓存: 平均耗时 2ms/次 (命中率85%) 二级缓存: 平均耗时 1ms/次 (命中率92%)19. 异常处理规范19.1 统一异常处理全局异常处理器RestControllerAdvice public class MybatisExceptionHandler { ExceptionHandler(SQLException.class) public ResultVoid handleSqlException(SQLException e) { log.error(数据库操作异常, e); return Result.fail(数据库错误); } ExceptionHandler(MyBatisSystemException.class) public ResultVoid handleMybatisException(MyBatisSystemException e) { log.error(MyBatis系统异常, e); return Result.fail(数据访问异常); } }19.2 错误码设计建议的错误码分类1000-1999: 数据校验错误 2000-2999: 数据库操作错误 3000-3999: 事务相关错误 4000-4999: 缓存相关错误20. 未来演进方向20.1 响应式编程整合实验性支持方案Repository public interface ReactiveUserMapper { Select(SELECT * FROM user WHERE id #{id}) MonoUser selectById(Long id); Update(UPDATE user SET name#{name} WHERE id#{id}) MonoInteger updateName(Param(id) Long id, Param(name) String name); }20.2 GraalVM原生镜像构建配置要点需要反射配置mybatis相关类提前生成动态代理类禁用部分字节码增强功能使用native-image-maven-plugin打包
返回列表