Inflearn brand logo image

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

아그작님의 프로필 이미지
아그작

작성한 질문수

죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.

5장. 작전2: Run Squad 장악 작전 - 배치 실행의 진입점과 Spring Batch 자동 구성(시스템이 스스로 무장하는 순간 🏴‍☠️)

TransactionManager 분리/통합 사용 시 이해가 가지 않는 상황 발생

해결된 질문

작성

·

189

·

수정됨

1

안녕 킬구횽. 정성 가득한 강의 감사한 마음으로 공부하고 있다. 고맙다.

다름이 아니라, 해당 작전에서 설명한대로 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) 트랜잭션 매니저를 분리하는 경우

  1. TransactionTemplate의 execute 메서드 실행

     

  2. doInTransaction 메서드 실행 시작

     

  3. TestTasklet의 execute메서드의 실행 * transaction from JPA

  4. execute메서드 내부에서 런타임 예외가 발생하는 트랜잭션 메서드 호출 -> 트랜잭션에 rollback-only 마킹됨

  5. 내부적으로 정상 처리하였으므로 RepeatStatus.FINISHED 반환

  6. StepExecution update (version 1 -> version 2) 커밋 * 메타데이터용 DB 커넥션 사용하는 것을 확인함

  7. doInTransaction 메서드 실행 완료

  8. TransactionTemplate의 execute 메서드에서 커밋 시도 * transaction from JPA

  9. UnexpectedRollbackException발생 * rollback-only 마킹으로 인함

  10. 롤백 시작

  11. version 1 -> version 2로 StepExecution update 시도

  12. 이때 다음 예외가 발생한다.

OptimisticLockingFailureException: Attempt to update step execution id=4 with wrong version (1), where current version is 2

 

반면, 트랜잭션 매니저를 분리하지 않으면 OptimisticLocking 예외가 발생하지 않는데,

OptimisticLocking 예외가 발생하지 않는지 궁금하다.

 

(비즈니스, 메타 데이터: JPA ) 트랜잭션 매니저를 분리하지 않는 경우

  1. TransactionTemplate의 execute 메서드 실행

     

  2. doInTransaction 메서드 실행 시작

  3. TestTasklet의 execute메서드의 실행

  4. execute메서드 내부에서 런타임 예외가 발생하는 트랜잭션 메서드 호출 -> 트랜잭션에 rollback-only 마킹됨

  5. 내부적으로 정상 처리하였으므로 RepeatStatus.FINISHED 반환

  6. StepExecution update (version 1 -> version 2) 커밋

  7. doInTransaction 메서드 실행 완료

  8. TransactionTemplate의 execute 메서드에서 커밋 시도

  9. UnexpectedRollbackException 발생

  10. 롤백 시작

  11. version 1 -> version 2로 StepExecution update 시도

  12. 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

답변 5

4

KILL-9님의 프로필 이미지
KILL-9
지식공유자

💀 KILL-9의 완전 검증 결과

하.. 생각보다 오래걸렸다.. 예제 하나 만들어 디버깅을 돌렸어야했는데 시스템 처형자라고 너무 자만했다.

나의 전장임에도 불구하고 코드 레벨에서 하나하나 추적하는 게 시간이 꽤 걸리는구나.

$ ps aux | grep "overconfidence_process"
overconfidence_process  1337  99.9  95.0  /tmp/arrogance
$ kill -9 1337
$ echo "겸손 모드 활성화..." > /tmp/humble_kilgoo.log

 

자, 정리한다.

 

핵심 원리:

JdbcTransactionManager: JPA 트랜잭션의 존재를 모르므로, JPA 트랜잭션과 함께 사용되면 새로운 독립적인 JDBC 트랜잭션을 시작

JpaTransactionManager: 기존에 시작된 JPA 트랜잭션(우리의 경우 스텝 트랜잭션)이 있는지 확인하고 존재하면 기존 트랜잭션에 참여

 

이 핵심원리를 바탕으로 아래 분석결과를 따라가보도록.

 

상세 실행 흐름:

1) TaskletStep에서 TransactionTemplate.execute() 호출

 

2) TransactionTemplate.execute() 내부에서 TransactionStatus status = this.transactionManager.getTransaction(this);를 통해 트랜잭션을 새로 생성한다. 이때는 JpaTransactionManager가 사용된다.

 

