Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Data Respository를 사용한 Redis 캐싱 수행시 Hikaricp Conneciton을 사용하는 문제 해결 #191

Merged
merged 3 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableRetry
@EnableCaching
@EnableScheduling
@SpringBootApplication
@ConfigurationPropertiesScan
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableRedisRepositories
Expand All @@ -21,4 +31,25 @@ public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()));
}

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.entryTtl(Duration.ofDays(1L))
.computePrefixWith(CacheKeyPrefix.simple())
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));

Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("memberCache", configuration);

return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory)
.cacheDefaults(configuration)
.withInitialCacheConfigurations(redisCacheConfigurationMap)
.build();
}
}
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public ResponseEntity<AuctionResponse> addAuction(
@DeleteMapping("/{auctionId}")
public ResponseEntity<Void> cancelAuction(
@PathVariable Long auctionId, @AuthenticationPrincipal MemberPrincipal memberPrincipal) {
auctionEventService.cancelAuction(auctionId, memberPrincipal.getUserRole(), memberPrincipal.id());
auctionEventService.cancelAuction(auctionId, memberPrincipal.userRole(), memberPrincipal.id());
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
}

JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public ResponseEntity<ProductResponse> updateProduct(
public ResponseEntity<Void> deleteProduct(
@PathVariable Long productId, @AuthenticationPrincipal MemberPrincipal memberPrincipal) {
String savedFileName = productService.findFileNameOfProduct(productId).fileName();
productService.deleteProduct(productId, memberPrincipal.getUserRole(), memberPrincipal.id());
productService.deleteProduct(productId, memberPrincipal.userRole(), memberPrincipal.id());
fileService.deleteFileIfExists(savedFileName);

return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,24 @@
import freshtrash.freshtrashbackend.entity.Address;
import freshtrash.freshtrashbackend.entity.Member;
import freshtrash.freshtrashbackend.entity.constants.UserRole;
import freshtrash.freshtrashbackend.security.TokenInfo;
import lombok.Builder;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@Builder
@RedisHash(value = "Member", timeToLive = 24 * 60 * 60)
public record MemberPrincipal(
@Id Long id,
Long id,
String email,
@JsonIgnore String password,
@JsonIgnore Collection<? extends GrantedAuthority> authorities,
UserRole userRole,
String nickname,
double rating,
String fileName,
Expand All @@ -38,78 +36,71 @@ public MemberPrincipalBuilder authorities(UserRole userRole) {
}
}

@JsonIgnore
public static MemberPrincipal fromEntity(Member member) {
return MemberPrincipal.builder()
.id(member.getId())
.email(member.getEmail())
.password(member.getPassword())
.nickname(member.getNickname())
.authorities(member.getUserRole())
.userRole(member.getUserRole())
.rating(member.getRating())
.fileName(member.getFileName())
.address(member.getAddress())
.build();
}

public static MemberPrincipal fromTokenInfo(TokenInfo tokenInfo) {
return MemberPrincipal.builder()
.id(tokenInfo.id())
.email(tokenInfo.email())
.nickname(tokenInfo.fileName())
.authorities(tokenInfo.userRole())
.rating(tokenInfo.rating())
.fileName(tokenInfo.fileName())
.address(tokenInfo.address())
.build();
}

public UserRole getUserRole() {
return authorities.stream()
.map(r -> UserRole.valueOf(r.getAuthority().substring(5)))
.findFirst()
.orElse(UserRole.ANONYMOUS);
}

@JsonIgnore
@Override
public Map<String, Object> getAttributes() {
return oAuth2Attributes;
}

@JsonIgnore
@Override
public String getName() {
return email;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
return Objects.isNull(authorities) || authorities.isEmpty()
? Set.of(new SimpleGrantedAuthority(userRole.getName()))
: authorities;
}

@JsonIgnore
@Override
public String getPassword() {
return password;
}

@JsonIgnore
@Override
public String getUsername() {
return email;
}

@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}

@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}

@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}

