Database/JPA / / 2024. 9. 23. 09:49

JPA Q&A 모음집

JPA 연관관계 매핑 및 헷갈리기 쉬운 개념 Q&A 모음

 

Q1. @GeneratedValue 어노테이션을 사용할 때, 기본적으로 어떤 전략이 사용되나요?

 

A:

@GeneratedValue를 사용할 때 전략을 명시하지 않으면, 기본적으로 **GenerationType.AUTO**가 적용됩니다. 이 전략은 JPA가 사용하는 데이터베이스 방언에 따라 적절한 키 생성 전략을 자동으로 선택합니다. 예를 들어, MySQL에서는 IDENTITY 전략을, Oracle에서는 SEQUENCE 전략을 사용할 수 있습니다.

 

Q2. ManyToOne, OneToMany는 클래스 참조 필드에서만 사용할 수 있나요?

 

A:

맞습니다! @ManyToOne, @OneToMany와 같은 어노테이션은 엔티티 클래스 간의 연관관계를 정의하는 데 사용됩니다. 즉, 다른 엔티티 클래스를 참조하는 필드에서만 사용할 수 있고, 기본 데이터 타입(String, int 등)에서는 사용할 수 없습니다. 예를 들어, Member 엔티티에서 Team 엔티티를 참조할 때 @ManyToOne을 사용하여 Team과의 관계를 정의할 수 있습니다.

 

Q3. @ManyToOne은 두 개의 다른 엔티티가 하나의 엔티티를 참조할 때 사용할 수 있나요?

 

A:

네, 맞습니다. 예를 들어, Member 엔티티와 User 엔티티가 하나의 Product 엔티티를 참조한다면, MemberUser에서 각각 @ManyToOne을 사용하여 Product를 참조할 수 있습니다. 이런 경우, 여러 MemberUser가 각각 하나의 Product를 참조할 수 있습니다.

 

Q4. @JoinColumn(name = "product_id")에서 product_id는 어디서 나온 건가요?

 

A:

product_id외래 키 컬럼의 이름을 나타냅니다. @JoinColumn을 사용하면 외래 키 컬럼의 이름을 지정할 수 있으며, 여기서 product_idProduct 엔티티의 기본 키를 참조하는 외래 키를 의미합니다. 만약 @JoinColumn을 명시하지 않으면, JPA가 기본적으로 필드 이름을 기반으로 외래 키 컬럼을 자동으로 생성합니다. 예를 들어, 필드 이름이 product이면 기본적으로 product_id라는 외래 키 컬럼이 생성됩니다.

 

Q5. 외래 키 컬럼 이름을 Product처럼 대문자로 시작하지 않고 소문자로 product로 쓰는 이유는 뭔가요?

 

A:

자바에서는 일반적으로 변수명을 **카멜 케이스(camelCase)**로 작성하는 관습이 있습니다. 그래서 클래스 이름은 대문자로 시작하지만, 필드 이름은 소문자로 시작하는 것이 일반적입니다. JPA는 필드 이름을 기반으로 외래 키 컬럼을 생성하므로, Product 클래스의 필드를 product로 선언하면 외래 키 컬럼 이름도 product_id처럼 소문자로 시작하게 됩니다.

 

Q6. mappedBy는 무엇이고, 왜 사용하나요?

 

A:

mappedBy양방향 연관관계에서 사용되며, “이 필드는 연관관계의 주인이 아니다”라는 의미입니다. 연관관계에서 외래 키를 관리하는 주인이 아닌 쪽에서 mappedBy를 사용해 주인이 누구인지 명시하는 역할을 합니다.

 

예를 들어, TeamMember 간의 양방향 관계에서, @OneToMany(mappedBy = "team")라고 하면 Member 엔티티의 team 필드가 연관관계의 주인임을 나타냅니다. 외래 키는 Member 엔티티에서 관리되며, Team 엔티티는 그 관계를 단순히 참조만 합니다.

 

Q7. @OneToMany(mappedBy = "team")에서 Team 엔티티가 외래 키의 주인인가요?

 

A:

아니요, Team 엔티티는 외래 키의 주인이 아닙니다. 외래 키의 주인은 Member 엔티티team 필드입니다. @OneToMany(mappedBy = "team")에서 mappedBy연관관계의 주인이 아니라는 의미로 사용되며, Member 엔티티에서 실제로 외래 키(team_id)를 관리합니다. 따라서 Team은 외래 키를 참조만 하고, 이를 직접 관리하지 않습니다.

 

Q8. ManyToOne 쪽이 외래 키의 주인이 되는 경우가 많은 이유는 무엇인가요?

 

