[면접공부] - JPA (feat.gpt)
- 국비지원 (스파르타)
- 2025. 4. 24. 23:32
오늘 JPA에 대한 면접 질문을 받았다.
대부분 원리에 대한 질문들이었다. 하지만 나는 재대로 답변하지 못했다.ㅜㅜ
질문은 총 6문제로 다음과 같은 질문이 있었다.
- JPA 엔티티 라이프 사이클 상태 설명
- N+1 문제와 FetchType(EAGER/LAZY)의 연관
- JPQL과 네이티브 쿼리 사용 시 영속성 컨텍스트 주의점
- @Transactional의 동작 원리
- 낙관적 락 / 비관적 락의 차이 및 적용 방식
- QueryDSL 사용 시 영속성 컨텍스트를 타지 않는 경우
첫 번째 질문같은 경우 엔티티가 어떻게 동작하는지 설명하는거였다. 제대로 설명하지 못했다.ㅜㅜ
이건 내가 노션으로 정리할때 빼먹고 안적어 둔거 같다.
암튼 요거는 엔티티가 어떻게 관리가 되어지는지 말해보라는건데... 쉽지 않았다.
JPA 엔티티는 총 네 가지 라이프사이클 상태를 가집니다.
비영속(Transient)은 단순 객체 상태이고, 영속(Persistent)은 영속성 컨텍스트에 의해 관리되며,
준영속(Detached)은 더 이상 관리되지 않는 상태이고, 삭제(Removed)는 실제 DB 삭제 예약 상태입니다.
상태에 따라 DB 반영 여부나 1차 캐시 활용 여부가 달라지므로, 상태 구분이 매우 중요합니다.
1차 캐시
- 영속성을 이용해서, DB에서 엔티티를 조회하고, 바로 버리는 것이 아닌 영속성 컨텍스트라는 곳에 저장해 같은 엔티티를 조회한다면, 영속성 콘텍스트에서 찾아 DB에서 조회하는 횟수를 줄여, 성능상 이점을 가져올 수 있습니다.
- EntityManager가 관리하는 영속성 컨텍스트 내부에 있는 첫 번째 캐시
특징
- EntityManager 단위의 캐시: 트랜잭션 내에서만 유효.
- 엔티티의 동일성 보장: 같은 트랜잭션 내에서 같은 데이터를 두 번 조회해도 같은 객체가 반환됨.
- 성능 최적화: 동일한 데이터를 여러 번 조회해도 DB 쿼리를 최소화.
- 자동 관리: JPA가 내부적으로 알아서 관리해줘서 따로 설정이 필요 없음.
1차 캐시를 언제 사용하는지
- 동일한 트랜잭션에서 같은 데이터를 반복 조회할 때.
- 트랜잭션 내에서 데이터 일관성을 유지할 때.
영속성
- 데이터를 메모리와 데이터베이스 사이에서 일관되게 관리하는 과정
동작 방식
- 최초 조회할 때는 1차 캐시에 엔티티가 없습니다.
- 데이터베이스에서 엔티티를 조회합니다.
- 엔티티를 1차 캐시에 보관합니다.
- 1차 캐시에 보관된 결과를 반환합니다.
- 이후 같은 엔티티를 조회하면 1차 캐시에 같은 엔티티가 있으므로 데이터베이스를 조회하지 않고 1차 캐시의 엔티티를 그대로 반환합니다.
- 1차 캐시는 객체의 동일성을 (a == b)를 보장합니다.
데이터베이스
1차 DB: 캐시에 적재한 후에 들고온다.
세션에 관리가 일어난다.
- N+1 문제와 FetchType(EAGER/LAZY)의 연관 요거는 한번도 N+1문제와 이거와레이지를 함께 생각해본적이 없어서 제대로 답변을 해보적이 없는데 요거는 어떻게 답변할 수 있을까?
N+1 문제는 엔티티를 조회한 후, 연관된 엔티티를 LAZY 로딩 방식으로 접근할 때
추가 쿼리가 N번 발생하는 현상입니다.
예를 들어 게시글 10개를 조회하고 각 게시글의 작성자를 접근하면,
게시글 1번 쿼리 + 작성자 10번 쿼리로 총 11번의 쿼리가 발생하게 됩니다.
이를 해결하기 위해 fetch join, entity graph, batch size 조정 같은 기법을 사용합니다.
오늘은 진도가 쭉쭉 나가는 구먼...
- JPQL과 네이티브 쿼리 사용 시 영속성 컨텍스트 주의점
이거는
JPQL은 영속성 컨텍스트와 연동되어 1차 캐시를 활용하지만,
네이티브 쿼리는 직접 DB에서 조회되므로, 영속성 컨텍스트에 반영되지 않습니다.
따라서 동일 객체를 사용하는 보장도 없고, 변경 감지도 되지 않아 주의가 필요합니다.
JPQL을 기본으로 사용하되, 네이티브 쿼리는 성능 최적화가 필요할 때 신중히 써야 합니다.
내가 JPA쪽이 약하구나 하나도 모르겠네...
JPQL과 네이티브 쿼리는 모두 JPA에서 사용하는 쿼리 방식이지만, **영속성 컨텍스트(Persistence Context)**와의 연관성에서는 중요한 차이가 있어요.
핵심 개념: 영속성 컨텍스트란?
- JPA가 엔티티의 상태를 관리하는 1차 캐시
- 동일한 트랜잭션 안에서는 동일한 엔티티 인스턴스를 유지
JPQL과 네이티브 쿼리의 차이 & 주의점
작성 방식 | 엔티티 기반 (SELECT m FROM Member m) | SQL 직접 작성 (SELECT * FROM member) |
영속성 컨텍스트 반영 | 반영됨 (1차 캐시 있음) | 반영되지 않음 (바로 DB에서 가져옴) |
동일성 보장 | O (동일 트랜잭션 내 같은 객체 반환) | X (같은 데이터라도 새로운 객체) |
Dirty Checking | O | X (변경 감지 안 됨) |
쓰기 작업 결과 | em.persist()로 저장 후 JPQL 사용 시 반영됨 | DB에는 반영되지만, 영속성 컨텍스트에는 없음 |
주의해야 할 문제들
1. 중복 조회 문제
→ 영속성 컨텍스트에 이미 있는 객체와 다르게 동작함 → 불일치 가능성
2. 수정 무시 문제
→ DB는 수정됐지만, JPA는 모름
해결 방법
- JPQL을 우선 사용: 영속성 컨텍스트와 동기화 유지
- 네이티브 쿼리 후에는 em.clear() 또는 em.refresh()를 명시적으로 사용해 동기화
- 네이티브 쿼리는 성능 최적화, 복잡한 조인 등 최후의 수단으로만 사용
- @Transactional의 동작 원리
이거는 JPA에서의 원리라하는데... 뭘까.... 아 모르겠네
하나도
@Transactional은 스프링이 제공하는 선언적 트랜잭션 관리 기능으로,
프록시 객체를 생성해 트랜잭션 시작 → 메서드 실행 → 정상/예외 여부에 따라 커밋 또는 롤백 흐름으로 처리됩니다.
내부적으로는 PlatformTransactionManager를 통해 트랜잭션을 제어하며,
JPA에서는 이 범위 내에서 영속성 컨텍스트가 생성되어 변경 감지, 지연 로딩 같은 기능이 정상적으로 작동하게 됩니다.
- 낙관적 락 / 비관적 락의 차이 및 적용 방식
요거는 JPA에서 낙관적락과 비관적락을 어떻게 지원을 하는지 물어보는 질문이었다. 낙관적 락같은 경우는 @Version으로 지정한다는 정도로 알고 있었고 비관적 락같은 경우는 내가 분.명.히 썼는데 기억이 나지를 않는다.
JPA에서 낙관적 락은 @Version을 통해 버전 정보를 비교하여 충돌 여부를 확인하고,
비관적 락은 조회 시점에 락을 걸어 다른 트랜잭션의 접근을 차단합니다.
낙관적 락은 충돌이 드문 경우 유리하고, 비관적 락은 높은 동시성이 필요한 경우에 적합합니다.
상황에 따라 성능과 안정성을 고려해 선택합니다
벌써 마지막 질문이다.. JPA 진짜 나중에 조져야곘다.
- QueryDSL 사용 시 영속성 컨텍스트를 타지 않는 경우
이건 진짜 몰랐다.. ㅜㅜ;;;
오늘은 왜 인지 잘 안되네
QueryDSL에서 영속성 컨텍스트를 타지 않는 경우
1. Projections.bean, Projections.fields, Projections.constructor 등으로 DTO 조회 시
이 경우 조회 결과는 순수 DTO 객체이며, JPA의 1차 캐시나 변경 감지와는 무관합니다.
→ 영속성 컨텍스트에 등록되지 않음
MemberDto는 엔티티가 아니라서 JPA가 관리하지 않기 때문이에요.
2. Native Query를 QueryDSL로 Wrapping한 경우
- QueryDSL 자체는 JPQL을 생성하는 도구지만, 일부 네이티브 SQL을 사용할 수 있도록 확장하면
그 결과 역시 영속성 컨텍스트와 분리됨
3. QuerydslRepositorySupport + JPQL 직접 작성 시
지연 로딩(LAZY)을 트리거하지 않는다면 영속성 컨텍스트와는 일시적으로 분리될 수 있어요.
결과적으로 생기는 문제들
동일성 비교 실패 | 동일한 엔티티를 JPA로 조회한 객체와 == 비교 시 false |
변경 감지 안 됨 | DTO에 값 변경 후 저장해도 DB에 반영 안 됨 |
트랜잭션 내 저장 안됨 | DTO 수정해도 영속성 컨텍스트에 없기 때문에 commit돼도 저장되지 않음 |
이정도면 feat이 아니라 with아닌가 대부분...
역대급으로 쉽지 않네
QueryDSL은 기본적으로 영속성 컨텍스트를 사용하지만,
Projections.*를 사용해 DTO를 조회하는 경우에는 순수 자바 객체를 반환하므로
JPA의 1차 캐시에 등록되지 않고, 변경 감지도 되지 않습니다.
따라서 DTO 수정 후 저장하는 방식은 의미가 없고, 엔티티를 직접 조회하여 수정하거나
필요 시 DTO → 엔티티로 매핑 후 저장하는 방식이 필요합니다.
왜 DTO로 변환하나요?
- 보안: 내부 엔티티 구조를 외부에 노출하지 않음
- 유연성: API 응답 포맷을 자유롭게 구성 가능
- 성능 최적화: 필요한 필드만 조회 (select 절 줄이기 가능)
- 불필요한 연관관계 로딩 방지: LAZY 필드 불필요하게 접근하지 않음
면접용 요약 템플릿
엔티티를 외부에 직접 노출하는 것은 유지보수성과 보안 측면에서 위험하기 때문에,
서비스 계층에서 DTO로 변환하여 반환합니다.
QueryDSL을 사용하는 경우 DTO로 바로 매핑하거나, 엔티티를 조회 후 stream을 통해 수동 변환하는 방식으로 처리합니다.
이 구조는 API 응답의 유연성과 성능 최적화 모두를 고려한 패턴입니다.
좋은 질문이에요
"엔티티 → DTO 변환 자동화 전략"은 말 그대로,
반복되는 수동 변환(map → new DTO(...)) 코드를 줄이기 위해,
자동으로 엔티티를 DTO로 매핑해주는 방식이에요. 특히 대규모 프로젝트에서 생산성과 유지보수성을 높이기 위해 자주 사용돼요.
자동화 전략 종류 요약
ModelMapper | 가장 유명한 객체 매핑 라이브러리 | DTO와 엔티티 필드명이 같을 것 |
MapStruct | 컴파일 타임 매핑. 성능 우수 | 인터페이스만 작성하면 구현 자동 생성 |
Lombok + 생성자 | 빌더/생성자 조합 | 비교적 수동에 가까움 |
QueryDSL Projections | QueryDSL 내부에서 자동 생성자 주입 | 단순 매핑에 적합 |
예시 1: ModelMapper
DTO와 Entity의 필드명이 같다면 자동으로 매핑해줍니다.
단점:
- 런타임 매핑이라 성능이 떨어질 수 있음
- 깊은 중첩 구조에서는 매핑 오류 발생 가능
예시 2: MapStruct ( 실무 추천)
✨ 컴파일 시 매핑 코드를 생성해주므로 성능이 우수하고 안정적이에요.
예시 3: QueryDSL Projections.constructor 방식 (간접 자동화)
이건 QueryDSL이 SQL 조회 시점에 DTO 생성자에 직접 넣어주는 방식이라,
변환 로직이 없다는 점에서 **"반자동화"**로 보시면 돼요.
실무 적용 기준
작고 단순한 API | 수동 매핑 (new DTO(...))도 충분 |
중형 이상, 반복 많음 | MapStruct 👍 |
필드명 동일한 DTO | ModelMapper (빠른 적용) |
QueryDSL 위주 | Projections 사용 (DTO 생성자 필수) |
DTO 변환을 자동화하기 위해 ModelMapper나 MapStruct 같은 라이브러리를 사용할 수 있습니다.
ModelMapper는 런타임에 필드명을 기준으로 매핑해주는 반면, MapStruct는 컴파일 타임에 타입 안전성을 보장하면서 코드를 생성합니다.
규모가 크고 DTO가 많은 프로젝트에서는 유지보수를 위해 MapStruct를 주로 사용합니다.
JPA는 나중에 한번더 정리를 해보는걸루 하고 마치겠다.ㅇㅇㅋㅋ
'국비지원 (스파르타)' 카테고리의 다른 글
[학습] 젠킨스 프리스타일 vs 파이프라인 (1) | 2025.04.27 |
---|---|
[학습] 젠킨스 (CI/CD) (2) | 2025.04.25 |
[면접공부] - java, spring (feat.gpt) (1) | 2025.04.23 |
[면접공부] - db (feat.gpt) (4) (1) | 2025.04.18 |
[면접공부] - db (feat.gpt) (3) (0) | 2025.04.17 |