Spring Boot调度器使用JPA保存记录时事务提交失败问题排查
问题分析
错误的核心原因是定时任务执行时不存在用户安全上下文:
- 接口调用场景下,用户经过认证流程,
SecurityContextHolder.getContext().authentication持有有效用户信息; - 但Spring Scheduler的定时任务是后台自动触发,未经过认证环节,
authentication为null,调用authentication.name时触发空指针异常,最终导致事务提交失败并回滚。
解决方案
修改JpaAuditingConfiguration中的auditorProvider Bean,增加对authentication为空的判断逻辑,根据业务需求选择以下处理方式:
方案1:返回默认系统操作标识
如果审计字段要求非空,可返回一个固定标识(比如"system")代表系统自动操作:
@Configuration @EnableJpaAuditing(auditorAwareRef = "auditorProvider") class JpaAuditingConfiguration { @Bean fun auditorProvider(): AuditorAware<String> { return AuditorAware { val authentication = SecurityContextHolder.getContext().authentication // 若认证信息为空,返回默认系统标识 Optional.ofNullable(authentication?.name ?: "system") } } }
方案2:允许审计者字段为空
如果实体类中的createdBy/lastModifiedBy字段支持null值,可直接返回空的Optional:
@Configuration @EnableJpaAuditing(auditorAwareRef = "auditorProvider") class JpaAuditingConfiguration { @Bean fun auditorProvider(): AuditorAware<String?> { return AuditorAware { val authentication = SecurityContextHolder.getContext().authentication Optional.ofNullable(authentication?.name) } } }
额外优化:定时任务事务管理
建议给定时任务方法添加事务注解,减少事务开启/提交的开销,同时优化异常日志打印:
@Component class Scheduler( private val repository: Repository ) { private val logger = LoggerFactory.getLogger(javaClass) @Scheduled(cron = "0 0 2 * * *") @Transactional // 批量事务管理 fun expire() { val records = repository.findRecords() for (record in records) { try { record.active = false repository.save(record) } catch (ex: Exception) { logger.error("Error expiring record ${record.id}") logger.error("Exception detail:", ex) // 正确打印完整异常栈 continue } } } }
注:原代码中
logger.error("Exception: ${ex.printStackTrace()}")存在错误,printStackTrace()返回void,会导致日志输出Exception: kotlin.Unit,需改为直接传入异常对象打印完整栈信息。
内容的提问来源于stack exchange,提问作者Simo
相关产品推荐
相关产品推荐

