[java] Quartz Scheduler와 데이터베이스 잠금 제어 연동하기

소개

Quartz Scheduler는 Java 애플리케이션에서 주기적 또는 일정 시간에 작업을 실행할 수 있는 강력한 도구입니다. 하지만 여러 개의 애플리케이션 인스턴스가 동일한 작업을 동시에 실행하면 문제가 발생할 수 있습니다. 이러한 상황에서 데이터베이스 잠금 제어를 이용하여 작업의 실행을 제어할 수 있습니다. 이 글에서는 Quartz Scheduler와 데이터베이스 잠금 제어를 연동하는 방법을 알아보겠습니다.

Quartz Scheduler 설정

먼저 Quartz Scheduler를 설정해야 합니다. 아래는 간단한 Quartz Scheduler 설정 파일의 예시입니다.

Properties props = new Properties();
props.put("org.quartz.scheduler.instanceName", "QuartzScheduler");
props.put("org.quartz.threadPool.threadCount", "1");
props.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
props.put("org.quartz.jobStore.dataSource", "myDS");
props.put("org.quartz.dataSource.myDS.driver", "com.mysql.jdbc.Driver");
props.put("org.quartz.dataSource.myDS.URL", "jdbc:mysql://localhost/quartz");
props.put("org.quartz.dataSource.myDS.user", "root");
props.put("org.quartz.dataSource.myDS.password", "password");
props.put("org.quartz.dataSource.myDS.maxConnections", "5");

SchedulerFactory schedulerFactory = new StdSchedulerFactory(props);
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.start();

위 설정 예시에서는 JobStoreTX를 사용하고, 데이터베이스는 MySQL을 사용합니다.

데이터베이스 잠금 제어 설정

다음으로 데이터베이스에서 잠금을 제어하기 위해 테이블을 생성해야 합니다. Quartz Scheduler는 작업을 제어하기 위해 QRTZ_LOCKS 테이블을 사용합니다. 아래는 MySQL 데이터베이스에서 사용할 수 있는 QRTZ_LOCKS 테이블의 스키마 예시입니다.

create table QRTZ_LOCKS
(
    SCHED_NAME varchar(120) not null,
    LOCK_NAME  varchar(40)  not null,
    PRIMARY KEY (SCHED_NAME, LOCK_NAME)
);

이제 Quartz Scheduler와 데이터베이스 잠금 제어를 연동할 준비가 되었습니다.

작업 실행 시 잠금 획득

Quartz Scheduler에서 작업을 실행하기 전에 잠금을 획득해야 합니다. 이는 다음과 같이 Job 클래스의 execute() 메서드에서 수행할 수 있습니다.

public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 잠금 획득
        boolean lockAcquired = false;
        Connection connection = null;
        try {
            JobDataMap dataMap = context.getJobDetail().getJobDataMap();
            String jobName = (String) dataMap.get("jobName");
            
            // 데이터베이스 연결
            DataSource dataSource = (DataSource) context.getScheduler().getContext().get("myDS");
            connection = dataSource.getConnection();
            
            // 잠금 획득 시도
            lockAcquired = tryAcquireLock(connection, jobName);
            if (!lockAcquired) {
                // 잠금 획득 실패 시 작업 종료
                return;
            }
            
            // 작업 실행 로직 작성
            // ...
        } catch (Exception e) {
            // 예외 처리
        } finally {
            // 잠금 해제
            if (lockAcquired) {
                releaseLock(connection);
            }
            
            // 데이터베이스 연결 해제
            if (connection != null) {
                connection.close();
            }
        }
    }
    
    private boolean tryAcquireLock(Connection connection, String jobName) throws SQLException {
        PreparedStatement ps = connection.prepareStatement("INSERT INTO QRTZ_LOCKS(SCHED_NAME, LOCK_NAME) VALUES (?, ?)");
        ps.setString(1, "QuartzScheduler");
        ps.setString(2, jobName);
        int result = ps.executeUpdate();
        ps.close();
        return result == 1;
    }
    
    private void releaseLock(Connection connection) throws SQLException {
        PreparedStatement ps = connection.prepareStatement("DELETE FROM QRTZ_LOCKS WHERE SCHED_NAME = ?");
        ps.setString(1, "QuartzScheduler");
        ps.executeUpdate();
        ps.close();
    }
}

위 코드에서 tryAcquireLock() 메서드는 잠금을 획득하는 메서드이고, releaseLock() 메서드는 잠금을 해제하는 메서드입니다. 이렇게 작업 실행 전후에 잠금을 제어하여 작업을 동시에 실행하지 않도록 할 수 있습니다.

결론

이제 Quartz Scheduler와 데이터베이스 잠금 제어를 연동하는 방법에 대해서 알아보았습니다. 이를 통해 여러 개의 애플리케이션 인스턴스에서 동시에 작업을 실행할 때 발생할 수 있는 문제를 방지할 수 있습니다.