💡 Spring 핵심 개념 인터뷰 Q&A
스프링 데이터란?
스프링 데이터 기능
CrudRepository, JpaRepository)@Query로 JPQL 또는 Native SQL 작성 가능Q1. Spring Data JPA를 사용하는 이유는?
Q2. 메서드 이름만으로 쿼리를 만들 수 있다?
List<User> findByEmailAndStatus(String email, Status status);
Q3. @Query 어노테이션은 언제 쓰나
@Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findRecentUsers(@Param("date") LocalDate date);
Q4. 영속성 컨텍스트와 Spring Data JPA 관계?
Q6. N+1 문제와 해결 방법?
N+1 문제란 무엇인가?
정의
N+1 문제는 JPA나 Hibernate에서 하나의 조회 쿼리를 실행한 후, 그 결과로 가져온 엔티티 각각에 대해 연관된 데이터를 다시 조회하면서 N번의 추가 쿼리가 발생하는 문제를 말한다.
즉, 총 1 + N번의 쿼리가 실행된다.
예시
List<Member> members = memberRepository.findAll();
for (Member member : members) {
System.out.println(member.getTeam().getName());
}
1. Fetch Join 사용
JPQL에서 fetch join을 명시적으로 사용하면 N+1 문제를 해결할 수 있다.
@Query("SELECT m FROM Member m JOIN FETCH m.team")
List<Member> findAllWithTeam();
장점
fetch join 주의할점
2. EntityGraph 사용
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();
3. @BatchSize 설정
@BatchSize(size = 100)
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
spring.jpa.properties.hibernate.default_batch_fetch_size=100
SELECT * FROM team WHERE team_id IN (1, 2, 3, ..., 100)
단점
4. QueryDSL 활용
queryFactory.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.name.eq("홍길동"))
.fetch();
마무리 요약
Spring Data JPA
Repository 계층 계보
CrudRepository // 기본 CRUD
↳ PagingAndSortingRepository // + 페이징/정렬
↳ JpaRepository // + JPA 전용 기능
JPA 기능
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByEmail(String email);
}
Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findRecentUsers(@Param("date") LocalDate date);
Page<T>, Slice<T> 리턴으로 페이지 처리할 수 있다.Page<User> findByStatus(Status status, Pageable pageable);
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
@CreatedDate : 생성일자자동생성, @LastModifiedDate : 수정일자 위 2개의 어노테이션은 이엔티티가 생성/수정될 때 현재 시간 등을 자동 주입합니다.
Auditing 안되는 경우
@EnableJpaAuditing 없으면 작동 안 함@CreatedDate나 @LastModifiedDate가 붙어 있어도, Hibernate는 해당 필드를 무시하고 값 주입을 하지 않습니다.예시
전체 설정 클래스에 선언하는 방식
@SpringBootApplication
@EnableJpaAuditing
public class ProjApplication {
public static void main(String[] args) {
SpringApplication.run(QrLogiApplication.class, args);
}
}
위는 ProjApplication 클래스에 @EnableJpaAuditing 어노테이션을 붙인다. 프로젝트가 소규모일떄 이런식으로 적용한다.
일반적인 어느정도의 규모가 있는 프로젝트에서는 configuration설정 클래스에 이 어노테이션을 붙여야한다.
별도의 설정 클래스에 선언하는 방식
@Configuration
@EnableJpaAuditing
public class JpaConfig {
...
}
common 또는 api/config 폴더에 위치시키면 깔끔합니다.JPQL
비교
예시 : SQL과 비교
| 항목 | JPQL | SQL |
|---|---|---|
| 대상 | 엔티티 (User) | 테이블 (user_table) |
| 필드 | 클래스 필드 (user.email) | 컬럼명 (user.email_address) |
| 반환 타입 | 엔티티 or DTO | 컬럼 집합 |
| 추상화 수준 | 객체 지향적 | 데이터베이스 지향적 |
Querydsl
findByXxx)나 @Query는 정적 쿼리입니다.Querydsl는 동적쿼리
비교 : JPQL vs QueryDsl
| 조건 | 추천 방식 |
|---|---|
| 단순한 조건 1~2개 | findByXxx, @Query (정적 쿼리) |
| 조건이 유동적 (null 허용, 선택적 검색) | Querydsl 사용 (동적 쿼리) |
프로젝트 병행사용전략
JpaRepository 사용Querydsl 커스텀 리포지토리 사용/// JPQL : 간단한 쿼리
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom
/// QueryDSL: 복잡한 쿼리
public interface UserRepositoryCustom {
List<User> search(UserSearchCondition cond);
}