팀에서 처음으로 Spring Batch를 사용하다 보니 문제 상황이 생기면 물어볼수도 없어서 혼자 끙끙대는 중인데 가끔 스스로 에게 하는 질문이나 의문 자체가 잘못된 느낌도 들고, Spring Batch에 대해 다시 처음부터 공부하고 시작해야 하는게 아닌가 하는 불안감도 들고 개발 하는 내내 싱숭생숭하다; 개발하면서 또 이런 적은 처음이라 당황스럽다-_-; 일단 개발은 시작했는데 내일은 다시 좀 기본 컨셉, 개념들을 살펴보고 코드들을 전체적으로 훑어봐야겠다..

 

1) 복잡한 Query를 질의해야 한다.

 

단순 조인으로만 이루어진 쿼리가 아니라 union all이나 여러 개의 내부 select절이 포함된 복잡한 쿼리를 Reader에서 던져야 했다. (JPA를 사용하는 스프링 부트 프로젝트에선 QueryDSL로도 좀 만들기 까다로운 경우에는 Native Query를 사용해서 질의를 해왔다.) 후보에는 JpaPagingItemReader, JdbcPagingItemReader, JdbcCursorItemReader이 올랐고, Cursor의 경우는 Socket Time Out의 문제로 Connection 이 끊어지지 않게끔 설정을 해줘야 되는 번거로움이 존재했다. 다른 블로그에서도 Paging을 추천하기도 했고, Jpa가 익숙해서 JpaPagingItemReader를 사용하고 있었는데 JpaPagingItemReader로는 복잡한 쿼리를 질의하는 케이스를 아무리 찾아봐도 보이지가 않는다(그때는..) 여차 저차 비슷한 걸 찾아서 JpaNativeQueryProvider를 이용해보았는데 실패; (메인 쿼리 전에 Jpa 에서 사전? 쿼리를 날리는데 이게 문제가 된다) 그래서 JdbcPagingItemReader를 사용해보려고 했는데 setSelectClase()같이 쿼리를 구분해서 넣어주기엔 쿼리가 너무 복잡해서 어쩔 수 없이 JdbcCursorItemReader를 사용하게 됐다. 

 

    @Bean
    public JdbcCursorItemReader<TestEntity> jdbcCursorItemReader() {
        return new JdbcCursorItemReaderBuilder<TestEntity>()
                            .fetchSize(chunkSize)
                            .dataSource(dataSource)
                            .rowMapper(new TestEntityMapper()) // 원래는 이 부분에서 BeanPropertyRowMapper를 사용했었음
                            .sql(getQuery())
                            .name("jdbcCursorItemReader")
                            .build();
    }

간단하게 바꿔서 가져와 봤다. 여기서 또 다른 난관을 맞닥뜨리게 되는데; rowMapper 부분이다;

현재 DB는 PostgreSQL을 쓰고 있고, 컬럼 타입이 Array인 경우가 많다. 이런 경우에는 아래와 같이 타입을 선언해주고 CRUD를 처리했는데 JdbcCursorItemReader의 rowMapper로 전달한 BeanPropertyRowMapper가 해당 타입을 변환해주지 못하고 에러를 뱉는다. 

@Column(name = "emails", columnDefinition = "varchar[]")
@Type(type = "~~~.CustomStringArrayType")
private String[] emails;

그래서 RowMapper를 Implements한 Custom Mapper를 생성해주고 아래와 같이 Array.타입을 변환해 mapping해주게 하였다.

public class TestEntityMapper implements RowMapper<TestEntity> {
    @Override
    public RiExpirationTarget mapRow(ResultSet rs, int rowNum) throws SQLException {
        TestEntity testEntity = new TestEntity();
        testEntity.setEmails((String[]) rs.getArray("emails").getArray());
     	~~~~
        return testEntity;
    }
}

여기까지 왔는데 아래 포스팅을 발견...  갑자기 이런 의문이 든다;

아무리 복잡한 쿼리라고 해도 우아한 형제들에서 쓰는 쿼리보다 복잡하려나? 내가 혹시 이상한 길로 간게 아닐까 하는 의구심이 들며... 우선은 이번 프로젝트를 마치고 바로 해당 부분을 리팩토링 하는 것을 목표로 삼겠다.. 

https://techblog.woowahan.com/2662/

 

2) Processor에서 DB에 접근해 데이터를 가져오는 것이 찝찝한 느낌.. 그래도 될까? (Reader에서 처리되어야 할 것 같은 너낌 적인 너낌)

 

Spring Batch의 Processor 단계에서 DB에 접속해서 데이터를 가져오는 로직이 존재해도 된다고 한다.

Processor는 일반적으로 데이터의 변환 및 가공을 수행하는 단계로 데이터 소스에서 필요한 데이터를 가져와 가공하는 것이 일반적이라고 한다.

 

그러나 다음과 같은 사항에 유의해야 한다고 한다.

  1. 성능 문제: Processor가 데이터 소스에 매번 접속하고 데이터를 가져오는 것은 성능상 좋지 않을 수 있습니다. 따라서 대량의 데이터를 처리할 때는 Chunk 단위로 처리하거나, 데이터베이스 쿼리 최적화를 고려해야 합니다.
  2. 트랜잭션 처리: Processor에서 데이터 소스에 접속하고 데이터를 가져올 때, 트랜잭션 처리에 대한 고민이 필요합니다. 

따라서 Processor에서 DB에 접속해서 데이터를 가져오는 로직이 존재해도 되지만, 성능과 트랜잭션 처리에 대한 고민이 필요한데 Spring Batch에서는 일반적으로 Chunk 단위로 트랜잭션 처리를 하므로, Processor에서도 Chunk 단위로 트랜잭션 처리를 해야한다. 

Processor에서 사용하는 메소드에 @Transactional을 선언할 수 있지만, 이 경우 Chunk 단위로 트랜잭션이 처리되지 않을 수 있다. 따라서 @Transactional을 사용할 경우, Processor가 Chunk 단위로 실행되도록 설정해야 한다.

 

Chunk 단위로 트랜잭션 처리를 하기 위해서는 ItemReader에서 데이터를 읽어올 때, 트랜잭션을 시작하고, ItemWriter에서 데이터를 처리한 후에 트랜잭션을 커밋하면 된다. 이렇게 Chunk 단위로 트랜잭션 처리를 하면, Processor에서 DB에 접속해서 데이터를 가져오는 로직을 작성해도 안전하게 사용할 수 있게 된다.

+ Recent posts