ItemReader – Spring Batch Chapter 3

DB에서는 단일 레코드, 애플리케이션에서는 단일 도메인 객체, 배치 작업에서는 Item. 이 녀석을 읽어들일 때 사용하는 것이 바로 ItemReader.

public interface ItemReader {

  Object read() throws Exception;

  void mark() throws MarkFailedException;

  void reset() throws ResetFailedException;
}

아주 깔끔한 인터페이스 read()는 읽고, mark()는 표시해두고, reset()은 최근에 mark() 된 곳으로 이동. 마치 책갈피가 있는 Iterator 느낌이 납니다.

그리고 이 인터페이스를 구현한 녀석이 DB나 파일등에 연결이 필요한 경우 반드시 같이 구현하는 인터페이스가 있는데 그건 바로 ItemStream.

public interface ItemStream {

  void open(ExecutionContext executionContext) throws StreamException;

  void update(ExecutionContext executionContext);
 
  void close(ExecutionContext executionContext) throws StreamException;
}

ExecutionContext를 사용해서 처음 위치부터 시작하지 않고 이전에 멈췄던 곳에서 다시 시작 할 수 있는 방법을 제공합니다. open() 해서 연결하고, update()는 ExecutionContext에서 현재 상태 읽어옵니다. 따라서 커밋하기 전에 update()를 호출해서 현재상태가 DB에 보존되고 있는지 확인할 때 사용하고, close()는 open으로 읽어온 자원들 반납합니다. 작업 마친 담에 꼭 호출해주는게 좋겠네요.

DB에 있는 Item을 읽어올 때, 스프링의 JdbcTemplate에서는 RowMapper를 사용해서 반환된 ResultSet에 있는 데이터들을 모두. 왕창. 한방에. 객체로 맵핑해서 반환해줬습니다. 이게 이슈가 되기도 합니다. 왜냐면, 데이터가 너무 많으면 메모리가 모자라서 죽게 될테니까요. 그런데 배치에서도 역시 RowMapper를 사용하는데, 그런 일이 벌어지지 않게 하기 위해서 두 가지 방법을 마련했습니다. 하나는 커서Cursor, 하나는 드라이빙쿼리DrivingQuery.

Cursor 스타일이란?
맵핑을 한 번에 전부 하는게 아니라, 한 번에 한 줄씩 합니다. “스트리밍” 방식이라고 할 수 있습니다. 위에서 살펴본 ItemReader의 next()를 호출 할 때마다 한 줄(레코드)씩 이동합니다.

  HibernateCursorItemReader itemReader = new HibernateCursorItemReader();
  itemReader.setQueryString(“from CustomerCredit”);
  //For simplicity sake, assume sessionFactory already obtained.
  itemReader.setSessionFactory(sessionFactory);
  itemReader.setUseStatelessSession(true);
  int counter = 0;
  ExecutionContext executionContext = new ExecutionContext();
  itemReader.open(executionContext);
  Object customerCredit = new Object();
  while(customerCredit != null){
    customerCredit = itemReader.read();
    counter++;
  }
  itemReader.close(executionContext);

itemReader.read()를 할 때마다 RowMapper를 적용해서 객체를 던져주고, 다음 레코드로 넘어갑니다.

Driving Query 스타일이란?
레코드 전체를 가져오는게 아니라, 주키만 가져온 다음에 필요시에 애플리케이션의 DAO를 사용해서 객체를 DB에서 로딩하는 방법. 이 방법을 쓰는 이유는 DB2 처럼 Pessimistic Locking(읽는 롹킹)하는 경우에 Cursor 방법이 비효율적일 수 있기 때문입니다.

이 녀석의 구현체인 DrivingQueryItemReader가 의존하고 있는 인터페이스 KeyCollector.

 public interface KeyCollector {

    List retrieveKeys(ExecutionContext executionContext);

    void updateContext(Object key, ExecutionContext executionContext);
  }

첫번째 메소드야 뻔하고, updateContext는 현재 어디까지 읽었나, 책갈피를 끼워두는 겁니다.

KeyCollector 구현체
– SingleColumnJdbcKeyCollector : 주키가 컬럼 하나 일때 사용.

  SingleColumnJdbcKeyCollector keyCollector = new SingleColumnJdbcKeyCollector(getJdbcTemplate(),
  “SELECT ID from T_FOOS order by ID”);

  keyCollector.setRestartSql(“SELECT ID from T_FOOS where ID > ? order by ID”);

  ExecutionContext executionContext = new ExecutionContext();

  List keys = keyStrategy.retrieveKeys(new ExecutionContext());

  for (int i = 0; i < keys.size(); i++) {
    System.out.println(keys.get(i));
  }

흠.. 이 예제 코드는 잘 이해가 안 가네요. ItemReader 인터페이스는 안 사용하나;;

복합키 일땐?? ExecutionContextRowMapper

The Domain Language of Batch – Spring Batch Chapter 2

배치 개념을 익히기 정말 좋은 챕터가 아닌가 생각됩니다. 스프링 배치의 도메인 언어인지, 일반적인 배치의 도메인 언어인지는 구분하기 힘들지만, 상당히 많이 정제되어 있다는 느낌을 받을 수 있었습니다. 분명, 수 많은 프로젝트의 배치 작업을 하면서 도출해낸 도메인 언어들이 아닐까 생각됩니다.

요즘은 귀찮아서 그림을 안 그렸었는데, 오랜만에 그려봐야겠습니다. 레퍼런스에 나와있는 그림에 표현하지 않은 도메인(JobParameters, ExecutionContext, Persistence 여부)도 있어서요.

사용자 삽입 이미지
후광이 있는 녀석들은 Persistent Domain입니다. 즉 (DB를 사용한다는 가정하에) Step이라는 테이블이 없다고 (즉, Step 정보를 유지하지 않는다고) 봐도 됩니다. 그래서 StepExecution 테이블에는 JobExecution의 주키를 참조하는 외례키 컬럼만 있고 Step_ID와 같은 컬럼은 없습니다.

연한색 박스는 인터페이스, 진한 색은 클래스입니다.

ExcutionContext는 StepExcuion 당 하나씩 생성됩니다.

재미있는 건 저 도메인들을 저장하는 책임을 지닌 JobRepository라는 인터페이스인데, 이 녀석의 구현이 어떻게 되어 있을까 궁금했는데, 구현체는 없었습니다. 어떻게 구현해야 할런지… JDBC 말고 애노테이션 기반 하이버네이트를 써서 구현하는 방법이 궁금해집니다. JobRepositoryHibernate 야.. 뭐 SessionFactory만 있으면 알아서 넣어줄테니 걱정되지 않는데, 저들 도메인의 맵핑 정보를 넘겨줘야 하는데 말이죠. 그걸 어떻게 애노테이션으로 할 방법은 없을까요. 흠… XML로만 해야 할까요. XML 로 해야 한다면, 맵핑 정보는 어떻게 만들면 될까요? 어느정도 고정적인 도메인이니까, 하나 만들어 두는게 좋을 것 같습니다.

흠.. Spring Batch + 하이버네이트를 기반으로 한 어떤 프레임워크가 되겠군요.

자세한 설명은 http://static.springframework.org/spring-batch/spring-batch-docs/reference/html/core.html 를 참조하세요.