Skip to content

스프링 배치 5 적용 #100

@CHISANW

Description

@CHISANW

개요

-회원 탈퇴 시, 탈퇴한 회원의 데이터를 3개월 동안 보관한 후 일괄 삭제하는 방식으로 설계되었습니다. 대량의 데이터를 효율적으로 삭제하기 위해 매일 00:05에 스프링 배치를 활용하여 3개월이 지난 회원의 데이터를 삭제합니다.

구성

  • 다중 DB 구성: metaDB와 dataDB로 나누어 설계.
  • JPA 기반 배치: 프로젝트는 JPA를 기본으로 사용하므로, JPA를 활용한 배치 설정을 적용.
  • JDBC 기반 페이징 처리: 성능 이슈를 최소화하기 위해 배치 실행 시 JDBC 기반의 페이징 방식을 적용.
  • MySQL 프로시저 활용: 단일 잡에서 여러 쿼리를 적용하기 어려운 문제를 해결하기 위해, MySQL 프로시저를 사용하여 대량 데이터 삭제를 처리.
  • 크론 스케줄링: 매일 00:05에 배치가 자동 실행되도록 크론 스케줄링 설정.

설정

MetaDBConfig

@Configuration
public class MetaDBConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource-meta")
    public DataSource metaDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean
    public PlatformTransactionManager metaTransactionManager() {
        return new DataSourceTransactionManager(metaDataSource());
    }
}

DataDBConfig

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {"team9499.commitbody"},
        entityManagerFactoryRef = "dataEntityManager",
        transactionManagerRef = "dataTransactionManager"
)
public class DataDBConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource-data")
    public DataSource dataDBSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean dataEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataDBSource());
        em.setPackagesToScan("team9499.commitbody");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.show_sql", "true");
        em.setJpaPropertyMap(properties);
        return em;
    }

    @Bean
    public PlatformTransactionManager dataTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(dataEntityManager().getObject());
        return transactionManager;
    }
}

사용자 탈퇴 배치 전체 코드

@Slf4j
@Configuration
public class MemberDeleteBatch {

    private final PlatformTransactionManager dataTransactionManager;
    private final JobRepository jobRepository;
    private final DataSource dataDBSource;

    public MemberDeleteBatch(@Qualifier("dataTransactionManager") PlatformTransactionManager dataTransactionManager,
                             JobRepository jobRepository,
                             @Qualifier("dataDBSource") DataSource dataDBSource) {
        this.dataTransactionManager = dataTransactionManager;
        this.jobRepository = jobRepository;
        this.dataDBSource = dataDBSource;
    }

    @Bean
    public Job deleteMemberJob() {
        log.info("탈퇴한 사용자 데이터 삭제 배치");
        return new JobBuilder("deleteMemberJob", jobRepository)
                .start(firstStep())
                .build();
    }

    @Bean
    public Step firstStep() {
        return new StepBuilder("firstStep", jobRepository)
                .<Member, Member> chunk(10, dataTransactionManager)
                .reader(beforeReader())
                .processor(itemProcessor())
                .writer(deleteWriters())
                .build();
    }

    @Bean
    public JdbcPagingItemReader<Member> beforeReader(){
        return new JdbcPagingItemReaderBuilder<Member>()
                .name("beforeReader")
                .dataSource(dataDBSource)
                .selectClause("select member_id")
                .fromClause("from member")
                .whereClause("where is_withdrawn = true and withdrawn_at <= date(now()) and withdrawn_at is not null")
                .sortKeys(Map.of("member_id", Order.ASCENDING))
                .rowMapper(new CustomMemberRowMapper())
                .pageSize(10)
                .build();

    }

    @Bean
    public ItemProcessor<Member,Member> itemProcessor (){
        return member -> {
            log.info("탈퇴 사용자 Id ={}",member.getId());
            return member;
        };
    }
    @Bean
    public JdbcBatchItemWriter<Member> deleteWriters() {
        String sql = "CALL delete_member_data(:id)";

        return new JdbcBatchItemWriterBuilder<Member>()
                .dataSource(dataDBSource)
                .sql(sql)
                .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
                .assertUpdates(false)
                .build();
    }
}

문제점

  • 단일 DB 연결 시 .yml 파일에서 .url 설정을 사용했지만, 다중 DB 구성에서는 jdbc-url로 설정을 변경해야 함.
  • 테이블 및 컬럼 생성 시 카멜케이스가 적용되지 않아, @column(name = "")을 통해 정확한 컬럼명을 명시해야 함.
  • 두 개의 트랜잭션을 사용하는 상황에서, metaDB에 적용된 트랜잭션을 명시하기 위해 @transactional(transactionManager = "트랜잭션명")을 사용해야 함

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation기능 구현기능 구현 입니다.

Type

No type

Projects

Status

진행중

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions