티스토리 툴바


개발한 Batch를 Unit Test하는데, FlatFileItemWriter로 생성한 파일에 "데이터가 기록되지 않는 현상"이 발생했다.
무엇이 잘못됐는지 한참을 해맸는데.
조사해보니, Unit Test에 @Transaction 어노테이션을 붙일 경우 테스트 종료와 함께 롤백이 되는데,
FlatFileItemWriter는 Transaction이 롤백될 때 같이 롤백되도록 구현되어있었다.

Spring Batch 2.1.0.M3 부터 선택적으로 Transaction을 태울지 여부를 결정할 수 있게됐다.
  - https://jira.springsource.org/browse/BATCH-1449

flatFileItemWriter의 transactional 속성은 true가 default값인데, false로 변경해주면 롤백되지 않는다.

flatFileItemWriter.setTransactional(false);



FlatFileItemWriter를 좀 더 알아보자.

아래와 같이 기록할 file명을 넘겨주고 open을 하면 file을 기록할 준비를 하는데 FlatFileItemWriter는 facade일 뿐, 실제 파일을 기록하는 클래스를 따로 가지고있다.

flatFileItemWriter.setResource(new FileSystemResource(TEST_FILE_DIRECTORY + "/test.txt")));
flatFileItemWriter
.open(executionContext);


transactional 속성이 true일 경우 BufferedWriter 대신에 TransactionAwareBufferedWriter를 생성한 후 File 연산과 관련한 모든것을 위임한다.
if (transactional) {

return new TransactionAwareBufferedWriter(writer, new Runnable() {

              ....

});

}

else {

return new BufferedWriter(writer);

}


이제 TransactionAwareBufferedWriter에 대해 알아야되는데
얘는 Transaction 범위가 아닐 경우 File에 데이터를 기록하고
if (!transactionActive()) {

    writer.write(cbuf, off, len);

return;

}


Transaction 범위일 경우에는 TransactionSynchronizationManager에 StringBuffer를 만들어두고 여기에 기록을 한다.

// 최초 실행 시 버퍼를 초기화하고
TransactionSynchronizationManager.bindResource(
bufferKey, new StringBuffer());

// 이후 수행시 꺼내서 기록
StringBuffer buffer = 
(StringBuffer) TransactionSynchronizationManager.getResource(bufferKey);
buffer.append(cbuf, off, len);


close 명령이 내려올 경우, Transaction 범위이면 TransactionSynchronizationManager에 close했다는 hint만을 남기고
if (transactionActive()) {
      TransactionSynchronizationManager.bindResource(closeKey, Boolean.TRUE);        
      return;
}

Transaction 범위가 아닐경우 그냥 File을 close해버린다.
writer.close();

Transaction이 커밋되었을 경우에만 Buffer에 있는 내용을 꺼내 파일에 기록한다.
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
    @Override
    public void afterCompletion(int status) {
        if (status == STATUS_COMMITTED) {
             complete();
        }
}
private void complete() {
StringBuffer buffer = (StringBuffer) TransactionSynchronizationManager.getResource(bufferKey);
if (buffer != null) {
try {
writer.write(buffer.toString());
writer.flush();
if (TransactionSynchronizationManager.hasResource(closeKey)) {
writer.close();
closeCallback.run();
}
} catch (IOException e) {
throw new FlushFailedException("Could not write to output buffer",e);
} finally {
TransactionSynchronizationManager.unbindResource(bufferKey);
if (TransactionSynchronizationManager.hasResource(closeKey)) {
TransactionSynchronizationManager.unbindResource(closeKey);
} } } } }); }

저작자 표시
Posted by cherrykyun