기존에는 NotificationService를 주입받아 적용했는데 서비스간의 의존성이 생기고 결합도가 높아서 이슈가 생길것이 염려되었습니다. 결국 관련해서 에러가 터지기도 했습니다. 의존성을 제거하기 위해 여러가지 방법을 찾아보았는데 EventListener가 있다는 사실을 알게 되었습니다. EventListener는 말 그대로 이벤트를 리스닝(?)하고 있다가 이벤트가 발생하면 처리하는 로직입니다. 

 

서비스간의 강한 결합, 강한 의존성을 낮출수 있는 방법입니다. 

eventlistener 알림

@TransactionalEventListener

@TransactionEventListener를 사용하면, 트랜잭션 흐름에 따라 이벤트를 제어할 수 있습니다.

@TransactionalEventListener는 4가지 옵션이 있습니다.

  • AFTER_COMMIT(default): 트랜잭션이 성공적으로 마무리(commit)이 되었을 때 이벤트를 실행합니다.
  • AFTER_ROLLBACK: 트랜잭션이 rollback이 되었을 때 이벤트를 실행합니다.
  • AFTER_COMPLETION: 트랜잭션이 마무리 되었을 때(commit or rollback) 이벤트를 실행합니다.
  • BEFORE_COMMIT: 트랜잭션의 커밋 전에 이벤트를 실행합니다.

 

문제점,

  • 이벤트 전달 시점을 트랜잭션 커밋 시점으로 설정한 경우 트랜잭션이 끝나 DB에 데이터를 저장하는 것이 불가능합니다.
    • 이때 @Transactional(propagation = Propagation.REQUIRES_NEW)으로 새로운 트랜잭션을 생성시켜 데이터를 저장하는 방식으로 문제를 해결합니다.

 

 

NotificationListener 구현 (1)

@Component
@RequiredArgsConstructor
@Slf4j
public class NotificationListener {

    private final NotificationService notificationService;

    @TransactionalEventListener
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async
    public void handleNotification(RequestNotificationDto requestNotificationDto){
        notificationService.send(
                requestNotificationDto.getReceiver(), requestNotificationDto.getSender(), requestNotificationDto.getNotificationType(),
                requestNotificationDto.getContent(), requestNotificationDto.getType(), requestNotificationDto.getTypeId(),
                requestNotificationDto.getPostId());
    }

}

RequsetDto를 따로 만들어서 Event가 발생하면 받아오게 하였습니다.

 

BeanInitializationException

위의 핸들러를 실행시키자 다음과 같이 BeanInitializationException이 발생했습니다. 

BeanInitializationException

org.springframework.beans.factory.BeanInitializationException: Failed to process @EventListener annotation on bean with name 'notificationService'; nested exception is java.lang.IllegalStateException: Maximum one parameter is allowed for event listener method: public void com.bluehair.hanghaefinalproject.sse.service.NotificationService.send(com.bluehair.hanghaefinalproject.member.entity.Member,com.bluehair.hanghaefinalproject.member.entity.Member,com.bluehair.hanghaefinalproject.sse.entity.NotificationType,java.lang.String,com.bluehair.hanghaefinalproject.sse.entity.RedirectionType,java.lang.Long,java.lang.Long) at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:157) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:974) ~[spring-beans-5.3.24.jar:5.3.24] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.6.jar:2.7.6] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.6.jar:2.7.6] at com.bluehair.hanghaefinalproject.HanghaeFinalProjectApplication.main(HanghaeFinalProjectApplication.java:13) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.6.jar:2.7.6] Caused by: java.lang.IllegalStateException: Maximum one parameter is allowed for event listener method: public void com.bluehair.hanghaefinalproject.sse.service.NotificationService.send(com.bluehair.hanghaefinalproject.member.entity.Member,com.bluehair.hanghaefinalproject.member.entity.Member,com.bluehair.hanghaefinalproject.sse.entity.NotificationType,java.lang.String,com.bluehair.hanghaefinalproject.sse.entity.RedirectionType,java.lang.Long,java.lang.Long) at org.springframework.context.event.ApplicationListenerMethodAdapter.resolveDeclaredEventTypes(ApplicationListenerMethodAdapter.java:127) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.event.ApplicationListenerMethodAdapter.<init>(ApplicationListenerMethodAdapter.java:117) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.transaction.event.TransactionalApplicationListenerMethodAdapter.<init>(TransactionalApplicationListenerMethodAdapter.java:65) ~[spring-tx-5.3.24.jar:5.3.24] at org.springframework.transaction.event.TransactionalEventListenerFactory.createApplicationListener(TransactionalEventListenerFactory.java:56) ~[spring-tx-5.3.24.jar:5.3.24] at org.springframework.context.event.EventListenerMethodProcessor.processBean(EventListenerMethodProcessor.java:200) ~[spring-context-5.3.24.jar:5.3.24] at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:154) ~[spring-context-5.3.24.jar:5.3.24] ... 15 common frames omitted
Maximum one parameter is allowed for event listener method

