λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
BackEnd🌱/Spring

ShedLock으둜 닀쀑 μΈμŠ€ν„΄μŠ€ ν™˜κ²½μ—μ„œ 단일 μŠ€μΌ€μ€„λŸ¬ λ™μž‘ 보μž₯ν•˜κΈ°

by μ•ˆμ£Όν˜• 2023. 11. 24.

κ°œμš”

μŠ€ν”„λ§μ—μ„œ μ œκ³΅ν•˜λŠ” @EnableScheduling μ–΄λ…Έν…Œμ΄μ…˜μ€ κ°„νŽΈν•˜κ²Œ μŠ€μΌ€μ€„λ§ μž‘μ—…μ„ μ„€μ •ν•  수 있게 ν•΄ μ£Όμ§€λ§Œ, 닀쀑 μΈμŠ€ν„΄μŠ€ ν™˜κ²½μ—μ„œλŠ” λ™μΌν•œ μž‘μ—…μ΄ μ—¬λŸ¬ 번 싀행될 수 μžˆλ‹€λŠ” λ¬Έμ œκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€.

이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„  λ‹€μ–‘ν•œ 방법이 μžˆμ§€λ§Œ, ShedLockμ΄λΌλŠ” μ˜€ν”ˆμ†ŒμŠ€ 라이브러리λ₯Ό μ‚¬μš©ν•˜λ©΄ μ†μ‰½κ²Œ ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ShedLock은 μŠ€ν”„λ§ μŠ€μΌ€μ€„λ§κ³Ό ν•¨κ»˜ μ‚¬μš©λ  수 있으며, μ—¬λŸ¬ μΈμŠ€ν„΄μŠ€κ°€ λ™μΌν•œ μŠ€μΌ€μ€„λ§ μž‘μ—…μ„ λ™μ‹œμ— μ‹€ν–‰ν•˜λŠ” 것을 방지해 μ€λ‹ˆλ‹€. 

ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” κΉƒν—ˆλΈŒμ—μ„œ ν™•μΈν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

ShedLock μ΄ν•΄ν•˜κΈ°

ShedLock은 λΆ„μ‚°λœ μ‹œμŠ€ν…œ ν™˜κ²½μ—μ„œ λ™μΌν•œ μŠ€μΌ€μ€„λ§ μž‘μ—…μ΄ μ€‘λ³΅μœΌλ‘œ μˆ˜ν–‰λ˜λŠ” 것을 λ°©μ§€ν•˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. 이 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” μ§€μ •λœ μž‘μ—…μ— 잠금 λ©”μ»€λ‹ˆμ¦˜μ„ μ μš©ν•˜μ—¬, ν•΄λ‹Ή μž‘μ—…μ΄ μ™„μ „νžˆ μ’…λ£Œλ  λ•ŒκΉŒμ§€ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œλŠ” λ™μΌν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μ—†κ²Œ ν•©λ‹ˆλ‹€.