@JsonIgnore
@Override
public boolean isEnabled() {
return true;
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
import freshtrash.freshtrashbackend.exception.AuthException;
import freshtrash.freshtrashbackend.exception.MemberException;
import freshtrash.freshtrashbackend.exception.constants.ErrorCode;
import freshtrash.freshtrashbackend.repository.MemberCacheRepository;
import freshtrash.freshtrashbackend.repository.MemberRepository;
import freshtrash.freshtrashbackend.security.TokenProvider;
import freshtrash.freshtrashbackend.utils.FileUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -29,7 +29,6 @@
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final MemberCacheRepository memberCacheRepository;
private final PasswordEncoder encoder;
private final TokenProvider tokenProvider;
private final FileService fileService;
Expand All @@ -42,10 +41,9 @@ public Member getMemberById(Long memberId) {
return memberRepository.findById(memberId).orElseThrow(() -> new MemberException(ErrorCode.NOT_FOUND_MEMBER));
}

@Cacheable(value = "memberCache", key = "#memberId")
public MemberPrincipal getMemberCache(Long memberId) {
return memberCacheRepository
.findById(memberId)
.orElseGet(() -> MemberPrincipal.fromEntity(getMemberById(memberId)));
return MemberPrincipal.fromEntity(getMemberById(memberId));
}

/**
Expand All @@ -67,7 +65,6 @@ public LoginResponse signIn(String email, String password) {

// 토큰 발급
String accessToken = tokenProvider.generateAccessToken(member.getId());
memberCacheRepository.save(MemberPrincipal.fromEntity(member));
return LoginResponse.of(accessToken);
}

Expand All @@ -93,6 +90,7 @@ public void checkNicknameDuplication(String nickname) {
* member 정보 수정
*/
@Transactional
@CacheEvict(value = "memberCache", key = "#memberPrincipal.id")
public Member updateMember(MemberPrincipal memberPrincipal, MemberRequest memberRequest, MultipartFile imgFile) {
if (!Objects.equals(memberPrincipal.nickname(), memberRequest.nickname())) {
checkNicknameDuplication(memberRequest.nickname());
Expand All @@ -116,7 +114,6 @@ public Member updateMember(MemberPrincipal memberPrincipal, MemberRequest member
else {
member.setFileName(null);
}
memberCacheRepository.save(MemberPrincipal.fromEntity(member));

return member;
}
Expand Down Expand Up @@ -148,9 +145,8 @@ public void changePassword(ChangePasswordRequest changePasswordRequest, MemberPr
updatePassword(memberPrincipal.email(), changePasswordRequest.newPassword());
}

public void logout(Long memberId) {
memberCacheRepository.deleteById(memberId);
}
@CacheEvict(value = "memberCache", key = "#memberId")
public void logout(Long memberId) {}

/**
* 비밀번호 일치 확인
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public ProductResponse addProduct(
@Transactional
public ProductResponse updateProduct(
Long productId, MultipartFile imgFile, ProductRequest productRequest, MemberPrincipal memberPrincipal) {
checkIfWriterOrAdmin(productId, memberPrincipal.getUserRole(), memberPrincipal.id());
checkIfWriterOrAdmin(productId, memberPrincipal.userRole(), memberPrincipal.id());
if (!FileUtils.isValid(imgFile)) {
throw new FileException(ErrorCode.INVALID_FIlE);
}
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void given_email_when_getMember_then_returnMember() {
void given_memberId_when_getMemberCache_then_returnMemberPrincipal() {
// given
Long memberId = 1L;
given(memberCacheRepository.findById(memberId)).willReturn(Optional.of(FixtureDto.createMemberPrincipal()));
given(memberRepository.findById(memberId)).willReturn(Optional.of(Fixture.createMember()));
// when
MemberPrincipal memberPrincipal = memberService.getMemberCache(memberId);
// then
Expand Down Expand Up @@ -112,11 +112,9 @@ void given_EmailAndPassword_when_generateAccessToken_then_saveCacheAndReturnToke
String email = "[email protected]", password = "pw";
Member member = Fixture.createMember();
String accessToken = "accessToken";
MemberPrincipal memberPrincipal = MemberPrincipal.fromEntity(member);
given(memberRepository.findByEmail(email)).willReturn(Optional.of(member));
given(encoder.matches(password, member.getPassword())).willReturn(true);
given(tokenProvider.generateAccessToken(member.getId())).willReturn(accessToken);
given(memberCacheRepository.save(memberPrincipal)).willReturn(memberPrincipal);
// when
LoginResponse loginResponse = memberService.signIn(email, password);
// then
Expand Down Expand Up @@ -180,7 +178,6 @@ void given_memberRequestAndImgFile_when_updateMember_then_memberRequestEqualsToU
MockMultipartFile imgFile = Fixture.createMultipartFileOfImage("test_image_content");
MemberRequest memberRequest = FixtureDto.createMemberRequest();
given(memberRepository.findById(memberId)).willReturn(Optional.of(Fixture.createMember()));
given(memberCacheRepository.save(any(MemberPrincipal.class))).willReturn(memberPrincipal);
willDoNothing().given(fileService).uploadFile(eq(imgFile), anyString());
// when
Member member = memberService.updateMember(memberPrincipal, memberRequest, imgFile);
Expand Down Expand Up @@ -270,7 +267,6 @@ void given_changePasswordRequestAndPrincipal_when_unmatchedOldPassword_then_thro
void given_memberId_when_then_deleteUserCache() {
//given
Long memberId = 123L;
willDoNothing().given(memberCacheRepository).deleteById(memberId);
//when
assertThatCode(() -> memberService.logout(memberId)).doesNotThrowAnyException();
//then
Expand Down
Loading