3) TransactionTemplate.execute가 ChunkTransactionCallback의 doInTransaction()을 호출한다. 이 doInTransaction() 안에 Tasklet.execute() 호출이 담겨있지. (모두 TaskletStep 코드 안에 담긴 놈들이다 참고하라)

3-1) Tasklet 호출 전에 StepExecution의 상태를 별도로 저장해둔다 (이때의 StepExecution은 version 1이겠지)

 

4) 너의 Tasklet이 성공 응답을 했다. 따라서 TaskletStep line#421에서 stepExecutionUpdated = true;를 한 번 호출해주고 line#435에서 getJobRepository().update(stepExecution);를 호출. 즉, StepExecution을 메타데이터 저장소에 저장하려고 시도한다.

 

5) 여기가 중요하다. getJobRepository().update(stepExecution) 호출에는 메타데이터 전용 TransactionManager가 참여한다 (자세한 내용은 JobRepositoryFactoryBean(또는 AbstractJobRepositoryFactoryBean)과 TransactionInterceptor를 참고하도록.)

5-1: 배치 메타데이터 트랜잭션 매니저를 분리한 경우) JdbcTransactionManager가 사용된다. 기존 트랜잭션이 없다고 판단하고 새 트랜잭션을 생성한다.

5-2: 배치 메타데이터 트랜잭션 매니저를 분리 안한 경우) 앞서 사용한 JpaTransactionManager가 그대로 사용된다. 따라서 2)에서 생성한 트랜잭션이 있기 때문에 새 트랜잭션을 생성하지 않는다.

5-3) TransactionInterceptor(정확히는 부모 클래스 TransactionAspectSupport)가 트랜잭션 매니저를 사용해 commit 시도

5-4) 핵심!! 트랜잭션 매니저의 부모(거진 할아버지) 클래스 AbstractPlatformTransactionManager의 processCommit()에서 새로 생성된 트랜잭션인지 여부 검사 → 트랜잭션을 새로 생성한 5-1케이스(JdbcTransactionManager)인 경우에만 트랜잭션 commit.

따라서 V2로 메타데이터 저장소에 업데이트된다. 기존 트랜잭션을 그대로 사용하는 5-2 케이스(JpaTransactionManager)는? EntityManager 1차 캐시에 남아있을 것이다. 따라서 이 경우에는 DB에는 여전히 V1 상태로 남아있게 되지

 

자.. 이제 돌아오자. 앞선 너의 Tasklet.execute()가 정상 응답했으니 ChunkTransactionCallback의 doInTransaction()도 정상적으로 완료되었다.

 

다음은?

 

6) TransactionTemplate이 3)의 트랜잭션 commit을 시도.(AbstractPlatformTransactionManager.processCommit() 참고)한다.(그러나 너의 TestRepository(@Transactional method)에서 예외가발생했기에 = rollback-only 이기 때문에) UnexpectedRollbackException이 던져지지

if (unexpectedRollback) {
    throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");
}

 

7) AbstractPlatformTransactionManager.processCommit()의

다음 코드로인해 ChunkTransactionCallback의 afterCompletion()이 호출된다.

catch (UnexpectedRollbackException ex) {
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); // 트리거
    this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));
    throw ex;
}	

afterCompletion()으로가보자 TaskletStep 내부에 있다. 

 

8) afterCompletion()에서는 트랜잭션 상태와 stepExecutionUpdated(4)에서 설정)를 비교한 후 아래 copy 메서드를 호출한다.

copy(oldVersion, stepExecution); // 여기 oldVersion이 3-1)에서 Tasklet 실행 전에 저장해둔 v1 StepExecution이다.

private void copy(final StepExecution source, final StepExecution target) {
    target.setVersion(source.getVersion());
    target.setWriteCount(source.getWriteCount());
    target.setFilterCount(source.getFilterCount());
    target.setCommitCount(source.getCommitCount());
    target.setExecutionContext(new ExecutionContext(source.getExecutionContext()));
}

다시 말해 v2 상태인 현재의 StepExecution이 v1의 상태로 롤백되는 것이지

 

9) 자... afterCompletion()도 끝났고 이제 7)에서 다시 던진 예외가 쭉 전파된다. 어디까지 가냐면 AbstractStep(TaskletStep의 부모클래스다)의 line#300: getJobRepository().update(stepExecution);이다.

 

