[ShedLock 추가 및 멤버 더미데이터 생성 로직 구현현] 분산 스케줄링을 위한 ShedLock 라이브러리를 추가하고, 멤버 추가(더미데이터) 스케줄러를 구현

This commit is contained in:
2025-09-02 14:37:21 +09:00
parent 3972a77c85
commit 470a5c8add
7 changed files with 170 additions and 6 deletions

View File

@@ -355,14 +355,20 @@ public class Member extends BaseEntity {
### 11. 데이터베이스 스키마
**데이터베이스 테이블 구조는 `ddl/schema.sql`에 정의되어 있습니다.**
**데이터베이스 테이블 구조는 `ddl/schema_entity.sql`에 정의되어 있습니다.**
#### 스키마 파일
- **위치**: `ddl/schema.sql`
- **내용**: 모든 테이블의 CREATE TABLE DDL 스크립트
- **위치**: `ddl/schema_entity.sql`
- **내용**: 모든 엔티티 테이블의 CREATE TABLE DDL 스크립트
#### 초기화 스크립트
- **위치**: `src/main/resources/schema_initial.sql`
- **내용**: 서버 부팅 시 자동 실행되는 초기화 스크립트 (예: shedlock 테이블 등)
#### 사용 방법
- **자동 생성**: 애플리케이션 시작 시 `schema.sql`로 테이블 자동 생성
- **자동 생성**: 애플리케이션 시작 시 `schema_entity.sql` 엔티티 테이블 자동 생성
- **초기화**: 서버 부팅 시 `schema_initial.sql`로 시스템 테이블 자동 생성
- **설정**: `spring.jpa.hibernate.ddl-auto=none`으로 Hibernate 자동 스키마 생성 비활성화

View File

@@ -73,6 +73,10 @@ dependencies {
// SpringDoc OpenAPI (Swagger)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
// ShedLock for distributed scheduling
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.10.2'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.10.2'
}
tasks.named('test') {

View File

@@ -0,0 +1,105 @@
package com.bio.bio_backend.domain.base.member.scheduler;
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.concurrent.ThreadLocalRandom;
/**
* 멤버 동기화 스케줄러
* ShedLock을 사용하여 분산 환경에서 중복 실행을 방지합니다.
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class MemberSyncScheduler {
private final MemberService memberService;
/**
* 1시간마다 멤버 동기화를 실행합니다.
* 현재는 더미 데이터를 생성하지만, 추후 실제 조직도 연동으로 변경 예정입니다.
*/
@Scheduled(cron = "0 0 * * * *") // 1시간마다
@SchedulerLock(name = "memberSync", lockAtMostFor = "50m", lockAtLeastFor = "5m")
public void syncMembersAtTopOfHour() {
log.info("1시간마다 멤버 동기화 시작");
try {
int createdCount = createDummyMembers(5); // 1시간마다 5명씩 생성
log.info("1시간마다 멤버 동기화 완료: {}명 생성", createdCount);
} catch (Exception e) {
log.error("1시간마다 멤버 동기화 실패: {}", e.getMessage(), e);
}
}
/**
* 매일 새벽 2시에 대량 동기화를 실행합니다.
*/
@Scheduled(cron = "0 0 2 * * *") // 매일 새벽 2시
@SchedulerLock(name = "memberBulkSync", lockAtMostFor = "30m", lockAtLeastFor = "5m")
public void bulkSyncMembers() {
log.info("일일 대량 멤버 동기화 시작");
try {
int createdCount = createDummyMembers(20); // 새벽에 20명씩 생성
log.info("일일 대량 멤버 동기화 완료: {}명 생성", createdCount);
} catch (Exception e) {
log.error("일일 대량 멤버 동기화 실패: {}", e.getMessage(), e);
}
}
/**
* 더미 멤버를 생성합니다.
* 추후 실제 조직도 API 연동으로 변경될 예정입니다.
*
* @param count 생성할 멤버 수
* @return 실제 생성된 멤버 수
*/
private int createDummyMembers(int count) {
int createdCount = 0;
for (int i = 0; i < count; i++) {
try {
String randomSuffix = String.valueOf(System.currentTimeMillis() + i);
MemberDto memberDto = MemberDto.builder()
.userId("user_" + randomSuffix)
.password("password123") // 실제로는 더 복잡한 패스워드 생성
.name(generateRandomName())
.email("user_" + randomSuffix + "@company.com")
.build();
memberService.createMember(memberDto);
createdCount++;
log.debug("더미 멤버 생성 완료: {}", memberDto.getUserId());
} catch (Exception e) {
log.warn("더미 멤버 생성 실패 ({}번째): {}", i + 1, e.getMessage());
// 개별 멤버 생성 실패는 전체 프로세스를 중단하지 않음
}
}
return createdCount;
}
/**
* 랜덤한 이름을 생성합니다.
* 실제 환경에서는 조직도에서 가져온 실제 이름을 사용합니다.
*
* @return 생성된 랜덤 이름
*/
private String generateRandomName() {
String[] surnames = {"", "", "", "", "", "", "", "", "", ""};
String[] givenNames = {"민수", "지영", "현우", "수진", "태현", "은지", "동훈", "예린", "준호", "서연"};
String surname = surnames[ThreadLocalRandom.current().nextInt(surnames.length)];
String givenName = givenNames[ThreadLocalRandom.current().nextInt(givenNames.length)];
return surname + givenName;
}
}

View File

@@ -0,0 +1,38 @@
package com.bio.bio_backend.global.config;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import javax.sql.DataSource;
/**
* 스케줄러 및 분산 락 설정을 위한 Configuration 클래스
* ShedLock을 사용하여 분산 환경에서 스케줄러 중복 실행을 방지합니다.
*/
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
public class SchedulerConfig {
/**
* ShedLock용 LockProvider Bean을 생성합니다.
* JDBC 기반으로 분산 락을 관리합니다.
*
* @param dataSource 데이터소스
* @return JdbcTemplateLockProvider 인스턴스
*/
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime() // DB 시간 사용으로 서버 간 시간차 문제 해결
.build()
);
}
}

View File

@@ -15,7 +15,7 @@ spring.devtools.restart.additional-paths=src/main/java
# ========================================
# 데이터베이스 설정
# ========================================
spring.datasource.url=jdbc:postgresql://stam.kr:15432/imas
spring.datasource.url=jdbc:postgresql://stam.kr:15432/imas?options=-c%20TimeZone=Asia/Seoul
spring.datasource.username=imas_user
spring.datasource.password=stam1201
spring.datasource.driver-class-name=org.postgresql.Driver
@@ -24,6 +24,10 @@ spring.datasource.driver-class-name=org.postgresql.Driver
# spring.datasource.username=${DB_USERNAME:}
# spring.datasource.password=${DB_PASSWORD:}
# 항상 schema_initial.sql, data.sql 실행
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema_initial.sql
# ========================================
# JPA/Hibernate 설정
# ========================================
@@ -43,7 +47,7 @@ spring.jpa.properties.hibernate.order_updates=true
# 스키마 생성 설정
spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=ddl/schema.sql
spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=ddl/schema_entity.sql
spring.jpa.properties.hibernate.hbm2ddl.schema-generation.script.append=false
# ========================================

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS shedlock (
name varchar(64) NOT NULL,
lock_until timestamptz(3) NOT NULL,
locked_at timestamptz(3) NOT NULL,
locked_by varchar(255) NOT NULL,
CONSTRAINT shedlock_pkey PRIMARY KEY (name)
);