ShedLock의 λ‚΄λΆ€ λ™μž‘ 방식은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

  1. 잠금 νšλ“: ShedLock은 λ°μ΄ν„°λ² μ΄μŠ€, Redis, Zookeeper λ“±μ˜ μ™ΈλΆ€ μ €μž₯μ†Œλ₯Ό μ‚¬μš©ν•˜μ—¬ 잠금 μƒνƒœλ₯Ό κ΄€λ¦¬ν•©λ‹ˆλ‹€. μŠ€μΌ€μ€„λ§ μž‘μ—…μ΄ μ‹€ν–‰λ˜κΈ° 전에 ShedLock은 λ¨Όμ € μ™ΈλΆ€ μ €μž₯μ†Œμ—μ„œ ν•΄λ‹Ή μž‘μ—…μ˜ 잠금 μƒνƒœλ₯Ό ν™•μΈν•©λ‹ˆλ‹€. λ§Œμ•½ ν•΄λ‹Ή μž‘μ—…μ— λŒ€ν•œ 잠금이 이미 νšλ“λ˜μ–΄ μžˆλŠ” 경우, λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œλŠ” ν•΄λ‹Ή μž‘μ—…μ„ μˆ˜ν–‰ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  2. 잠금 μœ μ§€: μŠ€μΌ€μ€„λ§ μž‘μ—…μ΄ μ‹œμž‘λ˜λ©΄, μž κΈˆμ„ νšλ“ν•œ μΈμŠ€ν„΄μŠ€λŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€. μ΄λ•Œ, ShedLock은 μž κΈˆμ„ μœ μ§€ν•˜μ—¬ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œ λ™μΌν•œ μž‘μ—…μ„ μ€‘λ³΅μœΌλ‘œ μ‹€ν–‰ν•˜μ§€ λͺ»ν•˜λ„둝 ν•©λ‹ˆλ‹€. μž κΈˆμ€ μ™ΈλΆ€ μ €μž₯μ†Œμ— μ €μž₯되며, 일정 μ‹œκ°„ λ™μ•ˆ μœ μ§€λ©λ‹ˆλ‹€.
  3. 잠금 κ°±μ‹ : μŠ€μΌ€μ€„λ§ μž‘μ—…μ˜ μ‹€ν–‰ μ‹œκ°„μ΄ 잠금의 μœ μ§€ μ‹œκ°„λ³΄λ‹€ κΈΈ 경우, ShedLock은 μž κΈˆμ„ κ°±μ‹ ν•˜μ—¬ μž‘μ—…μ΄ μ™„λ£Œλ  λ•ŒκΉŒμ§€ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œ μž‘μ—…μ„ μˆ˜ν–‰ν•˜μ§€ λͺ»ν•˜λ„둝 ν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 μž‘μ—…μ˜ 쀑볡 싀행을 λ°©μ§€ν•©λ‹ˆλ‹€.
  4. 잠금 ν•΄μ œ: μŠ€μΌ€μ€„λ§ μž‘μ—…μ΄ μ™„λ£Œλ˜λ©΄, ShedLock은 μž κΈˆμ„ μžλ™μœΌλ‘œ ν•΄μ œν•©λ‹ˆλ‹€. μž κΈˆμ€ μ™ΈλΆ€ μ €μž₯μ†Œμ—μ„œ μ œκ±°λ˜μ–΄ λ‹€μŒ μ‹€ν–‰ μ£ΌκΈ°μ—μ„œ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€κ°€ μž‘μ—…μ„ μˆ˜ν–‰ν•  수 있게 λ©λ‹ˆλ‹€.

ShedLock은 λΆ„μ‚°λœ ν™˜κ²½μ—μ„œ μŠ€μΌ€μ€„λ§ μž‘μ—…μ˜ 쀑볡 싀행을 λ°©μ§€ν•˜μ—¬ μ‹œμŠ€ν…œμ˜ μ•ˆμ •μ„±κ³Ό 일관성을 보μž₯ν•©λ‹ˆλ‹€. μ™ΈλΆ€ μ €μž₯μ†Œλ₯Ό μ‚¬μš©ν•˜μ—¬ 잠금 μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λ―€λ‘œ, μ—¬λŸ¬ μΈμŠ€ν„΄μŠ€ 간에 잠금 정보λ₯Ό κ³΅μœ ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 λ™μΌν•œ μž‘μ—…μ΄ μ€‘λ³΅μœΌλ‘œ μ‹€ν–‰λ˜λŠ” 것을 λ°©μ§€ν•˜κ³ , μž‘μ—…μ˜ μ‹€ν–‰ μˆœμ„œμ™€ 일관성을 μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

ShedLock μ‚¬μš©ν•˜κΈ°

ShedLock은 λ‹€μ–‘ν•œ RDBMS, NOSQL, In-Memory DB λ“± λ‹€μ–‘ν•œ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ‚¬μš©λ  수 μžˆλŠ”λ°, ν…ŒμŠ€νŠΈλŠ” MariaDBλ₯Ό μ‚¬μš©ν•˜λŠ” ν™˜κ²½μ—μ„œ 진행해 λ³΄κ² μŠ΅λ‹ˆλ‹€.

