Skip to content

Commit 8cecd7e

Browse files
authored
Merge pull request #20 from codej99/cache-data-redis
Cache data redis
2 parents bdd09ee + f7901cb commit 8cecd7e

16 files changed

+310
-10
lines changed

‎README.md

+5
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,8 @@ alter table user_roles
115115
- https://daddyprogrammer.org/post/2695/springboot2-simple-jpa-board/
116116
- Git
117117
- https://github.com/codej99/SpringRestApi/tree/feature/board
118+
- SpringBoot2로 Rest api 만들기(15) – Redis로 api 결과 캐싱(Caching)처리
119+
- Document
120+
- https://daddyprogrammer.org/post/3870/spring-rest-api-redis-caching/
121+
- Git
122+
- https://github.com/codej99/SpringRestApi/tree/cache-data-redis

‎build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ dependencies {
2626
implementation 'org.springframework.boot:spring-boot-starter-web'
2727
implementation 'org.springframework.boot:spring-boot-starter-security'
2828
implementation 'org.springframework.boot:spring-boot-starter-actuator'
29+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
30+
//embedded-redis
31+
implementation 'it.ozimov:embedded-redis:0.7.2'
2932
implementation 'io.jsonwebtoken:jjwt:0.9.1'
3033
implementation 'io.springfox:springfox-swagger2:2.6.1'
3134
implementation 'io.springfox:springfox-swagger-ui:2.6.1'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.rest.api.common;
2+
3+
public class CacheKey {
4+
5+
public static final int DEFAULT_EXPIRE_SEC = 60; // 1 minutes
6+
public static final String USER = "user";
7+
public static final int USER_EXPIRE_SEC = 60 * 5; // 5 minutes
8+
public static final String BOARD = "board";
9+
public static final int BOARD_EXPIRE_SEC = 60 * 10; // 10 minutes
10+
public static final String POST = "post";
11+
public static final String POSTS = "posts";
12+
public static final int POST_EXPIRE_SEC = 60 * 5; // 5 minutes
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.rest.api.config;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.context.annotation.Profile;
6+
import redis.embedded.RedisServer;
7+
8+
import javax.annotation.PostConstruct;
9+
import javax.annotation.PreDestroy;
10+
11+
/**
12+
* 로컬 환경일경우 내장 레디스가 실행된다.
13+
*/
14+
@Profile("local")
15+
@Configuration
16+
public class EmbeddedRedisConfig {
17+
18+
@Value("${spring.redis.port}")
19+
private int redisPort;
20+
21+
private RedisServer redisServer;
22+
23+
@PostConstruct
24+
public void redisServer() {
25+
redisServer = new RedisServer(redisPort);
26+
redisServer.start();
27+
}
28+
29+
@PreDestroy
30+
public void stopRedis() {
31+
if (redisServer != null) {
32+
redisServer.stop();
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.rest.api.config;
2+
3+
import com.rest.api.common.CacheKey;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.cache.annotation.EnableCaching;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.data.redis.cache.CacheKeyPrefix;
9+
import org.springframework.data.redis.cache.RedisCacheConfiguration;
10+
import org.springframework.data.redis.cache.RedisCacheManager;
11+
import org.springframework.data.redis.connection.RedisConnectionFactory;
12+
import org.springframework.data.redis.core.RedisTemplate;
13+
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
14+
import org.springframework.data.redis.serializer.RedisSerializationContext;
15+
import org.springframework.data.redis.serializer.StringRedisSerializer;
16+
17+
import java.time.Duration;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
@RequiredArgsConstructor
22+
@EnableCaching
23+
@Configuration
24+
public class RedisConfig {
25+
26+
@Bean(name = "cacheManager")
27+
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
28+
29+
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
30+
.disableCachingNullValues()
31+
.entryTtl(Duration.ofSeconds(CacheKey.DEFAULT_EXPIRE_SEC))
32+
.computePrefixWith(CacheKeyPrefix.simple())
33+
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
34+
35+
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
36+
// 캐시 default 유효시간 설정
37+
cacheConfigurations.put(CacheKey.USER, RedisCacheConfiguration.defaultCacheConfig()
38+
.entryTtl(Duration.ofSeconds(CacheKey.USER_EXPIRE_SEC)));
39+
cacheConfigurations.put(CacheKey.BOARD, RedisCacheConfiguration.defaultCacheConfig()
40+
.entryTtl(Duration.ofSeconds(CacheKey.BOARD_EXPIRE_SEC)));
41+
cacheConfigurations.put(CacheKey.POST, RedisCacheConfiguration.defaultCacheConfig()
42+
.entryTtl(Duration.ofSeconds(CacheKey.POST_EXPIRE_SEC)));
43+
cacheConfigurations.put(CacheKey.POSTS, RedisCacheConfiguration.defaultCacheConfig()
44+
.entryTtl(Duration.ofSeconds(CacheKey.POST_EXPIRE_SEC)));
45+
46+
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory).cacheDefaults(configuration)
47+
.withInitialCacheConfigurations(cacheConfigurations).build();
48+
}
49+
}

‎src/main/java/com/rest/api/entity/User.java

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fasterxml.jackson.annotation.JsonProperty;
55
import com.rest.api.entity.common.CommonDateEntity;
66
import lombok.*;
7+
import org.hibernate.annotations.Proxy;
78
import org.springframework.security.core.GrantedAuthority;
89
import org.springframework.security.core.authority.SimpleGrantedAuthority;
910
import org.springframework.security.core.userdetails.UserDetails;
@@ -22,6 +23,7 @@
2223
@AllArgsConstructor // 인자를 모두 갖춘 생성자를 자동으로 생성합니다.
2324
@Table(name = "user") // 'user' 테이블과 매핑됨을 명시
2425
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) // Post Entity에서 User와의 관계를 Json으로 변환시 오류 방지를 위한 코드
26+
@Proxy(lazy = false)
2527
public class User extends CommonDateEntity implements UserDetails {
2628
@Id // pk
2729
@GeneratedValue(strategy = GenerationType.IDENTITY)

‎src/main/java/com/rest/api/entity/board/Board.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import lombok.NoArgsConstructor;
66

77
import javax.persistence.*;
8+
import java.io.Serializable;
89

910
@Entity
1011
@Getter
1112
@NoArgsConstructor
12-
public class Board extends CommonDateEntity {
13+
public class Board extends CommonDateEntity implements Serializable {
1314
@Id
1415
@GeneratedValue(strategy = GenerationType.IDENTITY)
1516
private Long boardId;

‎src/main/java/com/rest/api/entity/board/Post.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package com.rest.api.entity.board;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnore;
34
import com.rest.api.entity.User;
45
import com.rest.api.entity.common.CommonDateEntity;
56
import lombok.Getter;
67
import lombok.NoArgsConstructor;
8+
import lombok.Setter;
9+
import org.hibernate.annotations.Proxy;
710

811
import javax.persistence.*;
12+
import java.io.Serializable;
913

1014
@Entity
1115
@Getter
16+
@Setter
1217
@NoArgsConstructor
13-
public class Post extends CommonDateEntity {
18+
public class Post extends CommonDateEntity implements Serializable {
1419
@Id
1520
@GeneratedValue(strategy = GenerationType.IDENTITY)
1621
private Long postId;
@@ -25,13 +30,13 @@ public class Post extends CommonDateEntity {
2530
@JoinColumn(name = "board_id")
2631
private Board board; // 게시글 - 게시판의 관계 - N:1
2732

28-
2933
@ManyToOne(fetch = FetchType.LAZY)
3034
@JoinColumn(name = "msrl")
3135
private User user; // 게시글 - 회원의 관계 - N:1
3236

3337
// Join 테이블이 Json결과에 표시되지 않도록 처리.
34-
protected Board getBoard() {
38+
@JsonIgnore
39+
public Board getBoard() {
3540
return board;
3641
}
3742

‎src/main/java/com/rest/api/service/board/BoardService.java

+20-5
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,27 @@
33
import com.rest.api.advice.exception.CNotOwnerException;
44
import com.rest.api.advice.exception.CResourceNotExistException;
55
import com.rest.api.advice.exception.CUserNotFoundException;
6+
import com.rest.api.common.CacheKey;
67
import com.rest.api.entity.User;
78
import com.rest.api.entity.board.Board;
89
import com.rest.api.entity.board.Post;
910
import com.rest.api.model.board.ParamsPost;
1011
import com.rest.api.repo.UserJpaRepo;
1112
import com.rest.api.repo.board.BoardJpaRepo;
1213
import com.rest.api.repo.board.PostJpaRepo;
14+
import com.rest.api.service.cache.CacheSevice;
1315
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
17+
import org.springframework.cache.annotation.CacheEvict;
18+
import org.springframework.cache.annotation.CachePut;
19+
import org.springframework.cache.annotation.Cacheable;
1420
import org.springframework.stereotype.Service;
1521

1622
import javax.transaction.Transactional;
1723
import java.util.List;
1824
import java.util.Optional;
1925

26+
@Slf4j
2027
@Service
2128
@Transactional
2229
@RequiredArgsConstructor
@@ -25,30 +32,36 @@ public class BoardService {
2532
private final BoardJpaRepo boardJpaRepo;
2633
private final PostJpaRepo postJpaRepo;
2734
private final UserJpaRepo userJpaRepo;
35+
private final CacheSevice cacheSevice;
2836

2937
// 게시판 이름으로 게시판을 조회. 없을경우 CResourceNotExistException 처리
38+
@Cacheable(value = CacheKey.BOARD, key = "#boardName", unless = "#result == null")
3039
public Board findBoard(String boardName) {
3140
return Optional.ofNullable(boardJpaRepo.findByName(boardName)).orElseThrow(CResourceNotExistException::new);
3241
}
3342

34-
// 게시판 이름으로 게시물 리스트 조회.
43+
// 게시판 이름으로 게시글 리스트 조회.
44+
@Cacheable(value = CacheKey.POSTS, key = "#boardName", unless = "#result == null")
3545
public List<Post> findPosts(String boardName) {
3646
return postJpaRepo.findByBoard(findBoard(boardName));
3747
}
3848

39-
// 게시물ID로 게시물 단건 조회. 없을경우 CResourceNotExistException 처리
49+
// 게시글ID로 게시글 단건 조회. 없을경우 CResourceNotExistException 처리
50+
@Cacheable(value = CacheKey.POST, key = "#postId", unless = "#result == null")
4051
public Post getPost(long postId) {
4152
return postJpaRepo.findById(postId).orElseThrow(CResourceNotExistException::new);
4253
}
4354

44-
// 게시물을 등록합니다. 게시물의 회원UID가 조회되지 않으면 CUserNotFoundException 처리합니다.
55+
// 게시글을 등록합니다. 게시글의 회원UID가 조회되지 않으면 CUserNotFoundException 처리합니다.
56+
@CacheEvict(value = CacheKey.POSTS, key = "#boardName")
4557
public Post writePost(String uid, String boardName, ParamsPost paramsPost) {
4658
Board board = findBoard(boardName);
4759
Post post = new Post(userJpaRepo.findByUid(uid).orElseThrow(CUserNotFoundException::new), board, paramsPost.getAuthor(), paramsPost.getTitle(), paramsPost.getContent());
4860
return postJpaRepo.save(post);
4961
}
5062

51-
// 게시물을 수정합니다. 게시물 등록자와 로그인 회원정보가 틀리면 CNotOwnerException 처리합니다.
63+
// 게시글을 수정합니다. 게시글 등록자와 로그인 회원정보가 틀리면 CNotOwnerException 처리합니다.
64+
//@CachePut(value = CacheKey.POST, key = "#postId") 갱신된 정보만 캐시할경우에만 사용!
5265
public Post updatePost(long postId, String uid, ParamsPost paramsPost) {
5366
Post post = getPost(postId);
5467
User user = post.getUser();
@@ -57,16 +70,18 @@ public Post updatePost(long postId, String uid, ParamsPost paramsPost) {
5770

5871
// 영속성 컨텍스트의 변경감지(dirty checking) 기능에 의해 조회한 Post내용을 변경만 해도 Update쿼리가 실행됩니다.
5972
post.setUpdate(paramsPost.getAuthor(), paramsPost.getTitle(), paramsPost.getContent());
73+
cacheSevice.deleteBoardCache(post.getPostId(), post.getBoard().getName());
6074
return post;
6175
}
6276

63-
// 게시물을 삭제합니다. 게시물 등록자와 로그인 회원정보가 틀리면 CNotOwnerException 처리합니다.
77+
// 게시글을 삭제합니다. 게시글 등록자와 로그인 회원정보가 틀리면 CNotOwnerException 처리합니다.
6478
public boolean deletePost(long postId, String uid) {
6579
Post post = getPost(postId);
6680
User user = post.getUser();
6781
if (!uid.equals(user.getUid()))
6882
throw new CNotOwnerException();
6983
postJpaRepo.delete(post);
84+
cacheSevice.deleteBoardCache(post.getPostId(), post.getBoard().getName());
7085
return true;
7186
}
7287
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.rest.api.service.cache;
2+
3+
import com.rest.api.common.CacheKey;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.cache.annotation.CacheEvict;
6+
import org.springframework.cache.annotation.Caching;
7+
import org.springframework.stereotype.Service;
8+
9+
@Slf4j
10+
@Service
11+
public class CacheSevice {
12+
13+
@Caching(evict = {
14+
@CacheEvict(value = CacheKey.POST, key = "#postId"),
15+
@CacheEvict(value = CacheKey.POSTS, key = "#boardName")
16+
})
17+
public boolean deleteBoardCache(long postId, String boardName) {
18+
log.debug("deleteBoardCache - postId {}, boardName {}", postId, boardName);
19+
return true;
20+
}
21+
}

‎src/main/java/com/rest/api/service/security/CustomUserDetailService.java

+3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.rest.api.service.security;
22

33
import com.rest.api.advice.exception.CUserNotFoundException;
4+
import com.rest.api.common.CacheKey;
45
import com.rest.api.repo.UserJpaRepo;
56
import lombok.RequiredArgsConstructor;
7+
import org.springframework.cache.annotation.Cacheable;
68
import org.springframework.security.core.userdetails.UserDetails;
79
import org.springframework.security.core.userdetails.UserDetailsService;
810
import org.springframework.stereotype.Service;
@@ -13,6 +15,7 @@ public class CustomUserDetailService implements UserDetailsService {
1315

1416
private final UserJpaRepo userJpaRepo;
1517

18+
@Cacheable(value = CacheKey.USER, key = "#userPk", unless = "#result == null")
1619
public UserDetails loadUserByUsername(String userPk) {
1720
return userJpaRepo.findById(Long.valueOf(userPk)).orElseThrow(CUserNotFoundException::new);
1821
}

‎src/main/resources/application-alpha.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ spring:
2121
showSql: true
2222
generate-ddl: false
2323
url:
24-
base: http://dev-api.daddyprogrammer.org
24+
base: http://dev-api.daddyprogrammer.org
25+
redis:
26+
host: Standalone Redis 호스트
27+
port: Standalone Redis 포트

‎src/main/resources/application-local.yml

+3
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ spring:
1818
generate-ddl: true
1919
url:
2020
base: http://localhost:8080
21+
redis:
22+
host: localhost
23+
port: 6379

0 commit comments

Comments
 (0)