EventListener는 최대 하나의 parameter만을 가진다고 합니다. 보니까 Singleton으로 설정되어 있어서 그렇습니다. 다시 코드를 변경해서 parameter 하나만 보내주는 것으로 notificationService.send를 변경했습니다.

 

NotificationListener 구현 (2)

아아... 그게 아니었습니다. 

@EventListener 어노테이션을 2군데 붙여놔서 그런거였습니다.

 

HandleNotification 메서드에만 붙어놔야 했는데 NotificationService안에도 붙여놔서 send 메서드에는 파라미터가 여러개여서 문제가 생긴거였습니다. send 메서드 위에 있던 @EventListener 어노테이션을 제거하니 제대로 작동이 되었습니다.

 

Service 구현

여러가지 서비스가 알림서비스를 이용하고 있었는데 게시글 좋아요 메서드 관련한 코드입니다. 

 

이렇게 고쳤습니다.

우선 ApplicationEventPublisher를 가져옵니다.

private final ApplicationEventPublisher eventPublisher;
@Transactional
public PostLikeDto postLike(Long postId, Member member){
    Post postliked = postRepository.findById(postId)
            .orElseThrow(()-> new NotFoundException(LIKE, SERVICE, POST_NOT_FOUND, "Post ID : " + postId)
            );
    PostLikeCompositeKey postLikeCompositeKey
            = new PostLikeCompositeKey(member.getId(), postliked.getId());
    boolean likecheck;
    Optional<PostLike> postLike= postLikeRepository.findByPostLikedIdAndMemberId(postliked.getId(), member.getId());

    if(postLike.isPresent()){
        postLikeRepository.deleteById(postLikeCompositeKey);
        postliked.unLike();
        postRepository.save(postliked);
        likecheck = false;

        return new PostLikeDto(likecheck, postliked.getLikeCount());
    }

    postLikeRepository.save(new PostLike(postLikeCompositeKey, member, postliked));
    likecheck=true;
    postliked.like();
    postRepository.save(postliked);


    Member postMember = memberRepository.findByNickname(postliked.getNickname())
            .orElseThrow(() -> new NotFoundException(COMMENT, SERVICE, MEMBER_NOT_FOUND, "Nickname : " + postliked.getNickname()));
    if(!postMember.getNickname().equals(member.getNickname())) {
        String content = postliked.getTitle() + "을(를) " + member.getNickname() + "님이 좋아합니다.";
        notify(postMember, member, NotificationType.POST_LIKED, content, RedirectionType.detail, postId, null);
    }

    return new PostLikeDto(likecheck, postliked.getLikeCount());

}

 

notify 메서드를 만들어서 event를 publish 합니다.

 

private void notify(Member postMember, Member sender, NotificationType notificationType,
                        String content, RedirectionType type, Long typeId, Long postId){
    eventPublisher.publishEvent(new RequestNotificationDto(postMember,sender, notificationType,content,type, typeId, postId));

}

 

 

 

이제 notify가 실행되면서 Eventlistener가 NotificationService의 send 메서드를 실행해줍니다.

 

@Component
@RequiredArgsConstructor
@Slf4j
public class NotificationListener {

    private final NotificationService notificationService;

