회원 정보 수정 API를 구현하며 dirty checking을 이용한 회원 정보 update를 사용했습니다. service의 update 메소드를 호출하기 전과 후를 controller단에서 로그를 찍어보니 정보가 잘 변경되는데 DB에는 반영이 되지 않아 애를 먹었습니다. serviceImpl단의 update 메소드에 @Transactional을 붙여서 해결했습니다. 그 전에는 service단의 update 메소드에 @Transactional을 붙였더니 안되더라구요. service단의 메소드에 붙이는 @Transactional와 serviceImpl단의 메소드에 붙이는 @Transactional이 다른 트랜잭션으로 작동하는건지, 그렇다면 어디에 @Transactional을 붙이는게 맞는건지 궁금합니다.
@Service @RequiredArgsConstructor public class SignUpMemberService implements SignUpMemberUseCase { private final MemberRepository memberRepository; @Override public Long invoke(final Command command) { validateEmailIsUnique(command.email()); validateNicknameIsUnique(command.nickname()); validatePhoneIsUnique(command.phone()); final Member member = command.toDomain(); return memberRepository.save(member).getId(); } } @Service @Transactional @RequiredArgsConstructor public class SignUpMemberService implements SignUpMemberUseCase { private final MemberRepository memberRepository; @Override public Long invoke(final Command command) { validateEmailIsUnique(command.email()); validateNicknameIsUnique(command.nickname()); validatePhoneIsUnique(command.phone()); final Member member = command.toDomain(); return memberRepository.save(member).getId(); } } 사용자 가입 UseCase에 대한 구현에서 앞단에서는 unique field에 대한 validation을 진행합니다 그 후에 memberRepository.save (member)를 진행하는데 (memberRepository는 Data Jpa Repo) 아시다시피 SimpleJpaRepository의 save흐름에는 @Transactional이 적용되어 있습니다 따라서 이 SignUpMemberService의 invoke 흐름부터 굳이 @Transactional을 걸어야 하는 생각이 듭니다 물론 붙임으로써 이 메소드가 writable한 로직을 가진다고 코드레벨에서 알고 JPA를 모르는사람은 SimpleJpaRepository의 구조를 모르기 때문에 가독성?적인 측면에서는 붙이는게 이해에 수월하다고 생각하는데 tx scope를 최대한 짧게 가져가는게 좋다는 생각이 들어서 약간의 고민을 하게 되었습니다
안녕하세요 재고관리시스템 강의를 복습하며 내용을 정리하는 중 의문이 생겨 질문을 드립니다. 가장 처음에 application code 레벨에서만 동시성을 해결하기 위해 syncrhonized 를 사용할 경우 해당 메서드에는 @Transactional 을 붙여서는 안된다고 설명해 주셨습니다. 실제로 이를 붙일 경우, 동시에 decrease 메서드가 호출되고 해당 로직 내부로 들어가는 것이 가능함을 확인하였습니다. 그런데 여기서 제가 의문이 들었던 것이 있습니다. @Transacitonal 을 사용할 경우 Spring AOP 에 의해 매 번 다른 proxy 인스턴스를 통해 target object 로의 호출을 하게 됩니다. proxy 객체에 대한 lock 은 서로 다른 프록시들 사이에 공유되지 않는다고 하더라도, 내부적으로 호출되는 target 객체에 대한 decrease 메서드는 결국 동일한 객체에 대한 호출을 하기 때문에, 공유되는 lock 에 대한 경쟁이 일어나는 것이 아닌가 생각이 들었습니다. 최종적으로는 target 객체에 대한 synchronized 메서드를 호출하는 것이라면, 단 하나의 스레드만 임계 영역에 들어갈 수 있어야 할 것 같은데, 그렇지 않음을 확인하였습니다. 왜 이런 일이 일어나는 것인지 이해가 잘 되지 않습니다 ㅠㅠ 이와 관련해서 어떤 키워드로 공부해보면 좋을지 추천 가능할까요..?
@Slf4j @Service public class MainService { public void methodA() { log.info("=== methodA 시작... ==="); // 비즈니스 로직 A .... methodB(); log.info("=== methodA 종료... ==="); } @Transactional public void methodB() { log.info("=== methodB 시작... ==="); log.info("-> is methodB transaction active? = {}", TransactionSynchronizationManager.isActualTransactionActive()); // 비즈니스 로직 B .... log.info("=== methodB 종료... ==="); } } @Controller @RequiredArgsConstructor public class MainController { private final MainService mainService; public ResponseEntity<Void> transactionalTestMethod() { mainService.methodA(); return ResponseEntity.ok().build(); } } 이러한 MainService 구조에서 MainController에서 mainSerivce.methodA()를 호출하게 되면 methodA는 현재 트랜잭션을 유도하지 않고 있고 그 내부적으로 methodB는 내부 프록시 호출이기 때문에 당연히 methodB에는 트랜잭션 처리가 되지 않음은 이해하였습니다 그런데 이와 관련해서 실험을 하던 도중에 @Slf4j @Service public class MainService { @Transactional public void methodA() { log.info("=== methodA 시작... ==="); // 비즈니스 로직 A .... methodB(); log.info("=== methodA 종료... ==="); } @Transactional public void methodB() { log.info("=== methodB 시작... ==="); log.info("-> is methodB transaction active? = {}", TransactionSynchronizationManager.isActualTransactionActive()); // 비즈니스 로직 B .... log.info("=== methodB 종료... ==="); } } 이러한 MainService 구조에서 mainService.methodA를 호출할 때 methodA에는 트랜잭션 처리가 됨이 당연한데 그 내부적으로 this.methodB를 호출하면 methodB에도 TransactionSynchronizationManager.isActualTransactionActive()의 결과가 true로 나옴에 따라 트랜잭션 처리가 되는 것 같습니다 제가 알고있던 것은 methodA에서 트랜잭션 처리가 진행이 되더라도 methodB는 내부적으로 호출되는 메소드이기 때문에 트랜잭션 처리가 이루어지지 않는다고 알고있었습니다 물론 Log를 찍어도 methodB 전 후에는 Participating transaction 관련 로그는 없었습니다 하지만 methodB에서 TransactionSynchronizationManager.isActualTransactionActive()의 결과가 true로 나옴에 따라 트랜잭션 처리가 유지되는 것 같은데 혹시 위와 같은 구조에서도 트랜잭션 처리가 유효한건가요?
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예 [질문 내용] 강의 중간에 이런 말을 하셨습니다. @Transactional -> readOnly 옵션을 true 로 초기화하게 되면 성능 최적화도 가능하다. 그런데 개인적으로 강의를 들으면서 이런 "있으면 손해 볼 것 없는 옵션" 들은 꼭 포함되어야 한다고 생각을 합니다. 그런데 굳이 이렇게 선언해서 사용하게 만든 이유를 알 수 있을까요 ?? 저는 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의를 수강하지 않았습니다. 때문에 위의 답을 해당 강의에서 찾을 수 있다면 그렇다면 답변 부탁드립니다. 감사합니다.
안녕하세요, 강의 잘 듣고 있습니다!! JPA 사용 중에 강의만으로는 어려운 문제를 만나 문의드립니다! JPA 트랜잭션에 대한 오류인것 같고 꽤 오래 찾아았으나, 아직 답을 찾지 못했습니다. 현상은 @Scheduled 가 추가된 메소드에서 다른 서비스 A의 @Transactional 메소드를 호출 시 아래와 같은 에러가 발생합니다. 예시) @Transactional public void changeStatus(Long id, String status) { Obj o = objRepository.findById(id).get(); o.setStatus(status); } 환경 SpringBoot 2.4 이며, 자동으로 설정되는 JpaTransactionManager 를 사용합니다. ============== 에러 메시지 ============== 2021-08-30 18:14:17.536 ERROR 18252 --- [ scheduler-1] o.s.s.s.TaskUtils$LoggingErrorHandler.handleError:95 - Unexpected error occurred in scheduled task org.springframework.orm.jpa.JpaSystemException: Unable to perform beforeTransactionCompletion callback: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.; nested exception is org.hibernate.HibernateException: Unable to perform beforeTransactionCompletion callback: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:331) Trace 로그로 볼 때에는 transaction marked for rollback only 라는 메시지도 나옵니다. 변경 감지로 할 경우와, save 메소드를 호출하는 경우 모두 발생하구요.. 제가 무언가 놓치고 있는 게 있다면, 조언 부탁드립니다!!
테스트 시 @Transactional 은 기본적으로 rollback 으로 작동한다고 하셨는데 일반적으로 db에서의 rollback 은 db에 쿼리가 나간 이후에 그 결과를 다시 원복하는 것으로 알 고 있습니다. 그런데 이 경우에는 아예 db로 insert 쿼리 자체가 나가지 않은 것으로 보이는데 그러면 최초 영속성 컨텍스트에만 저장하고, 종료 시점에는 db에 쿼리가 나가지 않고 끝나는게 맞나요?