A:

관습적으로 @ManyToOne 쪽이 외래 키의 주인이 되는 경우가 많습니다. 그 이유는 데이터베이스에서 외래 키는 주로 “다수” 엔티티에 저장되기 때문입니다. 예를 들어, 여러 Member가 하나의 Team에 속하는 경우, 각 Member는 어느 Team에 속하는지 알아야 하므로 Member 테이블에 외래 키(team_id)가 저장됩니다. 따라서 Member 엔티티가 외래 키를 관리하게 되고, @ManyToOne 쪽이 연관관계의 주인이 됩니다.

 

Q9. 연관관계의 주인은 무조건 외래 키를 가진 엔티티인가요?

 

A:

연관관계의 주인은 외래 키를 관리하는 엔티티입니다. 외래 키가 존재하더라도, 해당 외래 키를 실제로 관리하는 쪽이 주인이 됩니다. 예를 들어, MemberTeam 관계에서 외래 키(team_id)는 Member 테이블에 있지만, 이를 관리하는 필드는 Member 엔티티의 team 필드입니다. 그래서 Member가 외래 키의 주인이 되고, TeammappedBy를 통해 외래 키를 참조만 하게 됩니다.

 

Q10. 영속성 컨텍스트는 무엇인가요?

 

A:

**영속성 컨텍스트(Persistence Context)**는 JPA에서 엔티티를 관리하는 가상의 공간입니다. 엔티티 매니저(EntityManager)가 영속성 컨텍스트를 통해 엔티티의 생명주기를 관리하며, 이를 통해 캐싱, 1차 캐시, 변경 감지(Dirty Checking), 동일성 보장 등의 기능을 제공합니다.

 

간단히 말해, 영속성 컨텍스트는 JPA가 엔티티를 관리하는 메모리 공간이라고 생각하면 됩니다. 엔티티가 영속성 컨텍스트에 들어오면 JPA는 이를 추적하고 관리할 수 있게 됩니다.

 

Q11. 영속성 전이(Cascade)란 무엇인가요?

 

A:

**영속성 전이(Cascade)**는 어떤 엔티티의 상태 변화가 연관된 엔티티에도 전이되도록 설정하는 기능입니다. 즉, 하나의 엔티티를 저장하거나 삭제할 때, 그와 연관된 엔티티들도 함께 저장되거나 삭제되도록 할 수 있습니다.

 

예를 들어, 부모-자식 관계에서 부모 엔티티를 저장할 때 자식 엔티티도 자동으로 저장되게 하려면 CascadeType.PERSIST를 설정할 수 있습니다. 마찬가지로 부모를 삭제할 때 자식도 삭제되도록 하려면 CascadeType.REMOVE를 설정하면 됩니다.

@OneToMany(cascade = CascadeType.ALL)
private List<Child> children;

위 예시는 부모 엔티티의 상태 변화가 자식 엔티티에 전이되도록 설정한 것입니다.

 

Q12. 고아 객체 제거(orphanRemoval)는 무엇인가요?

 

A:

**고아 객체 제거(orphanRemoval)**는 부모 엔티티에서 연관된 자식 엔티티를 삭제할 때, 자식 엔티티가 데이터베이스에서 자동으로 삭제되는 기능입니다.

 

예를 들어, 부모 엔티티에서 자식 엔티티와의 연관관계를 끊으면 자식 엔티티는 고아 객체가 되는데, orphanRemoval = true를 설정하면 고아 객체가 자동으로 삭제됩니다.

@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children;

이 설정은 부모 엔티티가 자식 엔티티와의 관계를 끊으면, 해당 자식 엔티티는 데이터베이스에서도 자동으로 삭제되도록 하는 것입니다.

 

Q13. FetchType.LAZY와 FetchType.EAGER의 차이점은 무엇인가요?

 

A:

**FetchType.LAZY**와 **FetchType.EAGER**는 JPA에서 엔티티를 조회할 때 연관된 엔티티를 언제 로딩할지 결정하는 방법입니다.

 

FetchType.LAZY(지연 로딩): 연관된 엔티티를 필요할 때 로딩합니다. 즉, 연관된 엔티티가 실제로 호출되기 전까지는 로딩하지 않고, 그 엔티티가 실제로 사용될 때 데이터베이스에서 조회합니다. 기본적으로 @OneToMany, @ManyToMany 관계는 LAZY 로딩입니다.

FetchType.EAGER(즉시 로딩): 연관된 엔티티를 즉시 로딩합니다. 엔티티를 조회할 때 연관된 엔티티도 함께 즉시 로딩되어 한 번의 쿼리로 가져옵니다. 기본적으로 @ManyToOne, @OneToOne 관계는 EAGER 로딩입니다.

 