10) JdbcTransactionManager를 사용한 경우 5-4)에 의해 이미 DB에는 v2 StepExecution이 저장되어 있다. 그리고 너의 스텝은 여기서 v1 StepExecution을 저장하려고 하지.

그럼?

11) OptimisticLockingFailureException

 

PS: 원래는 디버깅 포인트까지 잡아주려고 했지만 생각보다 너무 오래걸려 거기까진 못했다. 디테일은 위 흐름을 참고해 직접 디버깅해보면된다. 그러나 1시간 이내로 요청한다면 각 라인까지 짚어 주겠다

$ echo "완벽한 분석 완료, 디버그 포인트는 옵션" > /tmp/analysis_complete.log
$ chmod 755 /tmp/debug_points_available.sh

 

엣지케이스를 질문한 너가 미울 뻔했으나 덕분에 강의에 추가하고 싶은 내용이 생겼다 고맙다 아그작..

$ ps aux | grep "anger_process"
anger_process  666  0.0  0.0  /tmp/hatred
$ kill -9 666
$ echo "분노 프로세스 종료됨" > /tmp/anger_terminated.log

$ touch /tmp/unexpected_gratitude.log
$ chmod 777 /tmp/happiness.sh

$ echo "이런 깊은 질문이 더 좋은 강의를 만든다. 그러나 자주는 하지마라..ㅋㅋ" >> /tmp/teaching_philosophy.log

 


직접 디버깅한게 아니다 틀린 부분이 발견된다면 댓글달라 그러나 기억하라 핵심원리는 동일하다

2

아그작님의 프로필 이미지
아그작
질문자

킬구형... 정말 감동이다. 이렇게까지 상세한 답변을 해주다니...

덕분에 완벽하게 이해 완료했다. 내가 놓쳤던 건 다음 부분이었다.

AbstractPlatformTransactionManager의 processCommit()에서 새로 생성된 트랜잭션인지 여부 검사 → 트랜잭션을 새로 생성한 5-1케이스(JdbcTransactionManager)인 경우에만 트랜잭션 commit. 따라서 version 2로 업데이트된다. 기존 트랜잭션을 그대로 사용하는 5-2 케이스(JpaTransactionManager)는? EntityManager 1차 캐시에 남아있을 것이다. 따라서 DB에는 여전히 version 1 상태로 남아있게 되지

사실 질문한 날 하루종일 스프링 배치 디버깅을 했다. 그러다 보니 트랜잭션 매니저 통합 사용 시, AbstractPlatformTransactionManager의 processCommit() 에서 커밋이 날라가서 DB에 반영이 되었다고 잘못 확인을 하였던 것 같다. (Participating in existing transaction 로그가 이제서야 보인다... 대가리 박는다.)

그래서 질문의 내용이 '왜 트랜잭션 매니저 통합 사용 시 OptimisticLocking이 발생하지 않는가''였다...

 

디버깅한 내용이 제대로 파악이 안되어있는데, 일목요연하게 정리해준 덕분에 빠르게 오해를 바로 잡을 수 있었다. 킬구형...정말 고맙고.... 미안하다...

KILL-9님의 프로필 이미지
KILL-9
지식공유자

아니다 덕분에 새로운 강의 소스를 발견했다 굿이다 💀

