공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
이번 시간에는 이용권 일괄 지급을 Tasklet 기반의 Step으로 구현해 본다. 이 기능을 위해 기존 구조에서 bulk_pass 테이블과 user_group_mapping 테이블을 추가하고, 이를 통해 다수의 사용자에게 일괄적으로 이용권을 지급하는 작업을 수행한다.
1. 테이블 생성 및 데이터 삽입
먼저, bulk_pass 테이블과 user_group_mapping 테이블을 추가한다.
CREATE TABLE `bulk_pass` (
`bulk_pass_seq` int NOT NULL AUTO_INCREMENT COMMENT '대량 이용권 순번',
`package_seq` int NOT NULL COMMENT '패키지 순번',
`user_group_id` varchar(20) NOT NULL COMMENT '사용자 그룹 ID',
`status` varchar(10) NOT NULL COMMENT '상태',
`count` int DEFAULT NULL COMMENT '이용권 수, NULL인 경우 무제한',
`started_at` timestamp NOT NULL COMMENT '시작 일시',
`ended_at` timestamp DEFAULT NULL COMMENT '종료 일시, NULL인 경우 무제한',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 일시',
`modified_at` timestamp DEFAULT NULL COMMENT '수정 일시',
PRIMARY KEY (`bulk_pass_seq`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='대량 이용권, 다수의 이용자에게 이용권을 지급하기 위함';
CREATE TABLE `user_group_mapping` (
`user_group_id` varchar(20) NOT NULL COMMENT '사용자 그룹 ID',
`user_id` varchar(20) NOT NULL COMMENT '사용자 ID',
`user_group_name` varchar(50) NOT NULL COMMENT '사용자 그룹 이름',
`description` varchar(50) NOT NULL COMMENT '설명',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '생성 일시',
`modified_at` timestamp DEFAULT NULL COMMENT '수정 일시',
PRIMARY KEY (`user_group_id`, `user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='사용자 그룹 매핑';
-----------------------------------------------------------
INSERT INTO `user_group_mapping` (user_group_id, user_id, user_group_name, description, created_at)
VALUES ('HANBADA', 'A1000000', '한바다', '한바다 임직원 그룹', '2022-08-01 00:00:00'),
('HANBADA', 'A1000001', '한바다', '한바다 임직원 그룹', '2022-08-01 00:00:00'),
('HANBADA', 'A1000002', '한바다', '한바다 임직원 그룹', '2022-08-01 00:00:00'),
('HANBADA', 'B1000010', '한바다', '한바다 임직원 그룹', '2022-08-01 00:00:00'),
('HANBADA', 'B2000000', '한바다', '한바다 임직원 그룹', '2022-08-01 00:00:00'),
('TAESAN', 'B2000001', '태산', '태산 임직원 그룹', '2022-08-01 00:00:00');
- bulk_pass 테이블과 user_group_mapping 테이블을 통해 특정 사용자 그룹에 일괄적으로 이용권을 지급할 수 있다.
- bulk_pass 테이블에서 user_group_id를 가지고 user_group_mapping 테이블을 조회하여 해당 그룹에 속한 사용자들의 user_id를 가져온 후, 이를 기반으로 각 사용자에게 pass를 생성해 줄 수 있다.
2. Tasklet 기반 처리 구현
Tasklet 기반 처리는 Spring Batch에서 반복적으로 실행해야 하는 작업을 정의하는 데 유용하다. 아래 이미지에서 나타난 것처럼 Tasklet은 단일 작업을 정의하기 위해 사용되며, 주로 배치 처리에서 단순한 작업을 수행할 때 적합하다.
Tasklet 인터페이스는 execute 메서드를 제공하며, 이 메서드는 RepeatStatus를 반환한다. RepeatStatus에는 두 가지 값이 있다.
- CONTINUABLE: 작업이 계속될 수 있는 상태를 의미하며, Spring Batch에게 이 Tasklet을 다시 실행하도록 지시한다.
- FINISHED: 작업이 완료되었음을 의미하며, 다음 처리 단계로 넘어가게 된다.
Tasklet의 주요 사용 사례로는 데이터를 초기화하거나 간단한 작업을 수행하는 데 유용하다. 예를 들어, 단순히 로그를 남기거나 특정 조건에 따라 작업을 종료하는 등의 작업을 처리할 수 있다. Tasklet 기반 Step은 단일 작업을 처리하며, 처리의 성공 여부와 관계없이 다음 Step으로 이동하게 된다.
AddPassesJobConfig
AddPassesJobConfig는 Tasklet을 기반으로 Job과 Step을 정의한다.
package com.example.pass.job.pass;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AddPassesJobConfig {
// @EnableBatchProcessing로 인해 Bean으로 제공된 JobBuilderFactory, StepBuilderFactory
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final AddPassesTasklet addPassesTasklet;
public AddPassesJobConfig(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory, AddPassesTasklet addPassesTasklet) {
this.jobBuilderFactory = jobBuilderFactory;
this.stepBuilderFactory = stepBuilderFactory;
this.addPassesTasklet = addPassesTasklet;
}
@Bean
public Job addPassesJob() {
return this.jobBuilderFactory.get("addPassesJob")
.start(addPassesStep())
.build();
}
@Bean
public Step addPassesStep() {
return this.stepBuilderFactory.get("addPassesStep")
.tasklet(addPassesTasklet)
.build();
}
}
- AddPassesJobConfig
- Spring Batch의 Job과 Step을 설정하는 역할을 한다.
- addPassesJob()
- Job을 정의하며, 이 Job은 단일 Step(addPassesStep)으로 구성된다.
- addPassesStep()
- Tasklet을 설정하여, 해당 Tasklet이 반복적인 작업을 처리하도록 한다. 이 때 실제로 실행되는 로직은 AddPassesTasklet에 구현된다.
AddPassesTasklet
AddPassesTasklet은 실제로 이용권을 생성하는 로직을 포함한다.
package com.example.pass.job.pass;
import com.example.pass.repository.pass.*;
import com.example.pass.repository.user.UserGroupMappingEntity;
import com.example.pass.repository.user.UserGroupMappingRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Component
public class AddPassesTasklet implements Tasklet {
private final PassRepository passRepository;
private final BulkPassRepository bulkPassRepository;
private final UserGroupMappingRepository userGroupMappingRepository;
public AddPassesTasklet(PassRepository passRepository, BulkPassRepository bulkPassRepository, UserGroupMappingRepository userGroupMappingRepository) {
this.passRepository = passRepository;
this.bulkPassRepository = bulkPassRepository;
this.userGroupMappingRepository = userGroupMappingRepository;
}
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
// 이용권 시작 일시 1일 전 user group 내 각 사용자에게 이용권을 추가해줍니다.
final LocalDateTime startedAt = LocalDateTime.now().minusDays(1);
final List<BulkPassEntity> bulkPassEntities = bulkPassRepository.findByStatusAndStartedAtGreaterThan(BulkPassStatus.READY, startedAt);
int count = 0;
for (BulkPassEntity bulkPassEntity : bulkPassEntities) {
// user group에 속한 userId들을 조회합니다.
final List<String> userIds = userGroupMappingRepository.findByUserGroupId(bulkPassEntity.getUserGroupId())
.stream().map(UserGroupMappingEntity::getUserId).toList();
// 각 userId로 이용권을 추가합니다.
count += addPasses(bulkPassEntity, userIds);
// pass 추가 이후 상태를 COMPLETED로 업데이트합니다.
bulkPassEntity.setStatus(BulkPassStatus.COMPLETED);
}
log.info("AddPassesTasklet - execute: 이용권 {}건 추가 완료, startedAt={}", count, startedAt);
return RepeatStatus.FINISHED;
}
// bulkPass의 정보로 pass 데이터를 생성합니다.
private int addPasses(BulkPassEntity bulkPassEntity, List<String> userIds) {
List<PassEntity> passEntities = new ArrayList<>();
for (String userId : userIds) {
PassEntity passEntity = PassModelMapper.INSTANCE.toPassEntity(bulkPassEntity, userId);
passEntities.add(passEntity);
}
return passRepository.saveAll(passEntities).size();
}
}
- AddPassesTasklet 클래스는 실제로 이용권을 일괄 지급하는 로직이 구현된 Tasklet이다.
- execute() 메서드에서 이용권 지급 조건에 맞는 BulkPassEntity 목록을 조회한 뒤, 사용자 그룹에 속한 사용자들에게 이용권을 추가하는 작업을 수행한다.
- 각 BulkPassEntity에 대한 이용권이 사용자들에게 지급되면, 해당 상태를 COMPLETED로 변경한다.
- addPasses() 메서드는 사용자의 ID를 기반으로 PassEntity를 생성하고 저장한다.
이 Tasklet은 Spring Batch에서 RepeatStatus.FINISHED를 반환하여 작업이 완료되었음을 알린다.
주요 포인트
- 이 구조에서는 BulkPassEntity와 UserGroupMappingEntity를 기반으로 하여 어떤 사용자에게 어떤 이용권이 지급되어야 하는지를 결정한다.
- PassModelMapper를 사용해 BulkPassEntity에서 PassEntity로 데이터를 매핑하여 변환한다.
- 상태 관리가 중요한 부분이며, 각 작업 후에는 상태를 업데이트하여 중복 처리를 방지한다.
AddPassesTaskletTest
AddPassesTaskletTest는 Spring Batch의 Tasklet을 테스트하는 단위 테스트 코드다. 이 테스트는 Mockito를 활용하여 의존성을 모킹(mocking)하고 AddPassesTasklet의 동작을 검증한다.
package com.example.pass.job.pass;
import com.example.pass.repository.pass.*;
import com.example.pass.repository.user.UserGroupMappingEntity;
import com.example.pass.repository.user.UserGroupMappingRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.repeat.RepeatStatus;
import java.time.LocalDateTime;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@Slf4j
@ExtendWith(MockitoExtension.class) // JUnit5
public class AddPassesTaskletTest {
@Mock
private StepContribution stepContribution;
@Mock
private ChunkContext chunkContext;
@Mock
private PassRepository passRepository;
@Mock
private BulkPassRepository bulkPassRepository;
@Mock
private UserGroupMappingRepository userGroupMappingRepository;
// @InjectMocks 클래스의 인스턴스를 생성하고 @Mock으로 생성된 객체를 주입합니다.
@InjectMocks
private AddPassesTasklet addPassesTasklet;
@Test
public void test_execute() {
// given
final String userGroupId = "GROUP";
final String userId = "A1000000";
final Integer packageSeq = 1;
final Integer count = 10;
final LocalDateTime now = LocalDateTime.now();
final BulkPassEntity bulkPassEntity = new BulkPassEntity();
bulkPassEntity.setPackageSeq(packageSeq);
bulkPassEntity.setUserGroupId(userGroupId);
bulkPassEntity.setStatus(BulkPassStatus.READY);
bulkPassEntity.setCount(count);
bulkPassEntity.setStartedAt(now);
bulkPassEntity.setEndedAt(now.plusDays(60));
final UserGroupMappingEntity userGroupMappingEntity = new UserGroupMappingEntity();
userGroupMappingEntity.setUserGroupId(userGroupId);
userGroupMappingEntity.setUserId(userId);
// when
when(bulkPassRepository.findByStatusAndStartedAtGreaterThan(eq(BulkPassStatus.READY), any())).thenReturn(List.of(bulkPassEntity));
when(userGroupMappingRepository.findByUserGroupId(eq("GROUP"))).thenReturn(List.of(userGroupMappingEntity));
RepeatStatus repeatStatus = addPassesTasklet.execute(stepContribution, chunkContext);
// then
// execute의 return 값인 RepeatStatus 값을 확인합니다.
assertEquals(RepeatStatus.FINISHED, repeatStatus);
// 추가된 PassEntity 값을 확인합니다.
ArgumentCaptor<List> passEntitiesCaptor = ArgumentCaptor.forClass(List.class);
verify(passRepository, times(1)).saveAll(passEntitiesCaptor.capture());
final List<PassEntity> passEntities = passEntitiesCaptor.getValue();
assertEquals(1, passEntities.size());
final PassEntity passEntity = passEntities.get(0);
assertEquals(packageSeq, passEntity.getPackageSeq());
assertEquals(userId, passEntity.getUserId());
assertEquals(PassStatus.READY, passEntity.getStatus());
assertEquals(count, passEntity.getRemainingCount());
}
}
Mock 객체 생성
- @Mock 애너테이션을 사용하여 StepContribution, ChunkContext, PassRepository, BulkPassRepository, UserGroupMappingRepository를 모킹 한다.
- @InjectMocks 애너테이션을 사용하여 AddPassesTasklet 인스턴스를 생성하고, 모킹 된 객체들을 주입한다.
테스트 시나리오
- 주어진 조건(given)으로 bulkPassRepository와 userGroupMappingRepository에서 리턴되는 값을 설정한다. 이를 통해 실제 데이터베이스를 사용하지 않고도 테스트를 수행할 수 있다.
- 테스트 실행(when)에서는 addPassesTasklet.execute(stepContribution, chunkContext)를 호출하여 Tasklet의 핵심 로직을 실행한다.
검증(then)
- 첫 번째로 RepeatStatus.FINISHED가 반환되는지 확인한다. 이는 Tasklet이 정상적으로 실행되어 종료되었음을 의미한다.
- 두 번째로 PassRepository.saveAll() 메서드가 호출되어 PassEntity가 저장되었는지 검증한다. ArgumentCaptor를 사용하여 실제로 저장된 엔티티를 캡처하고, 이를 통해 올바른 데이터가 저장되었는지 확인한다.
중요한 테스트 포인트
- Tasklet의 execute() 메서드가 올바르게 동작하여, bulkPassEntity의 정보를 바탕으로 유저별 PassEntity가 잘 생성되고 저장되는지 검증한다.
- Mockito를 활용하여 외부 의존성을 모킹함으로써, 독립적으로 비즈니스 로직을 검증할 수 있다.
이 테스트를 통해 Tasklet이 여러 의존성을 잘 연동하여 복잡한 로직을 수행하는지, 그리고 올바르게 PassEntity를 생성하는지 확인할 수 있다.
3. BulkPassEntity와 관련된 테이블 및 Repository
이용권 일괄 지급을 처리하기 위해 bulk_pass 테이블과 관련된 Entity 및 Repository를 정의한다.
BulkPassEntity
BulkPassEntity는 대량 이용권 정보를 저장하는 JPA 엔티티 클래스이다. 여러 사용자 그룹에게 동일한 이용권을 일괄 지급하기 위해 사용된다. 이 엔티티는 이용권 패키지, 사용자 그룹 ID, 상태, 이용권 수, 시작 및 종료 일시 등을 저장한다.
package com.example.pass.repository.pass;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
import java.time.LocalDateTime;
@Getter
@Setter
@ToString
@Entity
@Table(name = "bulk_pass")
public class BulkPassEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본 키 생성을 DB에 위임합니다. (AUTO_INCREMENT)
private Integer bulkPassSeq;
private Integer packageSeq;
private String userGroupId;
@Enumerated(EnumType.STRING)
private BulkPassStatus status;
private Integer count;
private LocalDateTime startedAt;
private LocalDateTime endedAt;
}
- bulkPassSeq: 대량 이용권 순번. Primary Key로, 자동 생성된다.
- packageSeq: 패키지 순번.
- userGroupId: 사용자 그룹 ID. 이 ID를 통해 특정 그룹에 소속된 사용자들에게 이용권을 지급한다.
- status: 대량 이용권의 상태. 이 필드는 BulkPassStatus Enum을 사용해 관리된다.
- count: 이용권의 수. null인 경우 무제한.
- startedAt, endedAt: 이용권의 시작 및 종료 일시.
BulkPassRepository
BulkPassRepository는 BulkPassEntity를 관리하는 JPA 리포지토리 인터페이스이다. 주로 데이터베이스에서 대량 이용권 데이터를 조회하고 저장하는 작업을 수행한다.
package com.example.pass.repository.pass;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime;
import java.util.List;
public interface BulkPassRepository extends JpaRepository<BulkPassEntity, Integer> {
// WHERE status = :status AND startedAt > :startedAt
List<BulkPassEntity> findByStatusAndStartedAtGreaterThan(BulkPassStatus status, LocalDateTime startedAt);
}
- findByStatusAndStartedAtGreaterThan
- 특정 상태와 시작 일시를 기준으로 대량 이용권 목록을 조회하는 메서드이다. 이 메서드는 이용권 발급 시점이 다가온 대량 이용권만을 조회해 처리할 수 있도록 한다.
BulkPassStatus Enum
BulkPassStatus는 대량 이용권의 상태를 관리하는 Enum 클래스이다. 주로 이용권의 발급 상태를 나타낸다.
package com.example.pass.repository.pass;
public enum BulkPassStatus {
READY, COMPLETED
}
- READY: 발급 준비 상태.
- COMPLETED: 발급이 완료된 상태.
4. MapStruct를 이용한 데이터 매핑
이 단계에서는 BulkPassEntity를 PassEntity로 변환하기 위해 MapStruct를 사용하여 매핑을 처리한다.
PassModelMapper
PassModelMapper는 MapStruct를 사용하여 BulkPassEntity를 PassEntity로 변환하는 매핑 인터페이스이다. Spring Batch 작업 중 대량 이용권 데이터를 개별 이용권 데이터로 변환할 때 사용된다.
package com.example.pass.repository.pass;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
// ReportingPolicy.IGNORE: 일치하지 않은 필드를 무시합니다.
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PassModelMapper {
PassModelMapper INSTANCE = Mappers.getMapper(PassModelMapper.class);
// 필드명이 같지 않거나 custom하게 매핑해주기 위해서는 @Mapping을 추가해주면 됩니다.
@Mapping(target = "status", qualifiedByName = "defaultStatus")
@Mapping(target = "remainingCount", source = "bulkPassEntity.count") //expression = "java(bulkPassEntity.getCount())"
PassEntity toPassEntity(BulkPassEntity bulkPassEntity, String userId);
// BulkPassStatus와 관계 없이 PassStatus값을 설정합니다.
@Named("defaultStatus")
default PassStatus status(BulkPassStatus status) {
return PassStatus.READY;
}
}
- toPassEntity 메서드는 BulkPassEntity와 사용자 ID(userId)를 받아 PassEntity 객체를 반환한다.
- 상태 필드(status)는 항상 READY 상태로 설정된다.
- remainingCount 필드는 bulkPassEntity.count 값으로 매핑된다.
매핑 과정
- @Mapper: MapStruct 인터페이스로 선언하며, 매핑 규칙을 정의한다.
- @Mapping: 특정 필드에 대해 매핑 규칙을 정의한다. 예를 들어, remainingCount는 bulkPassEntity.count에서 값을 가져온다.
- @Named: 특정 필드에 대해 기본값을 설정하는 메서드를 정의한다. status 필드는 항상 READY로 설정된다.
PassModelMapperTest
PassModelMapperTest는 PassModelMapper의 매핑 로직이 올바르게 동작하는지 검증하기 위한 JUnit 테스트 클래스이다.
package com.example.pass.repository.pass;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PassModelMapperTest {
@Test
public void test_toPassEntity() {
// given
final LocalDateTime now = LocalDateTime.now();
final String userId = "A1000000";
BulkPassEntity bulkPassEntity = new BulkPassEntity();
bulkPassEntity.setPackageSeq(1);
bulkPassEntity.setUserGroupId("GROUP");
bulkPassEntity.setStatus(BulkPassStatus.COMPLETED);
bulkPassEntity.setCount(10);
bulkPassEntity.setStartedAt(now.minusDays(60));
bulkPassEntity.setEndedAt(now);
// when
final PassEntity passEntity = PassModelMapper.INSTANCE.toPassEntity(bulkPassEntity, userId);
// then
assertEquals(1, passEntity.getPackageSeq());
assertEquals(PassStatus.READY, passEntity.getStatus());
assertEquals(10, passEntity.getRemainingCount());
assertEquals(now.minusDays(60), passEntity.getStartedAt());
assertEquals(now, passEntity.getEndedAt());
assertEquals(userId, passEntity.getUserId());
}
}
주요 테스트 목적
- BulkPassEntity의 필드 값이 정확하게 PassEntity로 매핑되는지 확인한다.
- 각 필드가 예상한 대로 매핑되었는지 검증한다.
테스트 과정
- BulkPassEntity를 생성하고, 다양한 필드 값을 설정한 후, PassModelMapper.INSTANCE.toPassEntity 메서드를 호출하여 PassEntity 객체를 생성한다.
- 생성된 PassEntity의 필드 값이 기대한 값과 일치하는지 검증한다.
테스트 검증
- packageSeq, status, remainingCount, startedAt, endedAt, userId 등의 필드가 BulkPassEntity에서 기대한 값으로 매핑되었는지 확인한다.
- status 필드는 항상 READY로 설정되는지 검증한다.
5. 사용자 그룹 매핑 테이블 및 리포지토리 정의
이 단계에서는 UserGroupMappingEntity와 이를 관리할 UserGroupMappingRepository를 정의하여, 사용자 그룹과 사용자 간의 매핑을 관리한다.
UserGroupMappingEntity
UserGroupMappingEntity는 사용자 그룹과 사용자 간의 매핑 정보를 저장하는 JPA 엔티티 클래스이다. 이 엔티티는 특정 그룹에 속한 사용자들을 관리하기 위해 사용된다.
package com.example.pass.repository.user;
import com.example.pass.repository.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Getter
@Setter
@ToString
@Entity
@Table(name = "user_group_mapping")
@IdClass(UserGroupMappingId.class)
public class UserGroupMappingEntity extends BaseEntity {
@Id
private String userGroupId;
@Id
private String userId;
private String userGroupName;
private String description;
}
- 이 엔티티는 userGroupId와 userId를 복합 키로 사용한다. 이는 한 그룹에 여러 사용자가 속할 수 있도록 하며, 각각의 사용자 그룹에 대한 정보를 관리할 수 있게 한다.
- @IdClass를 사용해 복합 키를 정의하고 있으며, 이를 통해 두 필드를 기본 키로 설정한다.
UserGroupMappingId
UserGroupMappingId는 UserGroupMappingEntity의 복합 키를 정의하기 위한 클래스이다. 이 클래스는 Serializable을 구현해야 한다.
package com.example.pass.repository.user;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
@Getter
@Setter
@ToString
public class UserGroupMappingId implements Serializable {
private String userGroupId;
private String userId;
}
- 이 클래스는 userGroupId와 userId를 복합 키로 사용하며, 이를 통해 UserGroupMappingEntity가 올바르게 동작하도록 한다.
- Serializable을 구현하여 JPA에서 복합 키를 처리할 수 있게 한다.
UserGroupMappingRepository
UserGroupMappingRepository는 UserGroupMappingEntity를 관리하기 위한 JPA 리포지토리 인터페이스이다.
package com.example.pass.repository.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserGroupMappingRepository extends JpaRepository<UserGroupMappingEntity, Integer> {
List<UserGroupMappingEntity> findByUserGroupId(String userGroupId);
}
- 이 리포지토리는 JPA를 사용해 UserGroupMappingEntity에 대한 기본적인 CRUD 작업을 처리한다.
- findByUserGroupId 메서드를 통해 특정 그룹에 속한 사용자들을 쉽게 조회할 수 있다. 이 메서드는 대량 이용권 발급 시 해당 그룹에 속한 사용자들을 조회할 때 사용된다.
이번 시간에는 Tasklet 기반의 Batch Job을 통해 대량 이용권을 사용자 그룹에 일괄 지급하는 과정을 구현해 보았다. 이를 위해 BulkPassEntity와 UserGroupMappingEntity를 설계하고, MapStruct를 활용한 엔티티 간 매핑을 통해 효율적으로 데이터를 처리하는 방법을 알아보았다. 마지막으로, 각 Tasklet과 관련된 테스트 코드를 작성하여 정확한 동작을 검증하였다.
'BackEnd > Project' 카테고리의 다른 글
[PT Manager] Ch03. Batch 수업 종료 후 이용권 차감 (0) | 2024.08.28 |
---|---|
[PT Manager] Ch03. Batch 예약된 수업 전 알람(외부 채널 알람 연동) (0) | 2024.08.28 |
[PT Manager] Ch03. Batch 이용 기간에 따른 만료 (0) | 2024.08.27 |
[PT Manager] Ch03. JPA를 사용한 MySQL 연동 (0) | 2024.08.27 |
[PT Manager] Ch03. Docker MySQL 설치 및 테이블 생성 (4) | 2024.08.27 |