TransactionManager 분리/통합 사용 시 이해가 가지 않는 상황 발생
해결됨
죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
안녕 킬구횽. 정성 가득한 강의 감사한 마음으로 공부하고 있다. 고맙다. 다름이 아니라, 해당 작전에서 설명한대로 TransactionManager을 분리해서 사용하고 있었는데, 이때 이해가 가지 않는 상황이 있다. 글이 조금 길어서, 요약을 먼저 하겠다. 요약 비즈니스용 트랜잭션 매니저(JPA) 과 메타데이터용 트랜잭션 매니저(JDBC) 을 분리한 경우 , UnexpectedRollbackException 이 발생한 후에 OptimisticLockingFailureException 까지 추가로 발생한다. 그러나 트랜잭션 매니저를 분리하지 않은 경우 , UnexpectedRollbackException 은 발생하지만 OptimisticLocking 예외는 발생하지 않는다 . 이유가 궁금하다. 상황 설명 다음은 @Configuration 클래스다. @Bean @Primary @ConfigurationProperties("spring.datasource.service") public DataSource dataSource() { return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } @Bean @BatchDataSource @ConfigurationProperties("spring.datasource.batch") public DataSource batchDataSource() { return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } @Bean @Primary public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } @Bean @BatchTransactionManager public PlatformTransactionManager batchTransactionManager(@BatchDataSource DataSource dataSource) { return new JdbcTransactionManager(dataSource); } 다음은 job을 구성하는 ( 잘못 작성된 ) tasklet이다. 트랜잭션 전파 속성은 Propagation.REQUIRED를 사용하고 있다. class TestTasklet implements Tasklet{ private final TestRepository testRepository; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { try{ // testRepository의 @Transactional method 호출 // but @Transactional method에서 RuntimeException을 throw } catch(RuntimeException e){ // sucess process logic } return RepeatStatus.FINISHED; } 위 코드에서, 예외를 캐치하는 것으로 예외를 처리한 것은 나의 실수였다. 런타임 예외를 던지는 트랜잭션은 rollback-only 마킹 처리되어 해당 트랜잭션이 커밋될 수 없었기 때문이다. 이해가 가지 않는 지점은 지금부터다. 이를 설명하기 위해 내가 파악한 흐름을 정리해봤다. 오류가 있을 수 있다. (비즈니스: JPA / 메타 데이터: JDBC) 트랜잭션 매니저를 분리하는 경우 TransactionTemplate의 execute 메서드 실행 doInTransaction 메서드 실행 시작 TestTasklet의 execute 메서드의 실행 * transaction from JPA execute 메서드 내부에서 런타임 예외가 발생하는 트랜잭션 메서드 호출 -> 트랜잭션에 rollback-only 마킹됨 내부적으로 정상 처리하였으므로 RepeatStatus.FINISHED 반환 StepExecution update (version 1 -> version 2) 커밋 * 메타데이터용 DB 커넥션 사용하는 것을 확인함 doInTransaction 메서드 실행 완료 TransactionTemplate의 execute 메서드에서 커밋 시도 * transaction from JPA UnexpectedRollbackException 발생 * rollback-only 마킹으로 인함 롤백 시작 version 1 -> version 2로 StepExecution update 시도 이때 다음 예외가 발생한다. OptimisticLockingFailureException: Attempt to update step execution id=4 with wrong version (1), where current version is 2 반면, 트랜잭션 매니저를 분리하지 않으면 OptimisticLocking 예외가 발생하지 않는데, 왜 OptimisticLocking 예외가 발생하지 않는지 궁금하다. (비즈니스, 메타 데이터: JPA ) 트랜잭션 매니저를 분리하지 않는 경우 TransactionTemplate의 execute 메서드 실행 doInTransaction 메서드 실행 시작 TestTasklet의 execute 메서드의 실행 execute 메서드 내부에서 런타임 예외가 발생하는 트랜잭션 메서드 호출 -> 트랜잭션에 rollback-only 마킹됨 내부적으로 정상 처리하였으므로 RepeatStatus.FINISHED 반환 StepExecution update (version 1 -> version 2) 커밋 doInTransaction 메서드 실행 완료 TransactionTemplate의 execute 메서드에서 커밋 시도 UnexpectedRollbackException 발생 롤백 시작 version 1 -> version 2로 StepExecution update 시도 OptimisticLock 예외가 터지지 않고 정상적으로 버전 업데이트가 완료된다. 이때 OptimisticLocking 예외가 발생하지 않았다는 것은, 현재 stepExecution의 버전이 1 이라는 것으로 받아들여진다. 의문인 점은, StepExecution update (version 1 -> version 2)이 커밋되는 것을 디버깅을 통해 확인했는데, 어째서 롤백 시점에서 stepExecution의 버전이 1이냐는 것이다. 무언가 엔티티 매니저와 관련이 있는 것 같은데, 잘모르겠다. 도움이 될까하여, 로그 일부를 첨부한다. 트랜잭션 매니저 분리하는 경우 2025-07-27T22:16:13.626+09:00 TRACE 141032 --- [server] [ main] o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 1, parameter value [28], value class [java.lang.Long], SQL type unknown 2025-07-27T22:16:13.626+09:00 TRACE 141032 --- [server] [ main] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.batch.core.repository.support.SimpleJobRepository.update] 2025-07-27T22:16:13.626+09:00 DEBUG 141032 --- [server] [ main] o.s.jdbc.support.JdbcTransactionManager : Initiating transaction commit 2025-07-27T22:16:13.626+09:00 DEBUG 141032 --- [server] [ main] o.s.jdbc.support.JdbcTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@33286612 wrapping org.postgresql.jdbc.PgConnection@6ae1d5f1] 2025-07-27T22:16:13.627+09:00 DEBUG 141032 --- [server] [ main] o.s.jdbc.support.JdbcTransactionManager : Releasing JDBC Connection [HikariProxyConnection@33286612 wrapping org.postgresql.jdbc.PgConnection@6ae1d5f1] after transaction 2025-07-27T22:16:13.627+09:00 DEBUG 141032 --- [server] [ main] o.s.jdbc.support.JdbcTransactionManager : Resuming suspended transaction after completion of inner transaction 2025-07-27T22:16:13.627+09:00 DEBUG 141032 --- [server] [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit 2025-07-27T22:16:13.627+09:00 DEBUG 141032 --- [server] [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(858762286<open>)] 2025-07-27T22:16:13.630+09:00 INFO 141032 --- [server] [ main] o.s.batch.core.step.tasklet.TaskletStep : Commit failed while step execution data was already updated. Reverting to old version. 2025-07-27T22:16:13.630+09:00 DEBUG 141032 --- [server] [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(858762286<open>)] after transaction 2025-07-27T22:16:13.630+09:00 DEBUG 141032 --- [server] [ main] o.s.batch.repeat.support.RepeatTemplate : Handling exception: org.springframework.transaction.UnexpectedRollbackException, caused by: org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only 2025-07-27T22:16:13.631+09:00 DEBUG 141032 --- [server] [ main] o.s.batch.repeat.support.RepeatTemplate : Handling fatal exception explicitly (rethrowing first of 1): org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only 2025-07-27T22:16:13.633+09:00 ERROR 141032 --- [server] [ main] o.s.batch.core.step.AbstractStep : Encountered an error executing step switchAliasTargetStep in job addressIndexingJob 분리하지 않는 경우 2025-07-27T22:18:22.239+09:00 TRACE 114800 --- [server] [ main] o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 1, parameter value [8], value class [java.lang.Long], SQL type unknown 2025-07-27T22:18:22.240+09:00 TRACE 114800 --- [server] [ main] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.batch.core.repository.support.SimpleJobRepository.update] 2025-07-27T22:18:22.240+09:00 DEBUG 114800 --- [server] [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit 2025-07-27T22:18:22.240+09:00 DEBUG 114800 --- [server] [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(707576293<open>)] 2025-07-27T22:18:22.241+09:00 INFO 114800 --- [server] [ main] o.s.batch.core.step.tasklet.TaskletStep : Commit failed while step execution data was already updated. Reverting to old version. 2025-07-27T22:18:22.242+09:00 DEBUG 114800 --- [server] [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(707576293<open>)] after transaction 2025-07-27T22:18:22.242+09:00 DEBUG 114800 --- [server] [ main] o.s.batch.repeat.support.RepeatTemplate : Handling exception: org.springframework.transaction.UnexpectedRollbackException, caused by: org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only 2025-07-27T22:18:22.242+09:00 DEBUG 114800 --- [server] [ main] o.s.batch.repeat.support.RepeatTemplate : Handling fatal exception explicitly (rethrowing first of 1): org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only 2025-07-27T22:18:22.246+09:00 ERROR 114800 --- [server] [ main] o.s.batch.core.step.AbstractStep : Encountered an error executing step switchAliasTargetStep in job addressIndexingJob
- java
- spring
- spring-boot
- spring-batch
- backend