    @TransactionalEventListener
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async
    public void handleNotification(RequestNotificationDto requestNotificationDto){
        notificationService.send(requestNotificationDto.getReceiver(), requestNotificationDto.getSender(),requestNotificationDto.getNotificationType(),
                requestNotificationDto.getContent(), requestNotificationDto.getType(), requestNotificationDto.getTypeId(), requestNotificationDto.getPostId());
        log.info("EventListener has been operated. Sender Id: " + requestNotificationDto.getSender().getId() + "NotificationType: " +requestNotificationDto.getNotificationType());
    }

}

 

참고 문서

 

알림 기능을 구현해보자 - SSE(Server-Sent-Events)!

시작하기에 앞서 이번에 개발을 진행하면서 알림에 대한 요구사항을 만족시켜야하는 상황이 발생했다. 여기서 말하는 알림이 무엇인지 자세하게 살펴보자. A라는 사람이 스터디를 생성했고 B라

gilssang97.tistory.com

 

읽지 않은 알림 갯수 반환 하기

FE로부터 요청이 왔습니다. 읽지 않은 알림의 갯수를 보내달라고 합니다. API를 새로 파서 보내기로 결정 했습니다. Event 발생할때 마다 넣어달라고 처음에 요청하셨는데 Transactional 이슈가 발생할 것 같아서 우선 API를 따로 만들어서 구현하기로 했습니다.

 

DB에서 바로 몇개인지 세서 보내기 위해서 Query를 직접 작성하기로 했습니다.

 

JPQL

@Transactional
@Query("SELECT COUNT(n) from Notification n where n.receiver.nickname =:nickname and n.isRead = false")
Long countUnreadNotifications(String nickname);

Count를 써서 조건에 해당하는 값들을 셌습니다.

 

해당 nickname을 가지고 있는 수신자(receiver)를 가지고 있는 notification의 isRead 칼럼이 false이면 count를 합니다.

 

생각보다 간단해서 다행이었습니다.

querydsl를 써보려다 더 복잡한 거 같아 간단한 JPQL을 사용해서 읽지않은 알림 갯수를 가져오는 메서드를 구현했습니다.

결국 SSE 관련 버그를 처리했습니다. 

 

TIL SSE 에러 트러블슈팅 230125

아직 해결중입니다. 버그가 쉽게 해결되지 않아서 스트레스가 밀려오지만 또 면접에서 할말 생겼다는 생각이 들어서 긍정적으로 생각하기로 했습니다. 집에서 공부하니까 집중이 잘 안되는 거

pizzathedeveloper.tistory.com

 

서치를 하다가 비동기, 동기 개념에 대해 더 깊게 파고들었고 결국 해결했습니다. 허탈하긴하지만 @Async 어노테이션 하나로 해결했습니다. 컨벤션 관련 리팩토링 중이여서 푸시를 못하고 있다가 오후에 시도했는데 @Async 하나 붙였더니 connection leak 이 발생하거나 connection pool이 다 차는 에러가 발생하지 않고 정상적으로 잘 작동이 되었습니다. 알림전송도 잘 됩니다.

@Async

비동기처리를 위해서 @Async 를 사용합니다. 사용하기 위해서는 @Enableasync를 함께 추가해줘야합니다. 

 

@Async // 비동기 처리를 위한 어노테이션
@Transactional
public void send(Member receiver, Member sender, NotificationType notificationType, String content, RedirectionType type, Long typeId, Long postId) {
    Notification notification = notificationRepository.save(new Notification(receiver, notificationType, content, type, typeId, postId, sender));
    String memberId = String.valueOf(receiver.getId());

    Map<String, SseEmitter> sseEmitters = emitterRepository.findAllEmitterStartWithByMemberId(memberId);
    sseEmitters.forEach(
            (key, emitter) -> {
                emitterRepository.saveEventCache(key, notification);
                sendToClient(emitter, key, SSE_MAPPER.NotificationtoResponseNotificationDto(notification));
            }
    );
}

 

아래와 같이 프로젝트Application에 @EnableAsync를 달아줍니다.

 

@EnableJpaAuditing
@EnableAsync
@SpringBootApplication
public class HanghaeFinalProjectApplication {
    public static void main(String[] args) {
        SpringApplication.run(HanghaeFinalProjectApplication.class, args);
    }
}

