Skip to content

Conversation

@kimsky247-coder
Copy link

안녕하세요 승현님! 오랜만에 뵙는 것 같습니다. 이번 미션도 잘 부탁드립니다 🙇

이번 미션에서는

순수 JPA 기반 구현
- 순수 JPA로 Entity를 설계하고, `EntityManager`로 데이터 조작 로직 작성한다.
- Spring Data JPA 없이 직접 Repository를 구현한다.

를 구현했습니다

프로젝트 구조

프로젝트 구조는 다음과 같습니다.

auth/
- AdminInterceptor - 관리자 권한을 검증

member/
- Member - 회원 도메인 객체
- MemberController - 회원 관련 API 엔드포인트 정의
- MemberService - 회원 비즈니스 로직 처리
- MemberRepository - 회원 데이터베이스 접근 로직 
- LoginMember - 로그인된 회원 정보를 담는 객체
- LoginMemberArgumentResolver - 토큰을 통해 LoginMember 객체를 주입하는 리졸버
- LoginRequest - 로그인 요청 DTO
- MemberRequest - 회원 생성/수정 요청 DTO
- MemberResponse - 회원 정보 응답 DTO

reservation/
- Reservation - 예약 도메인 객체
- ReservationController - 예약 관련 API 엔드포인트 정의
- ReservationService - 예약 및 예약 대기 병합 비즈니스 로직 처리
- ReservationRepository - 예약 데이터베이스 접근 로직
- ReservationRequest - 예약 생성 요청 DTO
- ReservationResponse - 예약 정보 응답 DTO
- MyReservationResponse - 내 예약 목록 응답 DTO

waiting/
- Waiting - 예약 대기 도메인 객체
- WaitingController - 예약 대기 관련 API 엔드포인트 정의
- WaitingService - 예약 대기 등록/취소 비즈니스 로직 처리
- WaitingRepository - 예약 대기 데이터베이스 접근 로직
- WaitingResponse - 예약 대기 정보 응답 DTO
- WaitingWithRank - 대기 정보와 순번을 담는 객체

theme/
- Theme - 테마 도메인 객체
- ThemeController - 테마 관련 API 엔드포인트 정의
- ThemeService - 테마 비즈니스 로직 처리
- ThemeRepository - 테마 데이터베이스 접근 로직
- ThemeRequest - 테마 생성 요청 DTO
- ThemeResponse - 테마 정보 응답 DTO

time/
- Time - 시간 도메인 객체
- TimeController - 시간 관련 API 엔드포인트 정의
- TimeService - 시간 비즈니스 로직 처리
- TimeRepository - 시간 데이터베이스 접근 로직
- TimeRequest - 시간 생성 요청 DTO
- TimeResponse - 시간 정보 응답 DTO

util/
- JwtUtil - JWT 토큰 생성 및 파싱을 담당하는 유틸리티 클래스

root/
- RoomescapeApplication
- WebMvcConfiguration - Interceptor 및 ArgumentResolver 등록 설정
- PageController - 화면 이동을 담당하는 컨트롤러
- ExceptionController - 전역 예외 처리

Copy link

@BackFoxx BackFoxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! 즐거운 연휴 보내셨나요?
entityManager를 사용해서 멋지게 코드를 구현해 주셔서 리뷰할 거리가 많진 않습니다.
대신 Spring과 JPA를 조금 더 심도있게 연구할 수 있도록 키워드를 몇 개 제시해 드렸어요. 한 번 연구해 보세요!

spring.datasource.url=jdbc:h2:mem:database