μ˜μ‘΄μ„± μΆ”κ°€

Gradle에 μ•„λž˜ 라이브러리λ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€.

implementation 'net.javacrumbs.shedlock:shedlock-spring:5.10.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.10.0'

lockProvider μ •μ˜

λ‹€μŒμœΌλ‘œ Configλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. 

@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("scheduled-task-pool-");
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                        .withJdbcTemplate(new JdbcTemplate(dataSource))
                        .usingDbTime()
                        .build()
        );
    }
}

μœ„μ˜ μ½”λ“œμ—μ„œ @EnableSchedulerLock(defaultLockAtMostFor = "PT30S")λ₯Ό 톡해 ShedLock의 μŠ€μΌ€μ€„λŸ¬ 잠금 κΈ°λŠ₯을 ν™œμ„±ν™”ν•˜λ©°, defaultLockAtMostForλŠ” κΈ°λ³Έ 잠금의 μ΅œλŒ€ μœ μ§€ μ‹œκ°„μ„ μ„€μ •ν•©λ‹ˆλ‹€.

lockProviderλŠ” ShedLock이 잠금 μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 데 μ‚¬μš©λ˜λŠ” beanμž…λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” JdbcTemplateLockProviderλ₯Ό μ‚¬μš©ν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€ 연결을 κ΄€λ¦¬ν•˜λŠ” DataSourceλ₯Ό 인자둜 λ°›μŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 ShedLock은 λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ΄μš©ν•˜μ—¬ 잠금 μƒνƒœλ₯Ό 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.

μœ„μ— 적힌 μ„€μ • 말고도 JdbcTemplateLockProvider에 λ“€μ–΄κ°ˆ 수 μžˆλŠ” μ˜΅μ…˜λ“€μ€ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

  • withJdbcTemplate(JdbcTemplate jdbcTemplate): JdbcTemplate 객체λ₯Ό μ„€μ •.
  • usingDbTime(): λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ˜ μ‹œκ°„μ„ μ‚¬μš©ν•˜λ„λ‘ μ„€μ •. 이 μ˜΅μ…˜μ„ μ‚¬μš©ν•˜λ©΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλ²„μ™€ λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„ κ°„μ˜ μ‹œκ°„ 차이λ₯Ό κ³ λ €ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
  • withLockId(String lockId): 잠금의 IDλ₯Ό μ„€μ •. λ™μΌν•œ IDλ₯Ό 가진 μž κΈˆμ€ λ™μΌν•œ λ ˆμ½”λ“œλ₯Ό μ‚¬μš©.
  • withColumnNames(ColumnNameProvider columnNames): μ‚¬μš©ν•  ν…Œμ΄λΈ”μ˜ 컬럼 이름을 μ„€μ •. ColumnNameProvider μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ—¬ μ‚¬μš©.
  • usingDbTime(ZoneId zone): λ°μ΄ν„°λ² μ΄μŠ€ μ„œλ²„μ˜ μ‹œκ°„λŒ€λ₯Ό μ„€μ •. 이 μ˜΅μ…˜μ€ usingDbTime()와 같이 μ‚¬μš©λ˜μ§€λ§Œ, νŠΉμ • μ‹œκ°„λŒ€λ₯Ό μ„€μ •ν•  수 μžˆλ‹€.
  • withTimeZone(TimeZone timeZone): λ°μ΄ν„°λ² μ΄μŠ€μ˜ μ‹œκ°„λŒ€λ₯Ό μ„€μ •.
  • withTableName(String tableName): μž κΈˆμ„ μ €μž₯ν•  ν…Œμ΄λΈ” 이름을 μ„€μ •.
  • withIsolationLevel(IsolationLevel isolationLevel): νŠΈλžœμž­μ…˜ 격리 μˆ˜μ€€μ„ μ„€μ •.
  • withTransactionManager(PlatformTransactionManager transactionManager): νŠΈλžœμž­μ…˜ κ΄€λ¦¬μžλ₯Ό μ„€μ •.