@Async를 사용하기 위해서는 public이어야 하며 self-invocation이면 안됩니다. 

 

우선은 @Async를 사용하면 기본 설정으로 SimpleAsyncTaskExecutor를 사용하게 됩니다. 커스터마이징 하고 싶으면 Async 설정 클래스를 만들어서 설정할 수 있는데 이는 내일 구현할 예정입니다. 일단 급한 불을 끄는데 집중했습니다. 

참조 블로그 (https://steady-coding.tistory.com/611)

 

 

동기 vs. 비동기

  • 동기(synchronous): 요청과 결과가 동시에 일어난다는 약속; 요청한 자리에서 결과가 주어져야 함
    • A작업이 모두 진행 될 때까지 B작업은 대기해야함
  • 비동기(Asynchronous): 요청과 결과가 동시에 일어나지 않을거라는 약속; 
    • A작업이 시작하면 동시에 B작업이 실행되며, A작업은 결과값이 나오는 대로 출력된다

 

SSE 관련 더 공부할 부분

ApplicationEventPublisher를 사용해서 서비스간의 의존성을 낮추는 방법에 대해 알게 되었습니다. EventListner를 사용해서 하는 방법이 있다고 해서 연구해보려고 합니다. 무래도 다른 Service안에 알림 Service를 넣다보니 의존성 문제가 있다고 여겨져서 방법을 찾고 있었기 때문입니다.

 

 

 

추가로 공부한 것

 

[Spring JPA] Entity, DTO

Entity, DTO 개념, DTO 사용법

velog.io

각자 파트를 맡아서 개발을 하다보니 컨벤션 준수가 되지 않거나 일관성이 없는 부분들에 대해서 팀장님이 리팩토링을 진행중입니다. DTO 관련해서 링크를 첨부해주셔서 읽어보려고 합니다. Mapper 사용할때 Dto를 참조하는 안티패턴이 발견되었습니다. 여러사람이 같은 프로젝트를 하니까 좋은점은 check-balance가 된다는 점입니다. 내공있는 조원들의 경험을 레버리지 할 수 있어서 좋습니다.

 

 

 

아직 해결중입니다. 버그가 쉽게 해결되지 않아서 스트레스가 밀려오지만 또 면접에서 할말 생겼다는 생각이 들어서 긍정적으로 생각하기로 했습니다. 집에서 공부하니까 집중이 잘 안되는 거 같아 나가려다 영하 17도를 보고 다시 담요를 여몄습니다. 집 세탁기가 얼었습니다. 다행히 보일러는 아직 살아있네요. 

 

발생한 버그에 대한 로그는 아래와 같습니다.

Connection leak detection triggered

 Connection leak detection triggered for com.mysql.cj.jdbc.ConnectionImpl@

HikariPool에 대해서 나오고 있어서 Hikari가 문젠가 하고 먼저 들여다 보았습니다.

 

HikariCP DeadLock

잠재적인 원인에 대해서 공부를 하기로 했습니다. 

 

HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그

{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할

techblog.woowahan.com

읽어보니 저희 오류랑은 조금은 다릅니다.

 

 

 

제가 다시 눈여겨본 로그는 다음입니다:

Async request timed out

 

무시해도 된다는 블로그 글을 찾긴 했습니다만...(https://jsonobject.tistory.com/558)  궁금해서 찾아봤습니다.

 

디버깅은 계속됩니다....

'TIL' 카테고리의 다른 글

TIL @EventListener 알림 기능 강한 결합 제거 230127  (0) 2023.01.28
TIL @Async 비동기 동기 230126  (0) 2023.01.27
TIL SSE 에러 230124  (0) 2023.01.25
TIL 설연휴 월요일 230123  (0) 2023.01.23
TIL 설날 2023 230122  (0) 2023.01.23

알림관련해서 성공한 줄 알았는데 아니었습니다. FE와 연결 시 발생한 SSE 관련 에러는 다음과 같습니다. 

 

1. subscribe  성공 후, 알림 2~3번 수신하면 서버가 멈추는 현상 -> DB Connection Pool이 다 차서 connection leack 이 발생

2. open-in-view 를 false로 설정하면 된다길래 (https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/) 설정했더니, transaction에 문제가 생겨 다른 service들 기능이 안되고 알림만 되는 현상

 

 

일단 오류가 발생한 것을 확인했습니다.

 

또 FE로부터 읽지않은알림 갯수를 보내달라는 요청을 받았는데 두 가지 안이 있습니다. 

FE에서 제안한 방법은

 

- 이벤트가 발생해서 알림을 보내줄 때 함께 읽지않은알림 갯수를 보내주기

 

입니다.

 

저의 생각은

 

- API를 새로 파서 unread notification count를 따로 보내주는 겁니다. 

 

 

여러 깃헙을 찾아보고 구글링해보면서 방법을 찾고 있습니다.

 

SSE가 자료도 많아서 간단할 줄 알았는데 쉽지 않네요. 

'TIL' 카테고리의 다른 글

TIL @Async 비동기 동기 230126  (0) 2023.01.27
TIL SSE 에러 트러블슈팅 230125  (0) 2023.01.26
TIL 설연휴 월요일 230123  (0) 2023.01.23
TIL 설날 2023 230122  (0) 2023.01.23
TIL 최종 프로젝트 중간 발표 230121  (0) 2023.01.23

오늘은 설연휴 첫날입니다. 게더에 접속했는데 생각보다 사람들이 많이 없어서 놀랐어요. 공부를 좀 해야겠습니다. 이제 정말 3주도 안남았거든요. 하차를 많이 한다고 했는데 하차를 할 이유는 이제 없어졌습니다. 이제 이 부트캠프에 투자한 돈과 시간이 가치가 있었는지를 검증할 일만 남았네요. 

 

내년 설연휴에는 어떤 모습으로 TIL을 쓰고 있을지 궁금해지네요. 인생이 5G로 달리고 있는 느낌입니다.

아침부터 차례지내고 친척들과 담소를 나누니 피곤해서 뻗었습니다. 저녁에는 오랜만에 친구들과의 약속이 있어서 나갔다 왔습니다. 대학교친구들인데 어느새 저희가 만난지도 10년이 다되어 간다고 하며 세월이 너무 빠르다고 했습니다. 제가 부트캠프 한다고 작년 가을에 말했었는데 그것도 이제 3주도 안남았습니다. 

 

근데 재밌는건 다른 친구들도 진로 변경에 대한 고민을 하고 있다는 거였습니다. 지금 나이가 과도기인거 같아요. 다들 뭐든 잘 할 수 있어서 더욱 더 고민이 되는 것 같습니다. 하나만 잘하는 게 아니라 여러개를 잘하는데 여러개에 흥미가 있어서 여러가지를 도전할 수 있는 상황인거에요. 지금 저희 세대는 평균 수명이 100세가 넘어가서 앞으로 최소 80년은 더 살아야하고 직업을 3~4개 가질 거라고 합니다. 저는 이미 하나는 클리어 했고 2개째 입니다. 고용되는 직업으로서는 그렇습니다. 

 

웹개발자는 전세계에 내가 만든 것을 선보일 수 있다는 점에서 더 재미가 있습니다. 디지털 세상에 내 영토를 만드는 것이라고 생각하면 개발자는 매력적인 직업입니다.

 

 

 

중간발표하면서 느낀건 이 하나의 프로젝트로 면접할 때 할말은 많겠다 입니다.

예상질문 잘 정리해서 답변 잘 할 수 있도록 준비해야겠습니다.

드디어 내일이 중간 발표날입니다. 조금은 해이해진 요즘인데 프론트로부터 알림 구현에 대해 수정 요청이 들어왔습니다. 제가 요청받은 사항은 아래와 같습니다.

 

  1. redirect 되는 페이지의 정보를 게시물 타입, 게시물 아이디로 보내주기
  2. 알림에 알림을 발신하는 멤버의 정보(닉네임, 프로필사진)을 추가
  3. 알림 목록 조회시 최신순으로 조회

 

1, 2번을 하고 실행을 눌렀는데 어제와 같은 에러가 발생했습니다. IP 주소를 확인하니 그새 또 변경이 되었더라고요. 제가 무슨 해커도 아니고 맨날 IP 주소가 변경이 되네요. 고정 IP 신청을 하면 된다는데 일단은 버텨봅니다.

 

 

비관적 락 pessimistic Lock

@Transactional
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({@QueryHint(name = "javax.persistence.lock.timeout", value ="3000")})
Optional<PostLike> findByPostLikedIdAndMemberId(Long postLikedId, Long memberId);

이틀 전부터 있던 버그는 위와 같이 비관적 락을 걸어줘서 바꿨습니다. 

 

TIL 동시성 문제? 나의 첫 버그 230118

동시성 문제로 추정되는 오류가 발생했다. 게시글 1에서는 좋아요가 잘 반영이 되는데 게시글 2부터 안된다. 이럴수가. 뭐가 문제냐. 여러가지 자료를 찾아보고 있는데 이런걸 바로 버그라고 하

pizzathedeveloper.tistory.com

비관적 락(Pessimistic Lock)을 사용하는 이유는 아래와 같습니다.

 

1. 트랜잭션이 충돌한다고 가정하고 락을 사용

2. DBMS의 락 기능을 사용

3. 데이터 수정 시 즉시 트랜잭션 충돌 여부를 확인 가능

 

비관적 락에는 두가지 종류가 있습니다:

  • Exclusive Lock은 데이터를 변경하고자 할 때 사용되며, 트랜잭션이 완료될 때까지 유지되어 해당 Lock이 해제될 때까지 다른 트랜잭션은 해당 데이터에 읽기를 포함하여 접근을 할 수 없습니다.
  • Shared Lock은 다른 사용자가 동시에 데이터를 읽을 수는 있지만 Write는 할 수 없습니다.

PESSIMISTIC_WRITE Exclusive Lock을 걸고, 데이터를 읽거나, 업데이트하거나, 삭제하는 것을 막습니다.

 

JPA는 PESSIMISTIC_WRITE 이외에도 

 

  • PESSIMISTIC_READ - Shared Lock을 걸고 데이터가 업데이트 되거나 삭제되지 않도록 한다
  • PESSIMISTIC_FORCE_INCREMENT - PESSIMISTIC_WRITE와 유사하고 ENTITY의 버전 속성을 추가로 증가시킨다.

 

EXCEPTION의 경우에는 PersistenceException(락을 가져오는데 실패하면 예외 발생), LockTimeoutException(락을 기다리다가 설정해놓은 wait time을 지났을 때 예외 발생) 이 있습니다.

 

저는 좋아요 실행 시 충돌이 발생한 것을 이미 봤기 때문에 충돌이 발생한다는 것을 가정하는 비관적 락을 걸었습니다. Lock이 길어지면 Latency가 길어지기 때문에, @QueryHints를 사용하여 Lock에 3초 타임 아웃을 적용했습니다.

 

 

 

결과는?

잘 됩니다.

좋아요를 여러번 연속해서 처리할 때 true 값만 반환이 되던(아마 충돌이 일어나서 실행이 되지 못했겠던) 현상이 사라지고 정상적으로 잘 작동이 됩니다.

 

 

 

프론트 요청 처리하기

새로운 칼럼을 추가하려고 하니 이미 DB가 서버에 올라가 있어서 다 날리고 새로 올리는 작업을 여러번 반복해야했습니다. 저만 칼럼을 수정하고 추가하고 있던게 아니었거든요.. API 명세가 처음이랑은 전혀 다른 모습이 되었습니다. 내일이 중간 발표인데 중간 발표가 끝나면 한번 정리하는 시간을 가져야 할 것 같습니다.

 

이제는 CRUD 수정하는 정도는 금방 해버립니다. 실제로 위의 1,2,3, 요청은 2 시간도 안걸려서 해결해버렸답니다. JPA가 정말 편해요.. 스프링은 사랑입니다😘

 

 

아래와 같이 SQL Error 가 발생했습니다.

SQL 에러

기존에 있던 Column 명을 변경해서 에러가 생긴 줄 알았는데 SQL에 등록해놓았던 ip 주소가 변경되어서 그런거였습니다.

ip 주소는 고정 ip를 신청하지 않는 이상 변경 될 수 있다고 하네요. 

+ Recent posts