You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何捕获@SchedulerLock注解内抛出的异常?

捕获Shedlock @SchedulerLock注解内的数据库连接异常方案

问题背景

我们的Spring Boot API采用Quartz+Shedlock实现批处理任务,当数据库连接不可用时,@SchedulerLock注解的锁操作会抛出异常。由于该异常发生在任务启动前,无法在业务代码中捕获,需要找到捕获该异常的方案以便及时告警。

使用的注解:

@SchedulerLock(name = "AurCifBatchTask", lockAtLeastFor = "${quartz.cif.batch.lock-at-least}", lockAtMostFor = "${quartz.cif.batch.lock-at-most}")

核心异常信息:

org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction
Caused by: java.sql.SQLTransientConnectionException: aur-service - Connection is not available, request timed out after 30002ms.
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The connection to the host YKE0-D16GP0108.devfg.rbc.com, named instance in08 failed. Error: "java.net.UnknownHostException: YKE0-D16GP0108.devfg.rbc.com"

可行解决方案

方案1:自定义Shedlock LockProvider包装类

实现自定义LockProvider,对原生LockProvider的lock方法做异常拦截,捕获数据库连接类异常并触发告警:

@Component
public class ExceptionHandlingLockProvider implements LockProvider {

    private final LockProvider delegate;
    private final AlertService alertService; // 自定义告警服务

    public ExceptionHandlingLockProvider(LockProvider delegate, AlertService alertService) {
        this.delegate = delegate;
        this.alertService = alertService;
    }

    @Override
    public Optional<Lock> lock(LockConfiguration lockConfiguration) {
        try {
            return delegate.lock(lockConfiguration);
        } catch (RuntimeException e) {
            if (isConnectionException(e)) {
                alertService.sendAlert("Shedlock锁操作失败:数据库连接异常", e);
            }
            throw e; // 抛出异常让Quartz处理任务失败逻辑
        }
    }

    private boolean isConnectionException(Throwable e) {
        if (e instanceof CannotCreateTransactionException) {
            return true;
        }
        if (e instanceof SQLTransientConnectionException) {
            return true;
        }
        if (e instanceof SQLServerException && 
            (e.getMessage().contains("Connection is not available") || e.getMessage().contains("UnknownHostException"))) {
            return true;
        }
        return e.getCause() != null && isConnectionException(e.getCause());
    }
}

在Shedlock配置类中替换默认LockProvider:

@Configuration
public class ShedlockConfig {

    @Bean
    public LockProvider lockProvider(DataSource dataSource, AlertService alertService) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcLockProvider jdbcLockProvider = JdbcLockProvider.builder()
                .withJdbcTemplate(jdbcTemplate)
                .usingDbTime()
                .build();
        return new ExceptionHandlingLockProvider(jdbcLockProvider, alertService);
    }
}

方案2:Spring AOP拦截@SchedulerLock方法

通过AOP环绕通知拦截所有标注@SchedulerLock的方法,捕获前置锁操作抛出的异常:

@Aspect
@Component
public class SchedulerLockExceptionAspect {

    private final AlertService alertService;

    public SchedulerLockExceptionAspect(AlertService alertService) {
        this.alertService = alertService;
    }

    @Around("@annotation(net.javacrumbs.shedlock.spring.annotation.SchedulerLock)")
    public Object handleSchedulerLockException(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            return joinPoint.proceed();
        } catch (RuntimeException e) {
            if (isConnectionException(e)) {
                String taskName = joinPoint.getSignature().getName();
                alertService.sendAlert("任务[" + taskName + "]Shedlock锁操作失败:数据库连接异常", e);
            }
            throw e;
        }
    }

    private boolean isConnectionException(Throwable e) {
        // 复用方案1的异常判断逻辑
        if (e instanceof CannotCreateTransactionException) {
            return true;
        }
        if (e instanceof SQLTransientConnectionException) {
            return true;
        }
        if (e instanceof SQLServerException && 
            (e.getMessage().contains("Connection is not available") || e.getMessage().contains("UnknownHostException"))) {
            return true;
        }
        return e.getCause() != null && isConnectionException(e.getCause());
    }
}

方案3:自定义Quartz JobFactory

如果使用Quartz触发任务,可自定义JobFactory在任务实例化阶段捕获异常:

@Component
public class AlertingJobFactory extends SpringBeanJobFactory {

    private final AlertService alertService;
    private final AutowireCapableBeanFactory beanFactory;

    public AlertingJobFactory(AlertService alertService, AutowireCapableBeanFactory beanFactory) {
        this.alertService = alertService;
        this.beanFactory = beanFactory;
    }

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        beanFactory.autowireBean(jobInstance);
        return jobInstance;
    }

    @Override
    public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
        try {
            return super.newJob(bundle, scheduler);
        } catch (SchedulerException e) {
            if (isConnectionException(e.getCause())) {
                String jobName = bundle.getJobDetail().getKey().getName();
                alertService.sendAlert("Quartz任务[" + jobName + "]Shedlock锁操作失败:数据库连接异常", e.getCause());
            }
            throw e;
        }
    }

    private boolean isConnectionException(Throwable e) {
        // 复用异常判断逻辑
        if (e == null) return false;
        if (e instanceof CannotCreateTransactionException) {
            return true;
        }
        if (e instanceof SQLTransientConnectionException) {
            return true;
        }
        if (e instanceof SQLServerException && 
            (e.getMessage().contains("Connection is not available") || e.getMessage().contains("UnknownHostException"))) {
            return true;
        }
        return isConnectionException(e.getCause());
    }
}

配置Quartz使用自定义JobFactory:

@Configuration
public class QuartzConfig {

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, AlertingJobFactory jobFactory) {
        SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
        schedulerFactory.setDataSource(dataSource);
        schedulerFactory.setJobFactory(jobFactory);
        // 其他Quartz配置项
        return schedulerFactory;
    }
}

内容的提问来源于stack exchange,提问作者Bruce Xie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 07:54:52