Spring

[Spring] 캐시 데이터 동기화 문제

worldcenter 2024. 11. 27. 20:07

회원정보 수정 시 데이터 동기화 문제

앞서 로그인 및 회원정보 조회시 Spring 캐시를 사용하도록 설정하였습니다.

@Service
@RequiredArgsConstructor
@Transactional
public class MemberService {

    private final MemberRepository memberRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    public SignupResponse signup(SignupRequest req) {

        String phone = req.phone();
        String nickname = req.nickname();
        String password = passwordEncoder.encode(req.password());
        MemberRole role = req.role();

        Member createMember = Member.createMember(phone, nickname, password, role);

        if (memberRepository.findByPhone(createMember.getPhone()).isPresent()) {
            throw new MemberException("이미 가입한 회원입니다.");
        }

        memberRepository.save(createMember);

        return SignupResponse.from(createMember);

    }

    @CachePut(value = "member", key = "#session.id")
    public LoginResponse login(HttpSession session, LoginRequest req) {

        String phone = req.phone();
        String rawPassword = req.password();

        Member member = memberRepository.findByPhone(phone).orElseThrow(() ->
                new MemberException("회원정보가 존재하지 않습니다.")
        );

        if (!passwordEncoder.matches(rawPassword, member.getPassword())) {
            throw new MemberException("패스워드가 일치하지 않습니다.");
        }

        return LoginResponse.from(member);

    }

    public UpdateMemberResponse updateMember(UpdateMemberRequest req, Member member) {

        String nickname = req.nickname();
        String password = passwordEncoder.encode(req.password());

        Member updateMember = member.updateMember(nickname, password);
        memberRepository.save(updateMember);
        return UpdateMemberResponse.from(updateMember);

    }

    public void deleteMember(DeleteMemberRequest req, Member member) {

        String rawPassword = req.password();

        if (!passwordEncoder.matches(rawPassword, member.getPassword())) {
            throw new MemberException("패스워드가 일치하지 않습니다.");
        }

        memberRepository.deleteById(member.getId());
    }

    @Transactional(readOnly = true)
    public FindMemberResponse findMember(Member member) {

        Member findMember = memberRepository.findByPhone(member.getPhone()).orElseThrow(() ->
                new MemberException("회원정보가 존재하지 않습니다.")
        );

        return FindMemberResponse.from(findMember);
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "member", key = "#member.id")
    public FindMemberResponse findMemberWithCache(Member member) {

        Member findMember = memberRepository.findByPhone(member.getPhone()).orElseThrow(() ->
                new MemberException("회원정보가 존재하지 않습니다.")
        );

        return FindMemberResponse.from(findMember);
    }
}

 

그 다음 회원정보를 조회하면 로그인할 때 클라이언트 쿠키에 저장되어있던 세션ID를 가지고 캐시 저장소에서 회원정보를 불러올 수 있습니다.

 

만약 이 상황에서 회원정보를 수정하게 되면 정상적으로 수정된 회원정보가 조회될까요? 먼저 회원정보를 수정해보겠습니다.

DB에도 정상적으로 회원정보가 수정되었음을 확인했습니다.

 

이제 회원정보를 조회해보면 여전히 수정 전 회원정보가 조회되는 것을 볼 수 있습니다. 즉, DB와 캐시 간의 데이터 동기화가 맞지 않는 문제가 발생합니다. 이를 어떻게 해결할 수 있을까요?

 

 

@CacheEvict 를 이용한 동기화 문제 해결

CacheEvict란 캐시 정보를 메모리에서 '삭제'하는 기능 입니다. 이를 이용하여 회원정보 수정 시 기존 캐시 저장소에 저장되어 있던 회원 정보를 삭제하여 DB에서 변경된 데이터와의 동기화 문제를 원천 차단시킵니다.

@Service
@RequiredArgsConstructor
@Transactional
public class MemberService {

    private final MemberRepository memberRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    ...
    
    @CacheEvict(value = "member", key = "#member.id", beforeInvocation = false)
    public UpdateMemberResponse updateMember(UpdateMemberRequest req, Member member) {

        String nickname = req.nickname();
        String password = passwordEncoder.encode(req.password());

        Member updateMember = member.updateMember(nickname, password);
        memberRepository.save(updateMember);
        return UpdateMemberResponse.from(updateMember);

    }

    ...
}