@SchedulerLock μ„€μ •

@Slf4j
@Component
public class Scheduling {
    @Scheduled(cron = "*/5 * * * * *")
    @SchedulerLock(name = "scheduled1", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
    public void schedule2() {
        log.info("Scheduled task executed at {}", LocalDateTime.now());
    }
    
    @Scheduled(cron = "*/5 * * * * *")
    @SchedulerLock(name = "scheduled2", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
    public void schedule2() {
        log.info("Scheduled task executed at {}", LocalDateTime.now());
    }
    
    @Scheduled(cron = "*/5 * * * * *")
    @SchedulerLock(name = "scheduled3", lockAtMostFor = "PT2S", lockAtLeastFor = "PT2S")
    public void schedule3() {
        log.info("Scheduled task executed at {}", LocalDateTime.now());
    }
}

μœ„μ˜ μ½”λ“œλŠ” @Scheduled와 @SchedulerLock μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ μŠ€μΌ€μ€„λ§ μž‘μ—…μ„ μ •μ˜ν•œ μ˜ˆμ œμž…λ‹ˆλ‹€. @SchedulerLock μ–΄λ…Έν…Œμ΄μ…˜μ—λŠ” λ‹€μŒκ³Ό 같은 속성듀이 μžˆμŠ΅λ‹ˆλ‹€.

  • name (ν•„μˆ˜): 잠금의 이름을 μ§€μ •ν•©λ‹ˆλ‹€. 같은 이름을 가진 μž κΈˆμ€ λ™μΌν•œ λ ˆμ½”λ“œλ₯Ό μ‚¬μš©ν•˜λ©°, κ³ μœ ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • lockAtMostFor (ν•„μˆ˜): 잠금이 μ΅œλŒ€λ‘œ μœ μ§€λ˜λŠ” μ‹œκ°„μ„ μ§€μ •ν•©λ‹ˆλ‹€. 이 μ‹œκ°„ μ΄ν›„μ—λŠ” 잠금이 μžλ™μœΌλ‘œ ν•΄μ œλ˜λ©°, 이 속성은 'PT30S', '10m', '2h' λ“±μ˜ ISO 8601 ν˜•μ‹μ˜ λ¬Έμžμ—΄μ„ μ‚¬μš©ν•˜μ—¬ μ§€μ •ν•©λ‹ˆλ‹€. 이 값은 λ°˜λ“œμ‹œ μž‘μ—…μ˜ μ˜ˆμƒ μ΅œλŒ€ μ‹€ν–‰ μ‹œκ°„λ³΄λ‹€ 컀야 ν•˜λŠ”λ°, λ§Œμ•½ μž‘μ—…μ΄ 이 μ‹œκ°„μ„ μ΄ˆκ³Όν•˜μ—¬ μ‹€ν–‰λœλ‹€λ©΄, λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œ μž κΈˆμ„ νšλ“ν•˜κ³  λ™μΌν•œ μž‘μ—…μ„ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • lockAtLeastFor(선택): 잠금이 μ΅œμ†Œλ‘œ μœ μ§€λ˜λŠ” μ‹œκ°„μ„ μ§€μ •ν•©λ‹ˆλ‹€. 잠금이 νšλ“λœ ν›„, 이 μ‹œκ°„ μ΄λ‚΄μ—λŠ” 잠금이 ν•΄μ œλ˜μ§€ μ•ŠμœΌλ©°, 이 속성 λ˜ν•œ ISO 8601 ν˜•μ‹μ˜ λ¬Έμžμ—΄μ„ μ‚¬μš©ν•˜μ—¬ 지정할 수 μžˆμŠ΅λ‹ˆλ‹€. 이 값을 μ„€μ •ν•˜λ©΄, μž‘μ—…μ΄ λΉ λ₯΄κ²Œ μˆ˜ν–‰λœ 후에도 일정 μ‹œκ°„ λ™μ•ˆμ€ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ—μ„œ λ™μΌν•œ μž‘μ—…μ΄ μ‹€ν–‰λ˜μ§€ μ•Šλ„λ‘ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • mode(선택): 잠금 λͺ¨λ“œλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. 기본값은 'IMMEDIATE'둜, 이 λͺ¨λ“œμ—μ„œλŠ” μž κΈˆμ„ μ¦‰μ‹œ νšλ“ν•˜λ €κ³  μ‹œλ„ν•˜λŠ”λ°, λ§Œμ•½ μž κΈˆμ„ νšλ“ν•˜μ§€ λͺ»ν•˜λ©΄ μž‘μ—…μ€ μ¦‰μ‹œ μ‹€νŒ¨ν•©λ‹ˆλ‹€. λ‹€λ₯Έ μ˜΅μ…˜μœΌλ‘œλŠ” 'EAGER'κ°€ 있으며, 이 λͺ¨λ“œμ—μ„œλŠ” μž κΈˆμ„ νšλ“ν•˜κΈ° μœ„ν•΄ μ§€μ •λœ μ‹œκ°„ λ™μ•ˆ λŒ€κΈ°ν•©λ‹ˆλ‹€.

ShedLock ν…Œμ΄λΈ” 생성

-- MySQL, MariaDB
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at  TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    locked_by  VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- Postgres
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL,
    lock_until TIMESTAMP    NOT NULL,
    locked_at  TIMESTAMP    NOT NULL,
    locked_by  VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- Oracle
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at  TIMESTAMP(3) NOT NULL,
    locked_by  VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- MS SQL
CREATE TABLE shedlock
(
    name      VARCHAR(64)  NOT NULL,
    lock_until datetime2 NOT NULL,
    locked_at datetime2 NOT NULL,
    locked_by VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

-- DB2
CREATE TABLE shedlock
(
    name       VARCHAR(64)  NOT NULL PRIMARY KEY,
    lock_until TIMESTAMP    NOT NULL,
    locked_at  TIMESTAMP    NOT NULL,
    locked_by  VARCHAR(255) NOT NULL
);

shedlock 데이터 확인

μ‹€μ œ shedlock의 λ°μ΄ν„°λŠ” μœ„μ™€ 같이 μ €μž₯λ˜λŠ”λ°, μ—¬κΈ°μ„œ μ€‘μš”ν•œ 사싀은 ShedLock은 μŠ€μΌ€μ€„λ§ μž‘μ—…μ΄ 싀행될 λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ λ ˆμ½”λ“œλ₯Ό μƒμ„±ν•˜λŠ” 것이 μ•„λ‹Œ, ν•΄λ‹Ή μž‘μ—…μ˜ 잠금 λ ˆμ½”λ“œλ₯Ό μ—…λ°μ΄νŠΈν•œλ‹€λŠ” μ μž…λ‹ˆλ‹€. μ΄λŠ” λ°μ΄ν„°λ² μ΄μŠ€μ˜ λΆ€ν•˜λ₯Ό μ€„μ΄λŠ” 데 도움이 되며, λ™μ‹œμ— μˆ˜ν–‰λ˜λŠ” μž‘μ—…μ„ 효과적으둜 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.(λ”°λΌμ„œ μž‘μ—…μ˜ μ‹€ν–‰ 둜그λ₯Ό κΈ°λ‘ν•˜λ €λ©΄ λ³„λ„λ‘œ λ³„λ„μ˜ λ‘œκΉ… μ‹œμŠ€ν…œμ„ κ΅¬μΆ•ν•˜κ±°λ‚˜, μŠ€μΌ€μ€„λ§ μž‘μ—… λ‚΄μ—μ„œ 둜그λ₯Ό κΈ°λ‘ν•˜λ„λ‘ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€.)

 

 

μ—λŸ¬ ν•΄κ²° κ³Όμ •

μ—¬κΈ°λΆ€ν„°λŠ” μ œκ°€ ShedLock을 μ μš©ν•˜λ©΄μ„œ κ²ͺμ—ˆλ˜ μ—λŸ¬μ— λŒ€ν•΄ μ •λ¦¬ν•©λ‹ˆλ‹€.

1. μ˜μ‘΄μ„± 버전 μ—λŸ¬

버전 μ—λŸ¬

ν”„λ‘œμ νŠΈμ˜ JDK 버전과 ShedLock 라이브러리의 λΉŒλ“œλœ JDK 버전이 μΌμΉ˜ν•˜μ§€ μ•Šμ•„ λ°œμƒν•˜λŠ” μ—λŸ¬μž…λ‹ˆλ‹€. 이 경우, ν”„λ‘œμ νŠΈμ˜ JDK 버전에 λ§žλŠ” ShedLock 라이브러리의 버전을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, JDK 17 버전 이전을 μ‚¬μš©ν•˜λŠ” 경우 ShedLock 라이브러리 v4.44.0을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. 이 μ •λ³΄λŠ” ShedLock의 README의 VERSION λͺ©μ°¨μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

JDK 버전에 λ”°λ₯Έ μ˜μ‘΄μ„± 버전 확인

κΉƒν—ˆλΈŒλ₯Ό 보면 JDK 17 버전 이전은 v4.44.0, μ΄ν›„λŠ” v5.1.0을 써야 ν•œλ‹€κ³  λͺ…μ‹œλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

2. shedlock ν…Œμ΄λΈ” 찾을 수 μ—†μŒ

ERROR 14:15:00 [scheduled-task-1] n.j.s.p.j.JdbcTemplateStorageAccessor, insertRecord at line 73; Unexpected exception
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT IGNORE INTO shedlock(name, lock_until, locked_at, locked_by) VALUES(?, TIMESTAMPADD(MICROSECOND, ?, UTC_TIMESTAMP(3)), UTC_TIMESTAMP(3), ?)]; SQL state [3D000]; error code [1046]; No database selected; nested exception is java.sql.SQLException: No database selected
	at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1542)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667)
	at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960)
	at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:981)