예시:

@ManyToOne(fetch = FetchType.LAZY)
private Team team;

LAZY는 성능 최적화를 위해 많이 사용됩니다. 필요하지 않은 데이터는 미리 가져오지 않기 때문에 불필요한 쿼리를 줄일 수 있습니다.

반면, EAGER는 연관된 엔티티를 자주 사용할 경우 한 번에 가져와서 편리하지만, 불필요한 데이터를 미리 로딩함으로써 성능에 영향을 미칠 수 있습니다.

 

Q14. N+1 문제는 무엇인가요? 어떻게 해결할 수 있나요?

 

A:

N+1 문제는 JPA에서 연관된 엔티티를 조회할 때 추가적인 쿼리가 반복적으로 발생하는 문제를 의미합니다. 예를 들어, Team을 조회할 때 각 팀에 속한 Member를 조회하기 위해 추가적인 쿼리가 N번 더 발생하는 경우가 대표적입니다.

 

이 문제는 주로 **지연 로딩(LAZY)**에서 발생합니다. 예를 들어, 여러 팀을 조회한 후 각 팀에 속한 멤버를 조회할 때 팀마다 멤버를 별도로 조회하는 쿼리가 추가로 발생하여 N+1 문제가 생깁니다.

 

해결 방법:

 

1. JOIN FETCH를 사용:

연관된 엔티티를 한 번의 쿼리로 조회할 수 있도록 JOIN FETCH를 사용하여 N+1 문제를 해결할 수 있습니다.

@Query("SELECT t FROM Team t JOIN FETCH t.members")
List<Team> findAllTeamsWithMembers();

 

2. @EntityGraph 사용:

@EntityGraph를 사용하여 연관된 엔티티를 함께 로딩할 수 있습니다.

@EntityGraph(attributePaths = {"members"})
List<Team> findAll();

 

Q15. JPQL과 Native Query의 차이점은 무엇인가요?

 

A:

**JPQL(Java Persistence Query Language)**는 객체를 기준으로 쿼리를 작성하는 JPA의 쿼리 언어입니다. SQL과 비슷하지만, 데이터베이스 테이블이 아니라 엔티티와 필드를 대상으로 쿼리를 작성합니다. 즉, JPQL은 엔티티 객체를 사용하여 쿼리를 작성하는 것이 특징입니다.

String jpql = "SELECT m FROM Member m WHERE m.name = :name";
List<Member> result = em.createQuery(jpql, Member.class)
                        .setParameter("name", "John")
                        .getResultList();

 

Native Query기본적인 SQL 쿼리를 그대로 사용할 수 있는 기능입니다. JPQL과 달리, 데이터베이스의 테이블과 컬럼을 직접 다룹니다. 복잡한 SQL 쿼리나 데이터베이스에 특화된 기능을 사용할 때 주로 사용됩니다.

String sql = "SELECT * FROM member WHERE name = ?";
List<Member> result = em.createNativeQuery(sql, Member.class)
                        .setParameter(1, "John")
                        .getResultList();

 

차이점:

 

JPQL엔티티 객체를 대상으로 하기 때문에 데이터베이스에 독립적입니다.

Native QuerySQL을 그대로 사용하므로, 데이터베이스에 의존적이고 특정 데이터베이스 기능을 활용할 수 있습니다.

 

Q16. flush()와 clear()의 차이점은 무엇인가요?

 

A:

 

flush(): 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다. 즉, 영속성 컨텍스트에서 관리하고 있는 엔티티 중 변경된 사항(삽입, 수정, 삭제)을 데이터베이스에 쿼리로 전달합니다. 하지만 flush()는 영속성 컨텍스트를 비우지 않으며, 여전히 엔티티는 관리 상태로 남아있습니다.

clear(): 영속성 컨텍스트를 완전히 비우고 관리하고 있는 모든 엔티티를 준영속 상태로 전환합니다. clear()가 호출되면 영속성 컨텍스트에서 더 이상 엔티티를 관리하지 않으며, 변경된 내용이 데이터베이스에 반영되지 않은 상태라면 데이터베이스와의 동기화가 끊어집니다.

 

차이점:

 

flush()는 영속성 컨텍스트의 변경 사항을 데이터베이스에 반영하지만, 엔티티를 여전히 관리합니다.

clear()는 영속성 컨텍스트를 비워버리고, 모든 엔티티를 준영속 상태로 만들어 더 이상 관리하지 않습니다.

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유