inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

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

소스코드

중간테이블에 대한 낙관적 락 적용법

207

한수현

작성한 질문수 3

0

현재 Member 테이블과 Appointment 테이블이 존재하는데, N:N 관계이기 때문에 아래와 같이 AppointmentUser라는 중간 테이블이 존재합니다.

@Entity
@Table(name = "appointment_and_user")
public class AppointmentUser extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "apppointment_id", nullable = false)
    private Appointment appointment;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id", nullable = false)
    private Member member;

    @Enumerated(value = EnumType.STRING)
    @Column(name = "member_authority", nullable = false)
    private MemberAuthority memberAuthority;

    @Version
    private Long version;

하나의 AppointmentUser 테이블은 약속과 멤버의 id를 하나씩 가집니다.

 

여기서 레포지토리의 코드는 이러합니다.

public interface AppointmentUserRepository extends JpaRepository<AppointmentUser, Long> {

...

    @Lock(LockModeType.OPTIMISTIC)
    @Query("select au from AppointmentUser au where au.id = :id")
    AppointmentUser findByIdWithOptimisticLock(Long id);

findByIdWithOptimistic 메서드를 통하여 특정 유저-약속 테이블의 데이터를 통해 AppointmentUser 객체를 반환합니다.

 

서비스 계층의 코드는 아래와 같습니다.

@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);
    appointment.changeTitle("asd");

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

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

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

 

<로직 설명>

위와 같은 중간 테이블 사용으로 인한 문제가 발생하였을 때 어떻게 강의자님이시라면 어떻게 해결하실지 궁금합니다!

 

java spring 동시성

답변 2

0

한수현

혹시 별도의 테이블로 Lock을 제어한다는게 어떠한 의미인지 여쭈어 봐도 될까요 ??..

1

최상용

AppointmentLock 과 같은 별도의 테이블을 생성한 이후 appointmentId 를 유니크로 키로 생성합니다.

그 후 역할을 변경하는 메소드 호출하기 전에 AppointmentLock 에 appointmentId 를 가진 데이터를 삽입하여 락을 획득한 후 역할을 모두 변경하였다면 appointmentId 가진 데이터를 삭제하여 락을 해제하는 방법입니다!

0

최상용

한수현님 안녕하세요.
단순히 해결만을 위한 것이라면 Version 필드를 Appointment 에 넣고 updatedAt 을 넣어서 변경을 발생시킬 수 있을 것 같습니다.

다만 올바른 해결방법으로는 안보이며 근본적으로 updateAuthority 메소드에 중복으로 접근해서는 안되보입니다.
이를위해 별도의 테이블로 Lock 을 제어할 수도 있으며, redis 를 사용하여 락을 제어하여 updateAuthority 메소드에 접근자체를 막을수도 있을것 같습니다.

정확히 어떤프로젝트이며 어떤 특성을 가진것인지 100% 이해하지 못한 상황에서 남기는 답변이므로 더 나은 방법이 존재할 수 있습니다.

감사합니다.

레디선 테스트코드에서 채널이름은 없어도 되는건가요?

0

41

2

낙관적 락을 사용할 떄 차이점

0

118

2

동시성 검증 코드에 관한 문의

0

91

2

단일연산

0

69

2

낙관적락vs. 레디스락

0

106

2

안녕하세요. 레디슨 질문있습니다..!!

0

66

2

@Lock(OPTIMISTIC)이 필요한 이유

0

89

2

get_lock 의 timeout이 3000초 이던데 너무 긴거 아닌가요?

0

128

2

DataSource Hikari 사용 이유

0

145

2

saveAndFlush 사용 이유 문의

0

112

3

비관적 락 VS 네임드 락

0

159

3

application.yaml 에 redis 정보

0

99

2

왜 클래스 이름에 Facade 가 붙나요?

0

181

2

@Transactional 으로 인한 동시성 문제 발생 원인이 궁금합니다.

0

218

2

@modifying 이용한 동시성 제어

0

168

2

DB락과 분산락

0

260

2

NamedLock 테스트 실패

0

186

2

테스트에서 트랜잭션 어노테이션 질문 있습니다.

0

171

2

optimistic Lock 재시도 질문입니다.

0

229

2

낙관적 락 테스트 실패

0

239

2

오류?

0

1625

4

LettureLockStockFacadeTest에서 오류가 발생합니다.

1

268

2

Pessimistic Lock 전체 테스트 오류 문의

0

358

3

비관적 락 vs 레디스(Lettuce)락 비교 관련 질문

0

459

2