springboot 7

[트러블슈팅] Redis 호출 5번을 Pipeline으로 1번에 끝내기

공고 상세 조회 API에서 Redis 명령을 개별로 5~6번 호출하던 구조를 Pipeline으로 개선한 과정을 정리해 보겠습니다.이전에 공식문서를 통해 Redis를 공부하고 있었을 때 Pipeline이라는 것을 알게 되었습니다 Redis 공식문서의 예제 코드는 다음과 같습니다 //pop a specified number of items from a queueList results = stringRedisTemplate.executePipelined( new RedisCallback() { public Object doInRedis(RedisConnection connection) throws DataAccessException { StringRedisConnection stringRedi..

[트러블 슈팅] SOS 상세 조회 API의 Row Explosion 문제 해결

모든 연관 데이터를 단일 LEFT JOIN 쿼리로 조회하던 구조에서 OneToMany 관계인 SosImage를 별도 쿼리로 분리하여 Row Explosion 문제를 해결하고 조회 안정성을 확보했습니다.그 과정에서 알게 된 내용을 정리해 보겠습니다문제 상황SOS 상세 조회 API를 구현하면서 아래 모든 데이터를 단일 QueryDSL 쿼리에서 LEFT JOIN으로 한 번에 조회하도록 설계했습니다.Member 정보 (badge)ProfileImageBusiness 정보 (name, address)BusinessCodeSos 정보SosImage 리스트문제는 SosImage가 OneToMany 관계라는 점이었습니다. 이미지가 5개라면 결과 Row도 5개가 생성됩니다. 데이터는 하나인데 이미지 수만큼 행이 복제되는..

[트러블슈팅] getReferenceById를 이용한 연관 관계 저장 최적화

문제 상황특정 엔티티를 다른 엔티티의 외래 키(FK)로 저장할 때, 기존에는 findById를 사용하여 해당 엔티티를 DB에서 완전히 조회한 후 세팅하는 방식을 사용했습니다.를 사용하여 해당 엔티티를 데이터베이스에서 완전히 조회한 후 세팅했습니다. 하지만 단순히 연관 관계를 맺기 위해 전체 데이터를 SELECT 하는 것이 비효율적이라는 판단하에 리팩토링을 진행했습니다. java@Transactionalpublic void addFavorite(Long announceId, Long memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> new EntityNotFoundException()); ..

[트러블 슈팅]공고 상세 조회 API의 불필요한 DB 쿼리 줄이기

문제 상황공고 상세 조회 API에서 공고 상세 정보(Announce), 사용자의 즐겨찾기 여부(MemberFavorite), 제출 서류 목록(Document) 세 가지 데이터를 함께 반환해야 했습니다.초기 구현에서는 이 세 가지를 모두 개별 쿼리로 분리하여 조회했습니다. sqlSELECT * FROM announce WHERE announce_id = ?SELECT EXISTS ( SELECT 1 FROM member_favorite WHERE member_id = ? AND announce_id = ?)SELECT * FROM document WHERE announce_id = ?API 요청 한 건당 DB와 3번의 Round Trip이 발생하는 구조였습니다. 참고)사실 exist를 쓰는 것..

[JWT 트러블슈팅] 매 요청마다 DB 조회가 발생하는 문제와 해결 방법

문제 상황JWT 기반 인증을 구현하는 과정에서 모든 요청마다 DB 조회가 발생하는 구조로 구현되어 있었습니다.기존 인증 로직은 JWT 토큰에서 email을 추출한 뒤 UserDetailsService를 호출하여 사용자 정보를 다시 DB에서 조회하는 방식이었습니다. JWT를 사용하면서도 매 요청마다 DB 조회가 발생하여 JWT의 핵심 장점인 Stateless 구조를 전혀 활용하지 못하는 문제가 있었습니다.기존 코드 javaif (jwtService.validate(token)) { String email = jwtService.extractSubject(token); UserDetails user = userDetailsService.loadUserByUsername(email); Use..

[트러블슈팅] @OneToOne(mappedBy) LAZY 로딩이 동작하지 않는 이유와 해결 방법

문제 상황회원가입 및 로그인 기능을 구현하던 중 memberRepository.findMemberByEmail() 메서드를 호출했을 때, 조회하지 않은 연관 엔티티의 쿼리까지 함께 실행되는 문제가 발생했습니다.단순히 Member만 조회했지만 실제로는 다음과 같은 SQL이 실행되었습니다. sqlSELECT * FROM member WHERE email = ?SELECT * FROM business WHERE member_id = ?SELECT * FROM profile_image WHERE member_id = ?SELECT * FROM auth WHERE member_id = ? @OneToOne(fetch = FetchType.LAZY)로 설정했기 때문에 연관 엔티티는 실제 접근 시점에 Lazy Lo..

2. 서블릿

@WebServletname = 서블릿 이름urlPatterns : URL 매핑HTTP 요청이 오면 매핑된 URL 이 호출되면서 서블릿 컨테이너는 protected void service(HttpServletRequest request, HttpServletResponse response)를 자동으로 호출한다 HttpServletRequestHTTP 요청 메시지를 개발자가 직접 파싱해도 되지만 매우 불편함그래서 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 HTTP 요청 메시지를 파싱 한다 HttpServletRequest 객체에 담아서 준다또한 HttpServletRequest는 추가로 여러 가지 부가 기능을 제공한다 " 임시 저장소 기능 "HTTP 요청이 시작부터 끝날 때까지..