(참고로 사실 6번 틀린것같다ㅋㅋ 예제돌려보며 직접 디버깅을했어야 정확한데.. 금방 파악할거라 착각한 나의 잘못이다. 그러나 결과적으로 7번으로 가는것은 사실이다. (안그러면 v1으로 롤백될수 없기때문이지)

2

KILL-9님의 프로필 이미지
KILL-9
지식공유자

$ kill -9 confusion_process
$ rm -rf /tmp/transaction_mystery/*
$ echo "1차 답변 시작..." > /tmp/kilgoo_first_answer.log

 

네가 발견한 건 Spring Batch의 숨겨진 함정이다

우선 네 분석이 100% 정확하다.

네가 만난 상황을 정리하면:





@Transactional(REQUIRED) 메서드가 기존 트랜잭션에 참여했다



RuntimeException 발생으로 해당 트랜잭션이 rollback-only로 마킹되었다



하지만 네 Tasklet이 예외를 catch해서 성공으로 위장했다



결과적으로 Step의 TransactionTemplate(내부적으로 TransactionManager 사용)이 rollback-only 트랜잭션을 커밋하려고 시도



UnexpectedRollbackException 발생

여기까지는 당연한 결과다. 문제는 그 다음이지.

 

하... 재밌구나..(아니.. 너가 밉다.. 살살해줘라)

우선 대략적인 파악은 완료했다. 결론부터말하자면 JpaTransactionManager가 메타데이터를 즉시 쓰지 않았기 때문으로 파악된다.

다만 코드레벨에서 통합/분리 시의 트랜잭션 생성(새로생성하느냐 여부) 로직을 확실히 정리하고 다시 답변하겠다..

 

넉넉히 30분만 더 후에 만나자

2

KILL-9님의 프로필 이미지
KILL-9
지식공유자

💀 KILL-9의 긴급 상황 보고

퇴근 지옥에서 겨우 탈출했다.

 

$ ps aux | grep "야근프로세스"
야근프로세스  1337  99.9  80.0  회사에서 집으로
$ kill -9 1337
야근프로세스: terminated

$ echo "확인이 늦었다 조금만 기다려라 형 " >> /var/log/kilgoo.log

상황 보고:

  • 현재 시각: 야근 종료 후 30분 카운트다운 시작

  • 시스템 상태: 뇌.exe 재부팅 중...

  • 예상 소요 시간: 30분 내 답변 배포 예정

긴급 패치 노트:

#!/bin/bash
# 답변 생성 스크립트
echo "TransactionManager 분리/통합 이슈 분석 시작..."
echo "OptimisticLocking 예외 원인 추적 중..."
echo "EntityManager 라이프사이클 디버깅 준비..."

0

안녕하세요, 인프런 AI 인턴입니다.

아그작님께서 Spring Batch에서 TransactionManager를 분리하여 사용할 때 발생하는 문제점에 대해 질문하셨군요. 각각의 트랜잭션 매니저가 어떻게 동작하는지에 대한 이해가 필요한 상황인 듯합니다.

문제 설명

  1. UnexpectedRollbackException: rollback-only로 마킹된 트랜잭션에서 커밋을 시도할 때 발생하며, 이는 예상된 동작입니다.

  2. OptimisticLockingFailureException: 트랜잭션 중에 메타데이터가 잘못 처리되어 발생할 수 있는 예외입니다. 이는 주로 두 트랜잭션 매니저에서 서로 다른 타이밍에 커밋하는 경우 발생할 수 있습니다.

분리된 트랜잭션 매니저의 경우

  • JPA 트랜잭션에서 비즈니스 로직을 처리하고 난 후, JDBC 트랜잭션을 통해 메타데이터가 업데이트됩니다. 만약 JPA 트랜잭션이 실패하고 롤백되는 경우, 이미 JDBC 트랜잭션으로 커밋된 메타데이터와 불일치가 발생하여 Optimistic Lock 예외가 발생할 수 있습니다.

통합된 트랜잭션 매니저의 경우

  • 통합된 트랜잭션 매니저를 사용하면, 비즈니스 로직과 메타데이터가 같은 트랜잭션 컨텍스트에서 관리됩니다. 따라서 JPA 트랜잭션에 의해 관리된 로직과 동일하게 JDBC 메타데이터를 관리하기 때문에 일관성이 유지되고, Optimistic Lock 예외가 발생하지 않습니다.

해결 방안

  • 비즈니스 로직과 메타데이터 처리를 하나의 트랜잭션 컨텍스트에서 처리하도록 설계하여, 일관성을 유지하고 예외를 방지하는 것이 좋습니다. 특히 중요한 데이터 처리에서는 트랜잭션 간의 일관된 상태를 유지하는 것이 핵심입니다.

  • 선택지:

    • 가능한 경우, 별도의 트랜잭션 매니저 대신 단일 트랜잭션 매니저를 사용하여 트랜잭션을 관리합니다.
    • Propagation.REQUIRED와 같은 전파 규칙을 활용하여 방법을 최적화·정리합니다.

기타 유사한 사례로 “트랜잭션 분리와 통합 시의 메타데이터 동작 문제”에 대한 논의를 다룬 답변이 있으니 참고하시길 권장드립니다:

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

아그작님의 프로필 이미지
아그작

작성한 질문수

질문하기