Skip to content

Commit 8abe2af

Browse files
authored
Merge pull request #42 from Sportize/feat/redis
✨Feat: Like 도메인 캐시 적용(isLiked & likeCount)
2 parents a1286c2 + d6e333a commit 8abe2af

5 files changed

Lines changed: 80 additions & 4 deletions

File tree

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,3 @@ src/main/resources/*.yml
3939
### VS Code ###
4040
.vscode/
4141

42-
## local docker compose
43-
docker/docker-compose.local.yml

docker/docker-compose.local.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
version: "3.9"
2+
3+
services:
4+
db:
5+
image: postgis/postgis:16-3.4
6+
container_name: sportize-postgis-local
7+
restart: unless-stopped
8+
9+
environment:
10+
POSTGRES_DB: sportize
11+
POSTGRES_USER: angora
12+
POSTGRES_PASSWORD: password
13+
TZ: Asia/Seoul
14+
15+
ports:
16+
- "5432:5432"
17+
18+
volumes:
19+
- sportize_pgdata_local:/var/lib/postgresql/data
20+
21+
healthcheck:
22+
test: ["CMD-SHELL", "pg_isready -U angora -d sportize"]
23+
interval: 10s
24+
timeout: 5s
25+
retries: 5
26+
redis:
27+
image: redis:7.2.4-alpine
28+
container_name: sportize-redis-local
29+
restart: unless-stopped
30+
ports:
31+
- "6379:6379"
32+
command: ["redis-server", "--appendonly", "yes", "--requirepass", "password"]
33+
volumes:
34+
- sportize_redisdata_local:/data
35+
healthcheck:
36+
test: [ "CMD", "redis-cli", "-a", "password", "PING" ]
37+
interval: 10s
38+
timeout: 3s
39+
retries: 5
40+
volumes:
41+
sportize_pgdata_local:
42+
sportize_redisdata_local:

src/main/java/com/be/sportizebe/domain/like/service/LikeServiceImpl.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import jakarta.transaction.Transactional;
99
import lombok.RequiredArgsConstructor;
1010
import lombok.extern.slf4j.Slf4j;
11+
import org.springframework.cache.annotation.CacheEvict;
12+
import org.springframework.cache.annotation.Cacheable;
13+
import org.springframework.cache.annotation.Caching;
1114
import org.springframework.dao.DataIntegrityViolationException;
1215
import org.springframework.security.core.parameters.P;
1316
import org.springframework.stereotype.Service;
@@ -23,6 +26,10 @@ public class LikeServiceImpl implements LikeService {
2326

2427
@Override
2528
@Transactional
29+
@Caching(evict = {
30+
@CacheEvict(cacheNames = "likeCount", key = "#targetType + ':' + #targetId"),
31+
@CacheEvict(cacheNames = "likeStatus", key = "#user.id + ':' + #targetType + ':' + #targetId")
32+
})
2633
public LikeResponse toggleLike(User user, LikeTargetType targetType, Long targetId) {
2734

2835
boolean liked = false; // 좋아요 여부 변수
@@ -39,18 +46,20 @@ public LikeResponse toggleLike(User user, LikeTargetType targetType, Long target
3946
likeRepository.save(like);
4047
liked = true;
4148
}
42-
43-
long likeCount = getLikeCount(targetType, targetId); // 해당 타겟(게시물 or 댓글)의 좋아요 개수 저장 변수
49+
// 토글 응답은 최신 값이 필요하므로 레포로 직접 count
50+
long likeCount = likeRepository.countByTargetTypeAndTargetId(targetType, targetId);
4451

4552
return LikeResponse.of(liked, targetType, targetId, likeCount);
4653
}
4754

4855
@Override
56+
@Cacheable(cacheNames = "likeStatus", key = "#user.id + ':' + #targetType + ':' + #targetId")
4957
public boolean isLiked(User user, LikeTargetType targetType, Long targetId) {
5058
return likeRepository.existsByUserAndTargetTypeAndTargetId(user, targetType, targetId);
5159
}
5260

5361
@Override
62+
@Cacheable(cacheNames = "likeCount", key = "#targetType + ':' + #targetId")
5463
public long getLikeCount(LikeTargetType targetType, Long targetId) { // 좋아요 개수 추적 메서드
5564
return likeRepository.countByTargetTypeAndTargetId(targetType, targetId);
5665
}

src/main/java/com/be/sportizebe/global/config/RedisCacheConfig.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory)
5353
new Jackson2JsonRedisSerializer<>(Long.class);
5454
commentCountSerializer.setObjectMapper(objectMapper);
5555

56+
// likeStatus 캐시는 Boolean 타입으로 역직렬화
57+
Jackson2JsonRedisSerializer<Boolean> likeStatusSerializer =
58+
new Jackson2JsonRedisSerializer<>(Boolean.class);
59+
likeStatusSerializer.setObjectMapper(objectMapper);
60+
61+
5662
// 기본 캐시 설정
5763
// TTL: 5분, Serializer: Object 기준
5864
RedisCacheConfiguration defaultConfig =
@@ -91,6 +97,24 @@ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory)
9197
)
9298
.entryTtl(Duration.ofSeconds(30))
9399
);
100+
101+
cacheConfigs.put(
102+
"likeCount",
103+
RedisCacheConfiguration.defaultCacheConfig()
104+
.serializeValuesWith(
105+
RedisSerializationContext.SerializationPair.fromSerializer(commentCountSerializer)
106+
)
107+
.entryTtl(Duration.ofSeconds(30))
108+
);
109+
110+
cacheConfigs.put(
111+
"likeStatus",
112+
RedisCacheConfiguration.defaultCacheConfig()
113+
.serializeValuesWith(
114+
RedisSerializationContext.SerializationPair.fromSerializer(likeStatusSerializer)
115+
)
116+
.entryTtl(Duration.ofSeconds(15))
117+
);
94118
return RedisCacheManager.builder(redisConnectionFactory)
95119
.cacheDefaults(defaultConfig)
96120
.withInitialCacheConfigurations(cacheConfigs)

src/main/java/com/be/sportizebe/global/security/CustomUserDetailService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.be.sportizebe.domain.user.entity.User;
44
import com.be.sportizebe.domain.user.repository.UserRepository;
55
import lombok.RequiredArgsConstructor;
6+
import org.springframework.cache.annotation.Cacheable;
67
import org.springframework.security.core.userdetails.UserDetails;
78
import org.springframework.security.core.userdetails.UserDetailsService;
89
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -13,6 +14,8 @@
1314
public class CustomUserDetailService implements UserDetailsService {
1415
private final UserRepository userRepository;
1516

17+
@Cacheable(cacheNames = "userDetails", key = "#username", unless = "#result == null")
18+
// result == null: 유저가 있으면- > 캐시에 저장, 유저가 없으면 -> 캐시에 저장 X
1619
@Override
1720
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
1821
User user = userRepository.findByUsername(username)

0 commit comments

Comments
 (0)