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

한수현님의 프로필 이미지

작성한 질문수

재고시스템으로 알아보는 동시성이슈 해결방법

소스코드

version 컬럼이 증가하지 않는 이유

작성

·

181

·

수정됨

0

현재 강의 내용을 바탕으로 OptimisticLock 을 구현중인데,
version이 증가하지 않는 이유를 알고 싶습니다.

도메인은 이렇습니다.

@Entity
@Table(name = "appointment")
public class Appointment extends BaseEntity {

    ...

    @OneToMany(mappedBy = "appointment")
    private List<AppointmentUser> appointmentUsers = new ArrayList<AppointmentUser>();

    @Version
    private Long version;

 

그리고, 레포지토리는 이렇습니다.

public interface AppointmentRepository extends JpaRepository<Appointment, Long> {

...

    @Lock(LockModeType.OPTIMISTIC)
    @Query("SELECT au FROM Appointment a " +
            "JOIN a.appointmentUsers au " +
            "WHERE au.id = :appointmentUserId ")
    AppointmentUser findByIdWithOptimisticLock(Long appointmentUserId);
}

 

그리고, 서비스 계층 메서드는 이렇습니다.

@Transactional(readOnly = true)
@Service
public class AppointmentUserService {

    ...

    @Transactional
    public void updateAuthority(Long appointmentId, Long loginMemberId, Long targetMemberId) {
        Member loginMember = memberRepository.getById(loginMemberId);
        Member targetMember = memberRepository.getById(targetMemberId);
        Appointment appointment = appointmentRepository.getById(appointmentId);
        AppointmentUser loginAppointmentUser = appointmentUserRepository.getByMemberAndAppointment(loginMember, appointment);
        AppointmentUser targetAppointmentUser = appointmentUserRepository.getByMemberAndAppointment(targetMember, appointment);

        validateIsAdminMember(loginAppointmentUser.getId());
        MemberAuthority targetAuthority = targetAppointmentUser.getMemberAuthority();

        targetAppointmentUser.updateAuthority(MemberAuthority.getAnotherAuthority(targetAuthority));
        appointmentUserRepository.save(targetAppointmentUser);
    }

    private void validateIsAdminMember(Long loginAppointmentUserId) {
        if (appointmentRepository.findByIdWithOptimisticLock(loginAppointmentUserId).getMemberAuthority() != MemberAuthority.ADMIN) {
            throw new NotAdminMemberException();
        }
    }
}

위 updateAuthority 메서드 내에서 validateIsAdminMember를 호출하여, validateIsAdminMember 안에 있는 findByIdWithOptimisticLock을 통하여 Version을 올려주어서 낙관적 락을 성공적으로 구현할 수 있을 줄 알았는데 테스트를 해보니 아래와 같이 실패합니다..

 

@Test
void 동시성_테스트() throws InterruptedException {
    Logger logger = Logger.getLogger(MultiThreadTest.class.getName());

    ExecutorService executorService = Executors.newFixedThreadPool(2);
    CountDownLatch latch = new CountDownLatch(2);

    AtomicReference<Boolean> flag = new AtomicReference<>(false);

    executorService.execute(() -> {
        try {
            logger.log(Level.INFO, "첫 번째 요청 시작: member1 -> member2");
            logger.log(Level.INFO, "버전1전" + appointmentRepository.getById(1L).getVersion());
            appointmentUserService.updateAuthority(1L, 1L, 2L);
            logger.log(Level.INFO, "버전1완" + appointmentRepository.getById(1L).getVersion());
            logger.log(Level.INFO, "첫 번째 요청 완료: member1 -> member2");
        } catch (Exception e) {
            logger.log(Level.SEVERE, "첫 번째 요청 중 예외 발생", e);
            flag.set(true);
        } finally {
            latch.countDown();
        }
    });

    executorService.execute(() -> {
        try {
            logger.log(Level.INFO, "두 번째 요청 시작: member2 -> member1");
            logger.log(Level.INFO, "버전2전" + appointmentRepository.getById(1L).getVersion());
            appointmentUserService.updateAuthority(1L, 2L, 1L);
            logger.log(Level.INFO, "버전2완" + appointmentRepository.getById(1L).getVersion());
            logger.log(Level.INFO, "두 번째 요청 완료: member2 -> member1");
        } catch (Exception e) {
            logger.log(Level.SEVERE, "두 번째 요청 중 예외 발생", e);
            flag.set(true);
        } finally {
            latch.countDown();
        }
    });

    latch.await();
};

위와 같이, 두 개의 스레드에서 진행을 하였고, 각 스레드 내에서 updateAuthority()를 실행 전후에 version을 찍어보았으나, 찍힌 4개의 version 모두 0이 나왔고 또한, 동시성 제어도 안되는 상황입니다.

왜 0이 나오는걸까요.. 궁금합니다!

(컴파일 에러는 없습니다)

 

답변 2

1

한수현님의 프로필 이미지
한수현
질문자

해결되었습니다.

최상용님의 프로필 이미지
최상용
지식공유자

감사합니다!

혹시나 문제가 발생한다면 질문 남겨주세요!

0

안녕하세요, 인프런 AI 인턴입니다. 한수현님의 질문과 유사한 내용을 다룬 게시글이 있어, 도움이 될 만한 자료를 공유드립니다.

한수현님께서 낙관적 락(Optimistic Lock) 구현 중에 @Version이 증가하지 않는 문제에 부딪혔군요. 낙관적 락을 올바르게 사용하기 위해선 몇 가지 확인해야 할 사항들이 있습니다. 가장 중요한 것은, 실제 데이터베이스 엔티티에 변경사항이 발생하고, 그 변경사항이 데이터베이스에 반영될 때 @Version 필드가 올바르게 증가하는지 여부입니다.

유사 질문 링크

이 게시물에서는 낙관적 락이 무한 루프에 돌게 되는 상황과 그 해결법에 대해서 설명하고 있습니다. 비록 정확히 @Version 컬럼이 증가하지 않는 문제에 대한 직접적인 해결법을 제시하지 않지만, 낙관적 락과 관련된 동작 원리와 주의사항들을 이해하는 데에 큰 도움이 될 것입니다.

주요 포인트

  • 낙관적 락 구현 시, 엔티티의 변경 사항이 데이터베이스로 올바르게 반영되었는지 확인해야 합니다.
  • @Version 필드는 엔티티가 데이터베이스에 성공적으로 반영될 때 자동으로 증가해야 합니다. 이 과정에서 트랜잭션 관리가 올바르게 이루어져야 합니다. 예를 들어, 동일한 트랜잭션 내에서 반복적인 조회와 변경 작업이 이루어질 경우 예상치 못한 문제가 발생할 수 있습니다.
  • 테스트 과정에서는 여러 스레드를 사용하여 동시에 같은 엔티티를 수정하려고 했을 때, 각 엔티티의 버전 관리가 올바르게 이루어지지 않을 수 있으므로, 트랜잭션 경계와 격리 수준(isolation level)에 주의를 기울여야 합니다.

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