인프런 커뮤니티 질문&답변

dukbabo님의 프로필 이미지
dukbabo

작성한 질문수

스프링 배치

DB - JdbcBatchItemWriter

청크 배치 관련 write commit 및 rollback 관련 질문있습니다.

작성

·

2.3K

0

- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요!
- 먼저 유사한 질문이 있었는지 검색해보세요.
- 서로 예의를 지키며 존중하는 문화를 만들어가요.
- 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
 
청크 프로그램으로
itemReader 에서 데이터를 조회 한후에
 
itemWriter로 데이터를 보낸후
 
데이터를 insert 처리를 합니다.
 
보통 청크 사이즈가 5이라고 하면
 
리드를 10건 했다면
write는 5건 단위로 커밋 및 롤백하는것으로 알고있는데요
 
만약에
리드를 10건으로 entity로 했고
entity안에 리스트 엔티티로 데이터가 1000건 있다면
write 할때는 어떤식으로 commit 및 롤백 처리 되나요?
기존 대로 5건씩 처리 되는건가요?
 
customerItemWriter를 이용하여 리스트로 받고
for문을 이용하여 insert 및 update 합니다.
 
 
for문으로 entity를 청크 사이즈 만큼 5건을 읽는데
entity에 list가 있어서
또 for 문으로 1000건을 돌려서 insert나 update 처리해야 되는경우입니다
 
public class TranPayObjectDto {
private String clamUniqNo;
private String fromDate;
private String toDate;
private String custId;
private String isAuto;
private String cmsServiceFlag;
private List<NhcmsPayDto> nhcmsPayDto;
private List<AutoBillAddDto> autoBillAddDto;
}
1000건이 한번에 commit 되는건가요? 5건씩 commit 되는 건가요?
아니면 다른 방법으로 해야되는지 문의합니다
 
만일 1000건이 한번에 commit된다면 500건 단위로 강제로 commit하거나 rollback 할수 있는 방법이 있을까요?
 
아 제가 jdbcTemplate로 for문으로 돌려서 사용하는데 혹시 건 단위로 commit 되는건가요?
제가 좀 헷갈려서요 ...
 
만일 건 단위로 한다면 500건씩 묶어서 할수 있는 방법이 있나요?
 

답변 3

3

정수원님의 프로필 이미지
정수원
지식공유자

스프링 배치에서 트랜잭션은 청크단위로 커밋이 됩니다.

즉 ItemReader 에서 청크사이즈 만큼 읽은 데이터가 ItemProcessor 를 거쳐서 ItemWriter 로 전달되어 커밋이 되면  다시 청크사이즈 만큼 위 내용을 반복하게 됩니다.

그렇기 때문에 ItemWriter 에서 넘어온 output 청크데이터를 처리하게 되면 commit 이 되는 개념이기 때문에 커밋은 ItemWriter 의 모든 과정이 완료된 이후  일어나게 되고 또한 트랜잭션이 종료하게 됩니다.

그래서 list 에 담겨져 있는 데이터를 for 구문으로 인서트 처리하는 경우 ItemReader 에서 시작된 트랜잭션이 계속 유효한 상태라면 엔터티를 포함한 모든 하위 데이터의 작업이 완료된 이후 commit 이 일어나게 됩니다.

다만 스프링 배치에서 제공하는 JdbcBatchItemWriter 는 작성된 인서트 쿼리대로 작업하기 때문에 하위 list 데이터를 별도로 인서트  할 수는 없고 별도의 커스텀한 ItemWriter 구현체를 만들어서 로직을 구현해야 합니다.

강의에서 설명하고 있지만 TaskletStep 이 반복해서 ChunkOrientedTasklet 클래스를 실행시키고 ChunkOrientedTasklet 클래스는 내부적으로 ItemReader -> ItemProcessor -> ItemWriter 순으로 청크단위로 데이터를 처리하고 있는데 ChunkOrientedTasklet 감싸고 있는 클래스가 바로 TransactionTemplate 입니다. 

