Skip to content

Commit 46e63a6

Browse files
committed
Feat: NotificationSettingRepository QueryDSL로 변환
1 parent 73fd418 commit 46e63a6

File tree

4 files changed

+250
-26
lines changed

4 files changed

+250
-26
lines changed
Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,9 @@
11
package com.back.domain.notification.repository;
22

33
import com.back.domain.notification.entity.NotificationSetting;
4-
import com.back.domain.notification.entity.NotificationSettingType;
54
import org.springframework.data.jpa.repository.JpaRepository;
6-
import org.springframework.data.jpa.repository.Query;
7-
import org.springframework.data.repository.query.Param;
85
import org.springframework.stereotype.Repository;
96

10-
import java.util.List;
11-
import java.util.Optional;
12-
137
@Repository
14-
public interface NotificationSettingRepository extends JpaRepository<NotificationSetting, Long> {
15-
16-
// 특정 사용자의 모든 알림 설정 조회
17-
@Query("SELECT ns FROM NotificationSetting ns WHERE ns.user.id = :userId")
18-
List<NotificationSetting> findAllByUserId(@Param("userId") Long userId);
19-
20-
// 특정 사용자의 특정 타입 알림 설정 조회
21-
@Query("SELECT ns FROM NotificationSetting ns WHERE ns.user.id = :userId AND ns.type = :type")
22-
Optional<NotificationSetting> findByUserIdAndType(
23-
@Param("userId") Long userId,
24-
@Param("type") NotificationSettingType type
25-
);
26-
27-
// 특정 사용자의 설정이 존재하는지 확인
28-
@Query("SELECT COUNT(ns) > 0 FROM NotificationSetting ns WHERE ns.user.id = :userId")
29-
boolean existsByUserId(@Param("userId") Long userId);
30-
31-
// 특정 사용자의 활성화된 알림 설정만 조회
32-
@Query("SELECT ns FROM NotificationSetting ns WHERE ns.user.id = :userId AND ns.enabled = true")
33-
List<NotificationSetting> findEnabledSettingsByUserId(@Param("userId") Long userId);
8+
public interface NotificationSettingRepository extends JpaRepository<NotificationSetting, Long>, NotificationSettingRepositoryCustom {
349
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.back.domain.notification.repository;
2+
3+
import com.back.domain.notification.entity.NotificationSetting;
4+
import com.back.domain.notification.entity.NotificationSettingType;
5+
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
public interface NotificationSettingRepositoryCustom {
10+
11+
// 특정 사용자의 모든 알림 설정 조회
12+
List<NotificationSetting> findAllByUserId(Long userId);
13+
14+
// 특정 사용자의 특정 타입 알림 설정 조회
15+
Optional<NotificationSetting> findByUserIdAndType(Long userId, NotificationSettingType type);
16+
17+
// 특정 사용자의 설정이 존재하는지 확인
18+
boolean existsByUserId(Long userId);
19+
20+
// 특정 사용자의 활성화된 알림 설정만 조회
21+
List<NotificationSetting> findEnabledSettingsByUserId(Long userId);
22+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.back.domain.notification.repository;
2+
3+
import com.back.domain.notification.entity.NotificationSetting;
4+
import com.back.domain.notification.entity.NotificationSettingType;
5+
import com.querydsl.jpa.impl.JPAQueryFactory;
6+
import lombok.RequiredArgsConstructor;
7+
8+
import java.util.List;
9+
import java.util.Optional;
10+
11+
import static com.back.domain.notification.entity.QNotificationSetting.notificationSetting;
12+
13+
@RequiredArgsConstructor
14+
public class NotificationSettingRepositoryImpl implements NotificationSettingRepositoryCustom {
15+
16+
private final JPAQueryFactory queryFactory;
17+
18+
@Override
19+
public List<NotificationSetting> findAllByUserId(Long userId) {
20+
return queryFactory
21+
.selectFrom(notificationSetting)
22+
.where(notificationSetting.user.id.eq(userId))
23+
.fetch();
24+
}
25+
26+
@Override
27+
public Optional<NotificationSetting> findByUserIdAndType(Long userId, NotificationSettingType type) {
28+
NotificationSetting result = queryFactory
29+
.selectFrom(notificationSetting)
30+
.where(
31+
notificationSetting.user.id.eq(userId),
32+
notificationSetting.type.eq(type)
33+
)
34+
.fetchOne(); // 결과가 없으면 null, 1개 초과면 NonUniqueResultException 발생
35+
36+
return Optional.ofNullable(result);
37+
}
38+
39+
@Override
40+
public boolean existsByUserId(Long userId) {
41+
Integer fetchFirst = queryFactory
42+
.selectOne() // 존재 여부만 확인하므로 간단히 숫자 1을 조회
43+
.from(notificationSetting)
44+
.where(notificationSetting.user.id.eq(userId))
45+
.fetchFirst();
46+
47+
return fetchFirst != null;
48+
}
49+
50+
@Override
51+
public List<NotificationSetting> findEnabledSettingsByUserId(Long userId) {
52+
return queryFactory
53+
.selectFrom(notificationSetting)
54+
.where(
55+
notificationSetting.user.id.eq(userId),
56+
notificationSetting.enabled.isTrue()
57+
)
58+
.fetch();
59+
}
60+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package com.back.domain.notification.repository;
2+
3+
import com.back.domain.notification.entity.NotificationSetting;
4+
import com.back.domain.notification.entity.NotificationSettingType;
5+
import com.back.domain.user.entity.User;
6+
import com.back.domain.user.repository.UserRepository;
7+
import com.back.global.config.QueryDslTestConfig;
8+
import jakarta.persistence.EntityManager;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Nested;
12+
import org.junit.jupiter.api.Test;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
15+
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
16+
import org.springframework.context.annotation.Import;
17+
import org.springframework.dao.DataIntegrityViolationException;
18+
19+
import java.util.List;
20+
import java.util.Optional;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.junit.jupiter.api.Assertions.assertThrows;
24+
25+
@DataJpaTest
26+
@Import(QueryDslTestConfig.class)
27+
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
28+
class NotificationSettingRepositoryTest {
29+
30+
@Autowired
31+
private NotificationSettingRepository notificationSettingRepository;
32+
33+
@Autowired
34+
private UserRepository userRepository;
35+
36+
@Autowired
37+
private EntityManager entityManager;
38+
39+
private User userWithSettings;
40+
private User userWithoutSettings;
41+
42+
@BeforeEach
43+
void setUp() {
44+
userWithSettings = User.builder().email("[email protected]").username("유저1").build();
45+
userWithoutSettings = User.builder().email("[email protected]").username("유저2").build();
46+
userRepository.saveAll(List.of(userWithSettings, userWithoutSettings));
47+
48+
NotificationSetting commentSetting = NotificationSetting.create(
49+
userWithSettings,
50+
NotificationSettingType.POST_COMMENT
51+
);
52+
53+
NotificationSetting likeSetting = NotificationSetting.create(
54+
userWithSettings,
55+
NotificationSettingType.POST_LIKE,
56+
false
57+
);
58+
59+
notificationSettingRepository.saveAll(List.of(commentSetting, likeSetting));
60+
61+
entityManager.flush();
62+
entityManager.clear();
63+
}
64+
65+
@Nested
66+
@DisplayName("알림 설정 조회 테스트")
67+
class FindNotificationSettingsTest {
68+
69+
@Test
70+
@DisplayName("특정 사용자의 모든 알림 설정 조회 성공")
71+
void t1() {
72+
// when
73+
List<NotificationSetting> settings = notificationSettingRepository.findAllByUserId(userWithSettings.getId());
74+
75+
// then: Enum 타입이 정확한지 확인
76+
assertThat(settings).hasSize(2);
77+
assertThat(settings)
78+
.extracting(NotificationSetting::getType)
79+
.containsExactlyInAnyOrder(NotificationSettingType.POST_COMMENT, NotificationSettingType.POST_LIKE);
80+
}
81+
82+
@Test
83+
@DisplayName("특정 사용자의 특정 타입 알림 설정 조회 성공")
84+
void t2() {
85+
// when
86+
Optional<NotificationSetting> settingOpt = notificationSettingRepository.findByUserIdAndType(
87+
userWithSettings.getId(),
88+
NotificationSettingType.POST_COMMENT
89+
);
90+
91+
// then
92+
assertThat(settingOpt).isPresent();
93+
assertThat(settingOpt.get().getType()).isEqualTo(NotificationSettingType.POST_COMMENT);
94+
}
95+
96+
@Test
97+
@DisplayName("존재하지 않는 타입 조회 시 빈 Optional 반환")
98+
void t3() {
99+
// when
100+
Optional<NotificationSetting> settingOpt = notificationSettingRepository.findByUserIdAndType(
101+
userWithSettings.getId(),
102+
NotificationSettingType.SYSTEM // 저장하지 않은 타입
103+
);
104+
105+
// then
106+
assertThat(settingOpt).isEmpty();
107+
}
108+
109+
@Test
110+
@DisplayName("특정 사용자의 활성화된 알림 설정만 조회 성공")
111+
void t4() {
112+
// when
113+
List<NotificationSetting> enabledSettings = notificationSettingRepository.findEnabledSettingsByUserId(userWithSettings.getId());
114+
115+
// then: setUp에서 활성화한 POST_COMMENT만 조회되어야 함
116+
assertThat(enabledSettings).hasSize(1);
117+
assertThat(enabledSettings.get(0).getType()).isEqualTo(NotificationSettingType.POST_COMMENT);
118+
assertThat(enabledSettings.get(0).isEnabled()).isTrue();
119+
}
120+
}
121+
122+
@Nested
123+
@DisplayName("알림 설정 변경 및 영속성 테스트")
124+
class ChangePersistenceTest {
125+
126+
@Test
127+
@DisplayName("toggle 메서드 호출 후 변경사항 DB에 반영")
128+
void t1() {
129+
// given: POST_COMMENT 설정(enabled=true)을 조회
130+
NotificationSetting setting = notificationSettingRepository.findByUserIdAndType(
131+
userWithSettings.getId(),
132+
NotificationSettingType.POST_COMMENT
133+
).orElseThrow();
134+
assertThat(setting.isEnabled()).isTrue();
135+
136+
// when: toggle 메서드 호출
137+
setting.toggle();
138+
entityManager.flush(); // 변경 감지(Dirty Checking)를 통해 UPDATE 쿼리 실행
139+
entityManager.clear(); // 영속성 컨텍스트 초기화
140+
141+
// then: 다시 조회했을 때 enabled 상태가 false로 변경되어 있어야 함
142+
NotificationSetting updatedSetting = notificationSettingRepository.findById(setting.getId()).orElseThrow();
143+
assertThat(updatedSetting.isEnabled()).isFalse();
144+
}
145+
}
146+
147+
@Nested
148+
@DisplayName("데이터 제약 조건 테스트")
149+
class ConstraintTest {
150+
151+
@Test
152+
@DisplayName("동일한 사용자와 타입으로 중복 설정 저장 시 예외 발생")
153+
void t1() {
154+
// given: 이미 POST_COMMENT 타입 설정이 존재하는 사용자
155+
// when: 동일한 타입으로 새로운 설정을 생성
156+
NotificationSetting duplicateSetting = NotificationSetting.create(
157+
userWithSettings,
158+
NotificationSettingType.POST_COMMENT
159+
);
160+
161+
// then: Unique 제약 조건 위반으로 DataIntegrityViolationException이 발생해야 함
162+
assertThrows(DataIntegrityViolationException.class, () -> {
163+
notificationSettingRepository.saveAndFlush(duplicateSetting);
164+
});
165+
}
166+
}
167+
}

0 commit comments

Comments
 (0)