Skip to content

Commit

Permalink
feat: Notification 생성 시 새로 save하는 경우 트랜잭션 분리한다
Browse files Browse the repository at this point in the history
Issue: #192
  • Loading branch information
melonturtle committed Mar 13, 2024
1 parent 061f4f0 commit 6e2b4de
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
Expand Down Expand Up @@ -45,7 +46,11 @@ private Member findMember(Long memberId) {
}

//== notify ==//
@Transactional

/**
* Notification 생성 트랜잭션 분리하기 위해 새로운 트랜잭션에서 실행됨
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void notifyVoteResult(Topic topic) {
// voters' notifications
List<Member> voters = memberRepository.findAllListeningVoteResultAndVotedTopicId(topic.getId());
Expand Down Expand Up @@ -76,7 +81,10 @@ private void addAuthorsNotificationIfAuthorListeningVoteResult(Topic topic, List
}
}

@Transactional
/**
* Notification 생성 트랜잭션 분리하기 위해 새로운 트랜잭션에서 실행됨
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void notifyLikeInComment(LikedComment likedComment) {
if (shouldNotifyLikeInComment(likedComment)) {
LikeInCommentNotification notification = new LikeInCommentNotification(likedComment.getComment());
Expand All @@ -97,7 +105,10 @@ private boolean shouldNotifyLikeInComment(LikedComment likedComment) {
return !likerIsWriter && writerListenLikeInComment;
}

@Transactional
/**
* Notification 생성 트랜잭션 분리하기 위해 새로운 트랜잭션에서 실행됨
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void notifyCommentOnTopic(Comment comment) {
if (shouldNotifyCommentOnTopic(comment)) {
CommentOnTopicNotification notification = new CommentOnTopicNotification(comment);
Expand All @@ -117,7 +128,10 @@ private boolean shouldNotifyCommentOnTopic(Comment comment) {
return commenterIsNotAuthor && authorListenCommentOnTopic;
}

@Transactional
/**
* Notification 생성 트랜잭션 분리하기 위해 새로운 트랜잭션에서 실행됨
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void notifyVoteCountOnTopic(Topic topic) {
if (shouldNotifyVoteCountForTopic(topic)) {
VoteCountOnTopicNotification notification = new VoteCountOnTopicNotification(topic);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package life.offonoff.ab.application.notification;

import jakarta.persistence.EntityManager;
import life.offonoff.ab.domain.TestEntityUtil.TestMember;
import life.offonoff.ab.domain.comment.Comment;
import life.offonoff.ab.domain.comment.LikedComment;
import life.offonoff.ab.domain.member.Member;
Expand All @@ -17,18 +16,18 @@
import life.offonoff.ab.web.response.notification.NotificationResponse;
import life.offonoff.ab.web.response.notification.message.CommentOnTopicNotificationMessage;
import life.offonoff.ab.web.response.notification.message.VoteCountOnTopicNotificationMessage;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.transaction.TestTransaction;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static life.offonoff.ab.domain.TestEntityUtil.*;
import static life.offonoff.ab.domain.TestEntityUtil.createRandomMember;
import static life.offonoff.ab.domain.TestEntityUtil.createRandomTopic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

Expand All @@ -48,6 +47,29 @@ public class NotificationServiceIntegrationTest {
@Autowired
EntityManager em;

@Autowired
private JdbcTemplate jdbcTemplate;
private static final String[] touchedTables
= {"notification", "comment", "topic", "member"};

@BeforeEach
void setUp() {
JdbcTestUtils.deleteFromTables(
jdbcTemplate, touchedTables);
}

@AfterEach
void tearDown() {
TestTransaction.end();
TestTransaction.start();

// 남아있는 데이터 삭제
JdbcTestUtils.deleteFromTables(
jdbcTemplate, touchedTables);
TestTransaction.flagForCommit();
TestTransaction.end();
}

@Test
@DisplayName("VoteCountOnTopic 알림 생성한다.")
void create_VoteCountOnTopicNotification() {
Expand All @@ -61,6 +83,8 @@ void create_VoteCountOnTopicNotification() {
.build().buildTopic();
em.persist(topic);

commitTestTransactionAndRestart();

notificationService.notifyVoteCountOnTopic(topic);

// when
Expand All @@ -84,12 +108,15 @@ void delete_VoteResultNotification_when_topic_deleted() {
Topic topic = createRandomTopic();
em.persist(topic);

commitTestTransactionAndRestart();

// notification
VoteResultNotification voteResultNotification = VoteResultNotification.createForAuthor(topic);
em.persist(voteResultNotification);

// when
topicRepository.delete(topic);
commitTestTransactionAndRestart();

// then
assertThat(notificationService.findAllByReceiverId(author.getId())).isEmpty();
Expand All @@ -110,13 +137,17 @@ void delete_VoteCountOnTopicNotification_when_topic_deleted() {
Topic topic = createRandomTopic();
em.persist(topic);

commitTestTransactionAndRestart();

// notification
VoteCountOnTopicNotification notification = new VoteCountOnTopicNotification(topic);
em.persist(notification);

// when
topicRepository.delete(topic);

commitTestTransactionAndRestart();

// then
assertThat(notificationService.findAllByReceiverId(author.getId())).isEmpty();
}
Expand Down Expand Up @@ -144,6 +175,8 @@ void create_CommentOnTopicNotification_when_commented_by_voter() {
Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content");
em.persist(comment);

commitTestTransactionAndRestart();

notificationService.notifyCommentOnTopic(comment);

// when
Expand Down Expand Up @@ -171,6 +204,8 @@ void does_not_create_CommentOnTopicNotification_when_commented_by_author() {
Comment comment = Comment.createAuthorsComment(author, topic, "content");
em.persist(comment);

commitTestTransactionAndRestart();

notificationService.notifyCommentOnTopic(comment);

// when
Expand Down Expand Up @@ -204,11 +239,14 @@ void delete_CommentOnTopicNotification_when_comment_deleted() {
Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content");
em.persist(comment);

commitTestTransactionAndRestart();

notificationService.notifyCommentOnTopic(comment);

// when
comment.remove();
em.remove(comment);
em.remove(em.merge(comment));
commitTestTransactionAndRestart();

// then
assertThat(notificationService.findAllByReceiverId(author.getId())).isEmpty();
Expand Down Expand Up @@ -240,10 +278,13 @@ void delete_CommentOnTopicNotification_when_topic_deleted(){
Comment comment = new Comment(commenter, topic, ChoiceOption.CHOICE_A, "content");
em.persist(comment);

commitTestTransactionAndRestart();

notificationService.notifyCommentOnTopic(comment);

// when
em.remove(topic);
em.remove(em.merge(topic));

commitTestTransactionAndRestart();

// then
List<NotificationResponse> responses = notificationService.findAllByReceiverId(author.getId());
Expand Down Expand Up @@ -286,6 +327,8 @@ void create_LikeInCommentNotification_when_comment_liked_by_liker() {

LikedComment likedComment = new LikedComment(liker, comment);

commitTestTransactionAndRestart();

notificationService.notifyLikeInComment(likedComment);

// when
Expand Down Expand Up @@ -325,6 +368,8 @@ void does_not_create_LikeInCommentNotification_when_liker_is_writer() {

LikedComment likedComment = new LikedComment(commenter, comment);

commitTestTransactionAndRestart();

notificationService.notifyLikeInComment(likedComment);

// when
Expand Down Expand Up @@ -370,11 +415,14 @@ void delete_LikeInCommentNotification_when_comment_deleted() {

LikedComment likedComment = new LikedComment(commenter, comment);

commitTestTransactionAndRestart();

notificationService.notifyLikeInComment(likedComment);

// when
comment.remove();
em.remove(comment);
em.remove(em.merge(comment));
commitTestTransactionAndRestart();

// then
List<NotificationResponse> responses = notificationService.findAllByReceiverId(commenter.getId());
Expand Down Expand Up @@ -416,10 +464,13 @@ void delete_LikeInCommentNotification_when_topic_deleted() {

LikedComment likedComment = new LikedComment(commenter, comment);

commitTestTransactionAndRestart();

notificationService.notifyLikeInComment(likedComment);

// when
em.remove(topic);
em.remove(em.merge(topic));
commitTestTransactionAndRestart();

// then
List<NotificationResponse> responses = notificationService.findAllByReceiverId(commenter.getId());
Expand Down Expand Up @@ -466,4 +517,14 @@ void read_notification_exception_read_by_non_receiver() {
assertThatThrownBy(() -> notificationService.readNotification(nonReceiver.getId(), notification.getId()))
.isInstanceOf(IllegalReceiverException.class);
}

private void commitTestTransactionAndRestart() {
// 현재 영속성 컨텍스트의 엔티티 상태 정보 db에 반영.
// 실제 운영 중 notificationService는 엔티티의 상태가 이미 반영된 상태에서만 알림을 보낼 것이므로
// 운영 상황과 같다.
// ! 주의할 점은 실제로 데이터베이스에 커밋되므로 수동으로 롤백해줘야한다.
TestTransaction.flagForCommit();
TestTransaction.end();
TestTransaction.start();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package life.offonoff.ab.application.service;

import jakarta.persistence.EntityManager;
import life.offonoff.ab.application.notification.NotificationService;
import life.offonoff.ab.application.service.common.LengthInfo;
import life.offonoff.ab.application.service.request.CommentRequest;
import life.offonoff.ab.domain.TestEntityUtil;
Expand All @@ -25,6 +26,7 @@
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -52,6 +54,9 @@ class CommentServiceTest {
@Mock
MemberRepository memberRepository;

@MockBean
NotificationService notificationService;

Member topicAuthor;
Member voter;
Vote vote;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -25,6 +26,7 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@Transactional
@SpringBootTest
@Import(TestVoteConfig.TestContainerVotingTopicConfig.class)
public class VotingTopicContainerServiceIntegrationTest {
Expand Down

0 comments on commit 6e2b4de

Please sign in to comment.