Search
👤

유저정보 캐싱하기 with Redis, pinia

태그
Redis
pinia
목차

유저정보를 캐싱 하는 것의 이점은 뭘까?

프론트 측에서 유저정보가 필요할 때마다 유저를 DB 에서 조회해서 전달 해주면 되는것 아닌가 싶지만 이 조회가 빈번할 경우에는 캐싱을 하는 것이 좋습니다. 레디스에 유저정보를 캐싱 해두면 유저 조회 시 응답 속도가 빠를 뿐더러 데이터 베이스의 부하 또한 줄어듭니다. 이는 애플리케이션의 성능을 향상시키고 사용자 경험을 개선하는 데 도움이 되죠.
보통 일반적인 포트폴리오 수준에서 유저 요청이 빈번 해봤자 얼마나 빈번 하겠냐만은… 상상의 나래를 펼쳐가며 개발을 해보는게 좋잖아요 저는 항상 제가 만든 서비스에 애착을 가지고 있기 때문에 이 서비스가 잘 될 거라는 것을 생각하며 개발을 하곤 한답니다.

그래서 캐싱은 어떻게 하나요?

앞에서 이점을 설명했다면 이젠 코드로 레디스에 유저정보를 어떻게 캐싱 하는지, 그리고 프론트 에서는 이를 어떻게 관리 하는지에 대해 알려드리겠습니다. 사용 기술은 아래와 같습니다.
백엔드
버전
프론트
스프링부트
3.0.x
vue3 composition API
레디스
pinia
TypeScript

UserDTO.java

유저 엔티티에서 캐싱할 유저정보를 담은 DTO 입니다.
@Getter @Setter public class UserDTO implements Serializable { private String userId; private String email; private String username; }
Java
복사

CacheConfiguration.java

캐싱 관련 설정 클래스 입니다. 다양한 기능이 있으나, 저의 경우 따로 설정 하지는 않았습니다. 일단 이 기능을 구현하는 것을 공부하는 것이 우선 이였기 때문입니다. 캐싱에 대해 익숙해지면 다양한 기능을 더 파보려 합니다.
@Configuration @EnableCaching public class CacheConfiguration { @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .build(); } }
Java
복사

UserInfoService.java

유저 정보에 관련된 서비스 클래스 입니다. 유저의 정보가 변경될 경우를 대비하여 reloadUserInfo() 메서드도 만들어 주었습니다.
@Service @RequiredArgsConstructor public class UserInfoService { private final AuthUtil authUtil; private final TokenUtil tokenUtil; private final RedisUtil redisUtil; private final ObjectMapper objectMapper; @Cacheable(value = "userInfo", key="@authUtil.getLoginUserIndex().toString()") public UserDTO cacheUserInfo() { User user = authUtil.getLoginUser(); return toDTO(user); } public String cacheUserInfo(HttpServletRequest request) { String userId = tokenUtil.getUserIdFromToken(tokenUtil.getJWTTokenFromHeader(request)); String userInfo = redisUtil.getData("userInfo::" + userId); if (userInfo == null) { UserDTO userDTO = cacheUserInfo(); try { String userJson = objectMapper.writeValueAsString(userDTO); redisUtil.setData("userInfo::" + userId, userJson); userInfo = userJson; } catch (Exception e) { e.printStackTrace(); } } return userInfo; } @CachePut(value = "userInfo", key="@authUtil.getLoginUserIndex().toString()") public UserDTO reloadUserInfo() { User user = authUtil.getLoginUser(); return toDTO(user); } public String reloadUserInfo(HttpServletRequest request) { String userId = tokenUtil.getUserIdFromToken(tokenUtil.getJWTTokenFromHeader(request)); UserDTO userDTO = reloadUserInfo(); try { String userJson = objectMapper.writeValueAsString(userDTO); redisUtil.setData("userInfo::" + userId, userJson); return userJson; } catch (Exception e) { e.printStackTrace(); return null; } } private UserDTO toDTO(User user) { UserDTO dto = new UserDTO(); dto.setUserId(String.valueOf(user.getId())); dto.setUsername(user.getUsername()); dto.setEmail(user.getEmail()); return dto; } }
Java
복사

AuthUtil.java

현재 로그인한 유저의 정보를 받는 메서드가 존재합니다.
@Component @RequiredArgsConstructor public class AuthUtil { public Long getLoginUserIndex() { User user = getLoginUser(); Long userId = user.getId(); return userId; } public User getLoginUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); return (User) principal; } }
Java
복사

UserInfoController.java

유저 정보를 요청하는 api 와 유저 정보를 갱신하는 api 가 담겨있는 컨트롤러 입니다.
유저 정보를 갱신하는 api 의 경우 프론트에서 유저 정보 수정 요청을 보낸 뒤 200 상태를 받을 때 요청 합니다. 이 경우 위의 서비스를 타고 들어가 현재 캐싱되어 있는 유저의 정보를 갱신합니다.
@RestController @RequiredArgsConstructor @RequestMapping(value = "/api/users") @Slf4j public class UserInfoController { private final UserInfoService userInfoService; @PostMapping(value = "/me") public ResponseEntity<?> getUserInfo(HttpServletRequest request) { String userInfo = userInfoService.cacheUserInfo(request); return ResponseEntity.ok().body(userInfo); } @PostMapping(value = "/me/reload") public ResponseEntity<?> reloadUserInfo(HttpServletRequest request) { String userInfo = userInfoService.reloadUserInfo(request); return ResponseEntity.ok().body(userInfo); } }
Java
복사

프론트에서 캐싱된 정보를 어떻게 사용하나요?

로그인 하고 바로 유저정보 요청 하고 저장하기

<script setup lang="ts"> import { ref } from 'vue'; import {useAuthStore} from "../../store/AuthStore"; import {localLogin} from "../../api/AuthApi"; import router from "../../router"; import {loginUserInfo} from "../../api/UserInfo"; const email = ref(''); const password = ref(''); const authStore = useAuthStore(); const handleLogin = async () => { const userData = { email: email.value, password: password.value, }; try { const response = await localLogin(userData); if (response.status == 200) { authStore.setLoginStatus(true); console.log(authStore.isLoggedIn); const userInfoResponse = await loginUserInfo(); authStore.setUserInfo(userInfoResponse); // console.log(authStore.userInfo); 1 이 반환됩니다. await router.push('/project'); } } catch (error) { console.log(error); } }; </script>
TypeScript
복사
로그인 후 받은 토큰으로 바로 유저정보를 요청합니다. 요청으로 받은 정보를 상태 저장소에 저장하면 됩니다. 저는 pinia 를 사용하여 유저 정보를 관리 하였는데요, 코드는 아래와 같습니다.
import { defineStore } from 'pinia'; // @ts-ignore export const useAuthStore = defineStore('auth', { state: () => ({ isLoggedIn: false, userInfo: null, }), actions: { setLoginStatus(status: boolean) { this.isLoggedIn = status; }, setUserInfo(info: any) { this.userInfo = info; }, }, });
TypeScript
복사

저장된 유저 정보 사용하기

이런식으로 저장된 유저를 사용할 수 있습니다.
<script setup lang="ts"> const authStore = useAuthStore(); const userId = authStore.userInfo.userId; </script>
TypeScript
복사