Spring Boot+MyBatis调用MySQL存储过程实战指南

Spring Boot+MyBatis调用MySQL存储过程实战指南 1. 项目概述存储过程作为数据库层面的重要功能组件在企业级应用开发中扮演着关键角色。当我们需要在Spring Boot应用中调用存储过程时MyBatis作为持久层框架提供了灵活的实现方案。不同于简单的SQL映射存储过程调用涉及参数传递模式、结果集处理、事务边界控制等复杂场景。我在金融支付系统开发中曾处理过日均调用量超百万次的交易核对存储过程深刻体会到正确调用方式对系统稳定性的影响。本文将基于实战经验演示Spring BootMyBatis环境下存储过程调用的完整实现路径并分享性能优化、异常处理等方面的最佳实践。2. 核心设计解析2.1 存储过程定义规范在MySQL中创建规范的存储过程是成功调用的前提。建议采用以下模板DELIMITER // CREATE PROCEDURE proc_order_summary( IN merchant_id INT, OUT total_count INT, OUT total_amount DECIMAL(18,2) ) BEGIN SELECT COUNT(*), SUM(amount) INTO total_count, total_amount FROM orders WHERE merchant_id merchant_id AND status SUCCESS; END // DELIMITER ;关键设计要点明确区分IN/OUT参数类型金额等数值类型指定精度添加完整的业务条件过滤使用DELIMITER避免语法冲突2.2 MyBatis映射配置在Mapper XML中配置存储过程调用时需要特别注意参数模式声明select idcallOrderSummary statementTypeCALLABLE {call proc_order_summary( #{merchantId, modeIN, jdbcTypeINTEGER}, #{totalCount, modeOUT, jdbcTypeINTEGER}, #{totalAmount, modeOUT, jdbcTypeDECIMAL} )} /select必须设置的属性statementTypeCALLABLE 声明为存储过程调用每个参数的mode属性明确指定IN/OUTjdbcType确保类型匹配数据库定义3. 完整实现步骤3.1 项目环境搭建使用Spring Initializr创建项目时需包含Spring Web (提供Controller层支持)MyBatis Framework (核心持久层框架)MySQL Driver (数据库连接)pom.xml关键依赖版本建议dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope version8.0.33/version /dependency3.2 存储过程调用实现完整的服务层调用示例Service RequiredArgsConstructor public class OrderService { private final OrderMapper orderMapper; public OrderSummaryVO getOrderSummary(Integer merchantId) { MapString, Object params new HashMap(); params.put(merchantId, merchantId); params.put(totalCount, null); params.put(totalAmount, null); orderMapper.callOrderSummary(params); return OrderSummaryVO.builder() .totalCount((Integer)params.get(totalCount)) .totalAmount(new BigDecimal(params.get(totalAmount).toString())) .build(); } }关键实现细节使用Map封装输入输出参数OUT参数初始化为null结果转换时注意类型处理建议添加Transactional保证过程原子性4. 高级实践技巧4.1 结果集映射处理当存储过程返回游标时需特殊处理resultMap idorderResult typecom.example.Order id columnorder_id propertyid/ result columnorder_no propertyorderNo/ /resultMap select idcallOrderQuery statementTypeCALLABLE {call proc_query_orders( #{status, modeIN}, #{result, modeOUT, jdbcTypeCURSOR, resultMaporderResult} )} /select4.2 批量处理优化对于需要批量处理的场景Transactional public void batchUpdateOrders(ListOrder orders) { SqlSession session sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH); try { OrderMapper mapper session.getMapper(OrderMapper.class); orders.forEach(order - { MapString, Object params new HashMap(); params.put(orderId, order.getId()); params.put(newStatus, order.getStatus()); mapper.callUpdateStatus(params); }); session.commit(); } finally { session.close(); } }5. 生产环境注意事项5.1 性能监控配置在application.yml中添加MyBatis日志配置logging: level: com.example.mapper: debug配合P6Spy可获取完整执行SQLdependency groupIdp6spy/groupId artifactIdp6spy/artifactId version3.9.1/version /dependency5.2 常见问题排查参数类型不匹配错误检查jdbcType与存储过程定义是否一致特别注意DECIMAL类型的精度设置存储过程不存在异常确认数据库连接指向正确的schema检查存储过程名称大小写敏感性事务不生效问题确保Transactional注解生效检查数据库引擎是否支持事务(如MyISAM不支持)游标泄漏风险使用try-with-resources确保ResultSet关闭设置defaultResultSetTypeFORWARD_ONLY6. 架构设计建议在微服务架构下建议采用以下模式将复杂业务逻辑下沉到存储过程服务层仅负责参数组装和结果转换通过数据库连接池控制并发调用量对高频调用过程添加结果缓存HikariCP连接池推荐配置spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000 leak-detection-threshold: 60000对于需要处理数万条记录的场景建议采用分页调用模式CREATE PROCEDURE proc_large_query( IN page_num INT, IN page_size INT, OUT total_records INT ) BEGIN -- 计算总数 SELECT COUNT(*) INTO total_records FROM large_table; -- 分页查询 SELECT * FROM large_table LIMIT page_size OFFSET (page_num - 1) * page_size; END在Java调用侧实现分页迭代public T ListT queryByPage(PageQueryT query) { MapString, Object params new HashMap(); params.put(pageNum, query.getPageNum()); params.put(pageSize, query.getPageSize()); params.put(totalRecords, null); ListT data mapper.queryLargeData(params); query.setTotalRecords((Integer)params.get(totalRecords)); return data; }7. 安全防护措施SQL注入防护永远不要拼接SQL参数使用#{param}语法而非${param}权限控制为应用账号配置最小必要权限单独设置存储过程执行权限敏感数据保护对金额等字段使用加密传输日志中脱敏关键参数审计日志示例配置Around(execution(* com.example.mapper.*.*(..))) public Object logProcedureCall(ProceedingJoinPoint pjp) throws Throwable { String methodName pjp.getSignature().getName(); Object[] args pjp.getArgs(); auditLog.info(调用存储过程: {}, 参数: {}, methodName, maskSensitiveData(args)); return pjp.proceed(); }8. 性能优化方案索引优化分析存储过程执行计划确保WHERE条件字段有合适索引连接池调优根据并发量调整maxPoolSize设置合理的空闲超时时间结果缓存对实时性要求不高的结果添加缓存使用Cacheable注解实现批量操作合并多次调用为批量操作使用UNION ALL替代多次查询执行计划分析示例EXPLAIN ANALYZE CALL proc_complex_report(2023-01-01, 2023-12-31);9. 异常处理策略建议定义统一的异常处理体系ControllerAdvice public class ProcedureExceptionHandler { ExceptionHandler(MyBatisSystemException.class) public ResponseEntityErrorResult handleProcedureError(MyBatisSystemException e) { Throwable rootCause NestedExceptionUtils.getRootCause(e); if (rootCause instanceof SQLException) { SQLException sqlEx (SQLException)rootCause; return ResponseEntity.status(500) .body(ErrorResult.of(PROCEDURE_ERROR, 存储过程执行失败: sqlEx.getMessage())); } return ResponseEntity.status(500) .body(ErrorResult.of(SYSTEM_ERROR, 系统异常)); } }针对存储过程特别处理的错误码public enum ProcedureErrorCode { INVALID_PARAM(4001, 参数校验失败), DATA_NOT_FOUND(4004, 数据不存在), CONCURRENT_CONFLICT(4009, 并发操作冲突); private final int code; private final String message; // constructor getters }10. 现代架构演进随着云原生架构的普及存储过程的使用模式也在发生变化分布式事务场景使用Seata等分布式事务框架将本地事务与存储过程结合多数据源调用配置多个SqlSessionTemplate使用DS注解切换数据源存储过程编排将复杂流程拆分为多个存储过程使用状态表控制执行流程多数据源配置示例Configuration MapperScan(basePackages com.example.db1.mapper, sqlSessionTemplateRef db1SqlSessionTemplate) public class Db1DataSourceConfig { Bean ConfigurationProperties(spring.datasource.db1) public DataSource db1DataSource() { return DataSourceBuilder.create().build(); } Bean public SqlSessionTemplate db1SqlSessionTemplate( Qualifier(db1DataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean factory new SqlSessionFactoryBean(); factory.setDataSource(dataSource); return new SqlSessionTemplate(factory.getObject()); } }在需要处理超大规模数据的场景下可以考虑以下优化策略分库分表支持使用ShardingSphere等中间件在存储过程中处理分片逻辑异步调用模式将存储过程调用放入消息队列使用Async实现异步执行读写分离配置主从数据源读操作路由到从库异步调用示例Async(dbTaskExecutor) Transactional public CompletableFutureReportResult generateReportAsync(ReportRequest request) { MapString, Object params convertToParams(request); mapper.callReportProcedure(params); return CompletableFuture.completedFuture(extractResult(params)); }线程池配置建议spring: task: execution: pool: core-size: 5 max-size: 20 queue-capacity: 100 thread-name-prefix: db-task-