여기서 TransactionTemplate 클래스 내부를 보면 다음과 같은 코드가 있습니다.0

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}

코드를 보시면 

TransactionStatus status = this.transactionManager.getTransaction(this);

트랜잭션을 얻고

result = action.doInTransaction(status);

트랜잭션 안에서 청크 프로세스를 실행하고 

rollbackOnException(status, ex);0

실패하면 rollback 하고 

this.transactionManager.commit(status);

성공하면 commit 합니다.

즉 청크 프로세스는 트랜잭션안에서 처리되기 때문에 ItemWriter 의 모든 작업이 완료되면 commit 됩니다.

그래서 커스텀하게 ItemWriter 를 구현해서 그 안에서 모든 데이터 입력처리를 하더라고 commit 은 일괄적으로 이루어지기 때문에 청크단위의 1번 커밋이 일어난다고 볼 수 있습니다.

강의에서도 설명을 하고 있는 부분이니 참고해 주시기 바랍니다.

 

1

정수원님의 프로필 이미지
정수원
지식공유자

ItemReader 의 read() 메소드는 아이템 1건을 읽는 것을 의미합니다.

이 때 아이템이란 한개의 entity 라고 볼 수 있습니다.

즉 DB 로부터 1개의 row 를 객체와 매핑한 객체입니다.

jpa 같은 경우 entity 안에 리스트 항목이 있을 경우 연관관계로 매핑한 값이 채워져서 나오게 됩니다.

지연 로딩일 경우에도 트랜잭션이 유효하다면 리스트 값을 참조할 수 있습니다.

이 같은 경우는 리스트 개수와 상관없이 db 로 부터 읽은 엔터티의 개수가 곧 청크사이즈로 잡히게 됩니다.

즉 jpa 내부적으로 리스트항목을 채운 아이템 한 개를 반환하기 때문에 정확하게 청크사이즈만큼 읽은 개수를 commit 혹은 rollback 하게 됩니다.

그렇다면 JDBC 같은 경우는 RowMapper 를 사용해서 DB 와 객체를 바인딩하게 되는데 jdbc 는 스프링 배치에서 리스트 항목을 자동으로 채워주지 않습니다.

이 부분은 별도의 로직을 구현해서 엔터티 안에 리스트 항목의 값을 채운 객체한개를 반환하도록 해야 합니다.

그래서 DB 에서 쿼리를 통해 나온 row 데이터를 기준으로 청크 사이즈 만큼만 읽어오기 때문에 질문하신 내용에 답변을 드린다면 5건 읽어서 commit 하는 것이 맞습니다.

쉽게 말하면 스프링 배치는 pageSize 만큼 DB 에서 읽어와서 객체에 바인딩 해주고 읽은 개수가 chunkSize 와 일치할 때 다음 프로세스로 넘기는  역할만 할 뿐 그 이후의 작업은 개발자의 몫입니다.

결론적으로 엔터티 안에 리스트는 commit 이나 rollback 에 관여하는 chunkSize 와는 아무런 연관이 없다고 보시면 됩니다.

0

dukbabo님의 프로필 이미지
dukbabo
질문자

답변 주셔서 감사합니다.

 

한가지  궁금한 부분이

하나의 entity에 리스트로 만건이 있는데

 

wirte 할때는 5건을 넘기고

 

처음 for문은 1건이고

 두번째 for 문을 돌려서 만건을 insert 하는데

 

청크 사이즈 5이니 5가 될때 commit 을 한다고 생각하면 list에 담겨있는 5만건이 한번에 commit된다는건가요?

 

아니면 청크 사이즈는 5이지만 list의 순차에의해서 5건씩 5만건이 commit 된다는건가요?

 

답변이 조금 이해가 안되서 재 질문 합니다.

 

dukbabo님의 프로필 이미지
dukbabo

작성한 질문수

질문하기