중간테이블에 대한 낙관적 락 적용법
207
작성한 질문수 3
현재 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();
}
}
<로직 설명>
하나의 약속 내에 멤버 두명이 존재
각 멤버들은 ADMIN or NORMAL 권한을 갖고 있음.
두명 다 약속 내 에서 ADMIN 권한을 갖고 있다는 상황을 가정. (ADMIN은 AppointmentUser 엔티티의 MemberAuthority 라는 필드의 Enum 값입니다.)
두명이 서로를 동시에 ADMIN에서 NORMAL로 권한을 박탈하는 경우, 하나의 약속 안에 ADMIN인 사람이 없어지는 예외적인 문제 상황이 발생함.
그래서 @Version을 AppointmentUser 엔티티의 필드로 등록하여 해결하려고 했으나..
사용자 A와 B가 있다고 할 때 service 코드 내의 validateIsAdminMember 메서드를 통해 상대방의 권한을 박탈하려는 유저(본인)가 ADMIN인지 검증하여 ADMIN이 맞다면 박탈하고, ADMIN이 아니라면, 예외를 던지게 끔 하는 로직에서,
레포지토리 내의 findByIdWithOptimisticLock 메서드를 동시에 접근했을때 Version 필드를 통해 동시성 문제를 제어할 수 있다고 생각했으나..
validateIsAdminMember로 검증하는 A와 B의 AppointmentUser 엔티티는 서로 다른 객체(데이터)이기 때문에 서로 다른 테이블의 Version 값을 변경하기 때문에 동시성 보장이 안됨..
그래서 Appointment에 Version필드를 넣어주려 했지만, Version값은 해당 테이블에 변화가 생겨야 변한다.
하지만 로직상, AppointmentUser(중간테이블)에 변화가 생기는게 맞다...
위와 같은 중간 테이블 사용으로 인한 문제가 발생하였을 때 어떻게 강의자님이시라면 어떻게 해결하실지 궁금합니다!
답변 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