spring.jpa.hibernate.ddl-auto=none
spring.sql.init.mode=always
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sql.init mode가 always이면서 ddl-auto 값이 none이면, 애플리케이션을 실행할 때마다 테스트 데이터가 중복으로 쌓이지 않나요? 😲

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 H2 In-Memory 모드를 사용 중이기 때문에 서버 종료 시 데이터가 휘발되어 중복 문제가 발생하지 않고 있었습니다. 그러나 영구 저장소를 사용하게 될 경우, ddl-auto=none 상태에서 데이터 중복 적재 및 PK 충돌 오류가 발생할 수 있다는 것을 배우게 되었습니다. 이러한 문제를 방지하고, 항상 깨끗한 상태로 데이터가 초기화되도록 보장하기 위해 spring.jpa.hibernate.ddl-auto 설정을 create로 변경하였습니다.

import java.security.Key;

public class JwtUtil {
private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secretKey가 코드에 노출돼 있네요! 민감정보를 어떻게 하면 숨길 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 내에 민감한 정보가 하드코딩 되어 있어 보안취약점이 존재했습니다. 해당 키를 application.properties 파일로 분리하여 관리하도록 수정했습니다. 또한, 분리된 값을 주입받기 위해 기존의 static 메서드로 구성된 JwtUtil을 스프링 빈으로 리팩토링하고, 생성자를 통해 값을 주입받아 사용하도록 구조를 개선했습니다

import java.util.List;

@Service
@Transactional(readOnly = true)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(readOnly = true)로 설정했을 때 내부적으로 어떤 일이 일어나나요? 현 프로젝트에서 해당 옵션을 지정했을 때 차이점이 있는지도 가능한 상세하게 분석해서 설명해 주세요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Transactional(readOnly = true)를 지정하면 스프링은 해당 트랜잭션을 조회 전용 트랜잭션으로 처리합니다.
읽기 전용 트랜잭션에서는 EntityManager의 FlushMode가 MANUAL으로 설정되어, 트랜잭션 종료 시 변경 감지에의한 flush가 발생하지 않습니다. 또한 변경 감지를 위한 스냅샷을 생성하지 않아 메모리 사요량을 줄일 수 있고, 일부 DB에서는 실행 계획 최적화나 쓰기 차단 힌트로 활용됩니다.

현재 프로젝트에서는 조회 전용 서비스에 @Transactional(readOnly = true)옵션을 적용함으로써 프로젝트에서는 의도치 않은 엔티티 변경이 DB에 반영되는 것을 방지할 수 있고, 조회 성격의 API에서 불필요한 변경 감지 및 flush 비용을 줄이는 효과가 있습니다.
하지만, 프로젝트의 트래픽이 크지 않고 엔티티 구조가 단순하기 때문에 성능적 이점보다 트랜잭션의 의도를 명확히 표현하고, 읽기/쓰기 책임을 구분한다는 설계적 의미가 크다고 생각합니다.

}

public List<Time> findAll() {
return em.createQuery("SELECT t FROM Time t", Time.class)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서는 JPQL을 사용하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPQL은 엔티티 객체를 대상으로 쿼리하기 때문에, DB 벤더가 바뀌거나 테이블 구조가 변경되더라도 쿼리를 수정할 필요 없는 DB 독립성을 가집니다. 또한, JPQL로 조회된 엔티티는 자동으로 영속성 컨텍스트의 관리 대상이 되므로, 추후 비즈니스 로직에서 데이터를 수정할 때 JPA의 기능을 활용할 수 있다는 장점이 있어 선택했습니다.


import java.util.List;

@Repository
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPA를 사용하면 리포지토리 레이어에서 @repository@component를 썼을 때의 차이점이 나타나기 시작합니다. 그 차이점을 한 번 연구해 보시겠어요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 어노테이션 모두 빈으로 등록된다는 점은 같지만, @repository에는 예외 변환 기능이 포함되어 있다는 차이가 있습니다. JPA를 사용할 경우 JPA 구현체 고유 예외가 발생하는데, @repository는 JPA 구현체가 던지는 예외를 스프링 공통 예외로 자동 변환해줍니다. 이로 인해 서비스 레이어는 하부 데이터 접근 기술에 의존하지 않고, DB 기술 변경 시에도 예외 처리 코드를 수정하지 않아도 되는 장점이 있습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants