[ShedLock 추가 및 멤버 더미데이터 생성 로직 구현현] 분산 스케줄링을 위한 ShedLock 라이브러리를 추가하고, 멤버 추가(더미데이터) 스케줄러를 구현
This commit is contained in:
		
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -355,14 +355,20 @@ public class Member extends BaseEntity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 11. 데이터베이스 스키마
 | 
					### 11. 데이터베이스 스키마
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**데이터베이스 테이블 구조는 `ddl/schema.sql`에 정의되어 있습니다.**
 | 
					**데이터베이스 테이블 구조는 `ddl/schema_entity.sql`에 정의되어 있습니다.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### 스키마 파일
 | 
					#### 스키마 파일
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **위치**: `ddl/schema.sql`
 | 
					- **위치**: `ddl/schema_entity.sql`
 | 
				
			||||||
- **내용**: 모든 테이블의 CREATE TABLE DDL 스크립트
 | 
					- **내용**: 모든 엔티티 테이블의 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 자동 스키마 생성 비활성화
 | 
					- **설정**: `spring.jpa.hibernate.ddl-auto=none`으로 Hibernate 자동 스키마 생성 비활성화
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,6 +73,10 @@ dependencies {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	// SpringDoc OpenAPI (Swagger)
 | 
						// SpringDoc OpenAPI (Swagger)
 | 
				
			||||||
	implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
 | 
						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') {
 | 
					tasks.named('test') {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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.username=imas_user
 | 
				
			||||||
spring.datasource.password=stam1201
 | 
					spring.datasource.password=stam1201
 | 
				
			||||||
spring.datasource.driver-class-name=org.postgresql.Driver
 | 
					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.username=${DB_USERNAME:}
 | 
				
			||||||
# spring.datasource.password=${DB_PASSWORD:}
 | 
					# 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 설정
 | 
					# 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.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
 | 
					spring.jpa.properties.hibernate.hbm2ddl.schema-generation.script.append=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# ========================================
 | 
					# ========================================
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src/main/resources/schema_initial.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/main/resources/schema_initial.sql
									
									
									
									
									
										Normal 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)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
		Reference in New Issue
	
	Block a user