신입 개발자 인터뷰 대비 - 8. JPA
신입 개발자 인터뷰 대비 : 8. JPA
JPA
JPA 영속성 컨텍스트가 무엇이며, 이점을 설명하시오(5가지)
JPA 영속성 컨텍스트는 엔티티를 영구 저장하는 환경이다. 데이터베이스와 애플리케이션 사이에서 엔티티의 생명주기를 관리한다. 이로 인한 이점은 다음과 같다:
- 1차 캐시: 영속성 컨텍스트는 1차 캐시 기능을 제공해 이미 조회한 엔티티를 메모리에 저장한다. 이는 같은 트랜잭션 내에서 엔티티를 다시 조회할 때 데이터베이스에 접근할 필요가 없어 성능이 향상된다.
- 동일성 보장: 같은 트랜잭션 내에서 같은 엔티티에 대한 조회는 항상 같은 인스턴스를 반환한다. 따라서 엔티티의 동일성이 보장되며, 데이터 일관성을 유지할 수 있다.
- 트랜잭션 지원 쓰기 지연 (Transactional write-behind): 엔티티의 변경 내용을 즉시 데이터베이스에 반영하지 않고, 트랜잭션이 종료될 때까지 내부 쿼리 저장소에 모은 후 일괄 처리한다. 이는 네트워크 사용을 최적화하고 데이터베이스 부하를 줄여 성능을 향상시킨다.
- 변경 감지 (Dirty Checking): 영속성 컨텍스트는 엔티티의 초기 상태와 트랜잭션이 끝날 때의 상태를 비교하여 변경된 부분만 데이터베이스에 업데이트한다. 이는 불필요한 쓰기 작업을 방지하며 데이터베이스 성능을 최적화한다.
- 연관된 객체와의 일관성 유지: 영속성 컨텍스트는 엔티티 사이의 관계를 관리하고, 연관된 엔티티를 적절히 처리한다. 이는 복잡한 엔티티 간의 관계를 유지하며 데이터 일관성을 보장하는 데 도움이 된다.
이런 기능들은 JPA를 사용함으로써 개발자가 객체 지향적인 방식으로 데이터베이스를 다룰 수 있게 하며, 데이터 접근 코드의 복잡성을 줄이고, 성능을 향상시킬 수 있다.
JPA Propagation 전파 단계를 설명하시오.
JPA 또는 Spring Framework에서 사용되는 트랜잭션 전파(propagation)는 현재 실행 중인 트랜잭션의 범위 내에서 다른 트랜잭션을 어떻게 처리할지를 정의한다. 주요 전파 단계는 다음과 같다:
- REQUIRED (필수): 기본 설정이다. 현재 활성 트랜잭션이 있으면 그 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작한다.
- SUPPORTS (지원): 현재 활성 트랜잭션이 있으면 그 트랜잭션에 참여한다. 트랜잭션이 없으면 트랜잭션 없이 실행된다.
- MANDATORY (필수적): 반드시 현재 활성 트랜잭션이 있어야 한다. 없을 경우 예외를 발생시킨다.
- REQUIRES_NEW (새로 필요): 항상 새로운 트랜잭션을 시작한다. 현재 활성 트랜잭션이 있으면 일시 정지시키고, 새 트랜잭션이 완료된 후에 이전 트랜잭션을 재개한다.
- NOT_SUPPORTED (지원하지 않음): 트랜잭션을 사용하지 않는다. 현재 활성 트랜잭션이 있다면 일시 정지시킨다.
- NEVER (절대 사용하지 않음): 현재 활성 트랜잭션이 있으면 예외를 발생시킨다. 트랜잭션 없이 실행되어야 한다.
- NESTED (중첩): 현재 활성 트랜잭션이 있으면 중첩된 트랜잭션 내에서 실행된다. 중첩된 트랜잭션은 부모 트랜잭션의 일부로, 부모 트랜잭션이 롤백되면 중첩된 트랜잭션도 롤백된다.
이러한 전파 옵션들은 트랜잭션 관리의 유연성을 제공하며, 복잡한 비즈니스 로직에서도 효율적인 트랜잭션 처리를 가능하게 한다. 각 상황에 맞게 적절한 전파 정책을 선택함으로써 성능 최적화 및 데이터 일관성을 유지할 수 있다.
JPA를 써야 하는 이유를 설명하시오
JPA(Java Persistence API)를 사용해야 하는 주요 이유는 다음과 같다:
- 객체-관계 매핑 (ORM): JPA는 데이터베이스 테이블과 자바 객체 간의 매핑을 자동으로 처리해준다. 이는 데이터베이스의 테이블을 객체로 표현하는 과정을 간소화하며, 개발자는 객체 지향 프로그래밍에 집중할 수 있게 해준다.
- 데이터베이스 독립성: JPA는 다양한 데이터베이스 제품에 독립적인 API를 제공한다. 이는 애플리케이션을 특정 데이터베이스에 종속되지 않게 하며, 데이터베이스를 변경하더라도 코드를 크게 수정할 필요가 없다.
- 표준화된 API: JPA는 자바 표준으로, 다양한 ORM 프레임워크(예: Hibernate, EclipseLink, OpenJPA 등)가 이를 구현한다. 표준 API를 사용함으로써, 애플리케이션의 호환성과 유지보수성이 향상된다.
- 생산성 및 유지보수: JPA를 사용하면 CRUD(Create, Read, Update, Delete) 작업을 위한 코드를 간결하게 작성할 수 있다. JPA가 제공하는 강력한 쿼리 언어(JPQL)를 사용하면 복잡한 쿼리도 쉽게 처리할 수 있으며, 코드의 가독성과 유지보수가 용이해진다.
- 캐싱 및 성능 최적화: JPA는 내부적으로 1차 캐시와 2차 캐시를 지원하여 데이터 접근 시간을 줄이고 성능을 향상시킬 수 있다. 또한, 지연 로딩(lazy loading)과 즉시 로딩(eager loading)과 같은 데이터 로딩 전략을 제공하여 필요에 따라 데이터를 효율적으로 로딩할 수 있다.
JPA의 사용은 개발자가 데이터베이스 작업을 보다 쉽게 처리하고, 오류 가능성을 줄이며, 코드의 일관성과 효율성을 높일 수 있게 도와준다. 이러한 이유로 많은 자바 기반 애플리케이션에서 널리 사용된다.
N+1 문제가 무엇이며 이를 해결할 방법은?
**N+1 문제는 ORM을 사용할 때 발생할 수 있는 성능 문제로, 한 번의 쿼리로 연관된 객체의 정보를 가져온 뒤, 각 객체에 대해 추가적인 쿼리가 발생하는 현상을 말한다. 예를 들어, 부모 엔티티를 조회한 후 연관된 자식 엔티티를 각각 별도의 쿼리로 불러오는 경우, 예상보다 많은 데이터베이스 접근이 발생하여 성능이 저하될 수 있다.
N+1 문제 해결 방법
-
Fetch Join 사용: JPA에서 제공하는 JPQL의 fetch join을 사용하면, 연관된 엔티티를 처음 쿼리할 때 함께 불러올 수 있다. 이 방식은 한 번의 쿼리로 필요한 모든 데이터를 로딩하므로 추가적인 쿼리 발생을 막을 수 있다.
SELECT p FROM Parent p JOIN FETCH p.children
-
Batch Size 설정: Hibernate 등의 JPA 구현체에서는
@BatchSize
어노테이션을 사용하여 한 번에 로드할 연관 엔티티의 수를 지정할 수 있다. 이는 여러 개의 쿼리를 적은 수의 쿼리로 묶어 처리하여 성능을 개선한다.@OneToMany @BatchSize(size=10) private Set<Child> children;
-
Entity Graphs 사용: JPA 2.1부터 도입된 Entity Graph 기능을 통해 쿼리 실행 시 어떤 속성을 사전에 로드할지 구체적으로 지정할 수 있다. 이를 통해 필요한 데이터만 선택적으로 로딩할 수 있어 성능을 최적화할 수 있다.
EntityGraph<Parent> graph = entityManager.createEntityGraph(Parent.class); graph.addAttributeNodes("children"); Map<String, Object> hints = new HashMap<>(); hints.put("javax.persistence.fetchgraph", graph); Parent parent = entityManager.find(Parent.class, id, hints);
-
JOIN Fetch 사용: Criteria API를 사용하여 명시적으로 연관 엔티티를 함께 로드할 수도 있다. 이는 코드상에서 동적으로 쿼리를 구성해야 할 때 유용하다.
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Parent> cq = cb.createQuery(Parent.class); Root<Parent> parent = cq.from(Parent.class); parent.fetch("children", JoinType.INNER); cq.select(parent); List<Parent> results = entityManager.createQuery(cq).getResultList();
이러한 기법들은 개발자가 적절히 활용하면 N+1 문제를 효과적으로 해결하고, 애플리케이션의 성능을 크게 향상시킬 수 있다.**