...

λ°μ΄ν„°λ² μ΄μŠ€κ°€ shedlock ν…Œμ΄λΈ”μ„ 찾지 λͺ»ν•΄ λ°œμƒν•˜λŠ” μ—λŸ¬μž…λ‹ˆλ‹€. 이 경우, DataSource μ΄ˆκΈ°ν™” μ‹œμ— μŠ€ν‚€λ§ˆλ₯Ό 지정해 μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, jdbc:mysql://localhost:3306/my_schema와 같이 지정할 수 μžˆμŠ΅λ‹ˆλ‹€. 

ν•˜μ§€λ§Œ, 더 κΉ”λ”ν•œ ν•΄κ²° 방법은 ShedLock μ„€μ •μ—μ„œ withTableNameλ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ μŠ€ν‚€λ§ˆμ™€ ν…Œμ΄λΈ” 이름을 μ§€μ •ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. λ‹€μŒκ³Ό 같이 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.(κ³΅μ‹λ¬Έμ„œμ˜ Configure LockProvider 뢀뢄에 μžμ„Ένžˆ λ‚˜μ™€μžˆμŠ΅λ‹ˆλ‹€.)

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                        .withTableName("schema.shedlock")
                        .withJdbcTemplate(new JdbcTemplate(dataSource))
                        .usingDbTime()
                        .build()
        );
    }

μœ„ μ„€μ •μ—μ„œ "schema.shedlock"은 {μŠ€ν‚€λ§ˆ}.{ν…Œμ΄λΈ”λͺ…}의 ν˜•νƒœλ‘œ μ§€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄, νŠΉμ • μŠ€ν‚€λ§ˆ λ‚΄μ˜ shedlock ν…Œμ΄λΈ”μ„ μ •ν™•νžˆ μ°Ύμ•„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λŒ“κΈ€