한 엔티티 조회시 연관되어 있는 엔티티들을 모두 조회하는 것보다는 필요한 연관관계만 조회해 오는 것이 효과적이다.
Member 객체와 연관되어있는 Team객체가 있다고 했을 때, 비지니스 로직에서 Team객체가 필요하지 않은 경우에 Member와 Team을 같이 조회하게 되면 낭비가 발생한다.
이처럼 우리는 때로 Member 객체만 조회하고 싶을 때가 있다. 이런 상황을 위해 JPA는 프록시와 지연로딩이라는 방식을 지원한다.
❓ 여기서 잠깐! 지연로딩과 즉시로딩을 간단하게 알아보자! (밑에서 더 자세히 설명할 예정)
지연로딩 : 자신과 연관된 엔티티를 실제로 사용할 때 연관된 엔티티를 조회하는 방식
즉시 로딩 : 자신과 연관된 엔티티를 조인을 통해 함께 조회하는 방식
지연로딩과 프록시(Proxy)
JPA 프록시는 연관된 객체들을 데이터베이스에서 조회하기 위해 사용한다.
지연로딩과 프록시를 사용하면 처음에 데이터베이스를 조회할때 연관된 객체들의 조회 결과는 실제 엔티티 객체가 아닌 프록시라는 가짜 엔티티 객체인 것이다. 그 이후에 연관된 객체가 실제 사용하는 시점에서 데이터베이스를 조회해 실제 엔티티 객체를 가져오게 된다.
Team team = new Team("팀");
em.persist(team);
Member member = new Member("멤버");
member.setTeam(team);
em.persist(member);
em.clear();
Member persistMember = em.find(Member.class, member.getId); // select member 쿼리 발생
System.out.println(persistMember.getTeam().getName()); // selcet team 쿼리 발생
지연로딩을 사용하면, member객체를 조회할때 member만 데이터베이스에서 조회한다. 그 이후 member와 연관된 객체인 team이 실제 사용되면 persistMember.getTeam().getName()을 호출했을 때 데이터베이스에서 Team 객체를 조회하게 된다.
정리하자면 연관된 객체들을 처음부터 데이터베이스에서 조회하는 것이 아니라 실제 사용하는 시점에 데이터베이스에서 조회하게 된다.
프록시 호출
프록시를 얻으려면 getReference() 메서드를 사용하면 된다.
em.find() vs em.getReference()
- em.find()메서드는 DB를 통해서 실제 엔티티 객체를 조회하는 메서드이고,
- em.getReference()는 DB조회를 미루는 가짜 프록시 엔티티 객체를 조회하는 메서드이다.
프록시는 실제 객체의 상속본이다.
프록시는 어떻게 실제 객체처럼 동작할 수 있을까?
이는 프록시가 실제 클래스를 상속받아서 만들어지기 때문이다.
프록시 객체는 실제 객체의 참조(target)를 보관하여, 프록시 객체의 메서드를 호출했을 때 실제 객체의 메서드를 호출한다.
실제 클래스와 겉 모습이 같기 때문에 사용자 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용해도 된다.
프록시를 사용하기 때문에 JPA 엔티티 생성의 중요 규칙이 생겼다. 기본 생성자는 반드시 필요하며 최소 protected 접근 제한자를 가져야하며, 프록시가 실제 엔티티 클래스를 상속받아야 하기 때문에 엔티티 클래스를 final로 선언할 수 없다.
이 내용은 아래 포스팅에 자세히 적어놨으니 참고. ✨
JPA의 Entity는 기본 생성자가 왜 반드시 필요할까? https://it-jin-developer.tistory.com/60
프록시 객체의 초기화
최초 지연 로딩시에 프록시는 당연히 참조값을 가지고 있지 않다.
프록시 객체는 member.getName()처럼 실제 객체의 메서드를 호출할 필요가 있을 때 데이터베이스를 조회해서 참조 값을 채우게 된다. 이를 프록시 객체 초기화라고한다.
단, 프록시 객체를 초기화를 한다는 것은 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 프록시 객체를 통해서 실제 엔티티에 접근하는것이 가능해진다는 점을 주의해야한다.
Member member = em.getReference(Member.class, “id1”);
member.getName(); // 프록시 초기화
- 프록시 객체에 member.getName()을 호출하여 실제 데이터를 조회한다.
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에서 실제 엔티티 생성을 요청핳는데 이것을 초기화라고 한다.
- 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버 변수에 보관한다.
- 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
프록시 객체 초기화시 주의점
프록의 초기화는 영속성 컨텍스트의 도움을 받는다.
때문에 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 즉 트랜잭션 범위 밖에서 프록시 객체를 초기화하려고 하면 문제 발생가 발생한다.
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)
프록시 초기화할때에는 반드시 프록시가 영속상태여야 한다는 점을 주의해야한다.
Member member = new Member();
member.setUsername("creator");
em.persist(member);
em.flush();
em.clear();
Member reference = em.getReference(Member.class, member.getId());
em.detach(reference);
//em.close도 동일
System.out.println("findMember.username = " + reference.getUsername());
tx.commit();
프록시 객체와 원본 객체 타입비교
프록시는 원본 엔티티의 자식 타입이므로 Instanceof 연산을 사용해야 한다.
Member find = em.find(Member.class, member1.getId());
Member reference = em.getReference(Member.class, member2.getId());
System.out.println("find == reference : " + (find == reference));
// find == reference : false
System.out.println("find : " + (find instanceof Member));
System.out.println("reference : " + (reference instanceof Member));
// find : true
// reference : true
영속성 컨텍스트 특징 - 동일성 보장
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.**getReference()**를 호출해도 실제 엔티티를 반환한다.
JPA는 하나의 영속성 컨텍스트에서 조회하는 같은 엔티티의 동일성을 보장한다.
→ JPA가 기본적으로 제공하는 매커니즘 중 하나인 반복가능한 읽기(repeatable read)이다.
Member find = em.find(Member.class, member.getId());
Member reference = em.getReference(Member.class, member.getId());
System.out.println(find == reference); // true
만약, 둘 다 getReference()로 가져오면 어떻게 될까?
둘 다 같은 프록시 객체가 된다. 프록시 객체도 위에서 말했던 것처럼 한 트랜잭션에서 조회하는 같은 엔티티의 동일성을 보장한다.
Member reference1 = em.getReference(Member.class, member.getId());
Member reference2 = em.getReference(Member.class, member.getId());
System.out.println(reference1 == reference2); // true
프록시 객체를 먼저 가져온 후 find()로 실제 객체를 조회한다면 어떻게 될까?
한 트랙잭션에 조회하는 같은 엔티티의 동일성을 보장하기 위해서 둘 다 같은 프록시 객체를 반환한다.
Member reference = em.getReference(Member.class, member.getId());
Member find = em.find(Member.class, member.getId());
System.out.println("reference == find : " + (reference == find)); // true
프록시의 equals 재정의
- getClass 비교가 아닌 instanceOf를 이용하여 비교해야한다.
- 프록시 객체의 값을 꺼낼때에는 getter를 사용하도록 변경해야한다.
@Override
public boolean equals(Object obj) {
...
if(this.getClass() != obj.getClass()) return false;
...
Member member = (Member) obj;
...
if(name!=null ? !name.equals(member.name) : member.name != null) return false;
...
}
@Override
public boolean equals(Object obj) {
...
if(!(obj **instanceof** Employee)) return false;
...
Member employee = (Member) obj;
...
if(name!=null ? !name.equals(member.getName()) : member.getName() != null) return false;
...
}
프록시와 식별자
엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달하는데 프록시 객체는 이 식별 값을 보관한다.
Team team = em.getReference(Team.class, "team1"); //식별자 보관
team.getId(); // 초기화 되지 않습니다.
프록시 객체는 식별자 값을 가지고 있으므로 식별자 값을 조회하면 team.getId()를 호출해도 프록시를 초기화 하지 않는다.
연관관계 설정시에 데이터베이스의 접근 횟수를 줄일 수 있다.
Member member = em.getReference(Member.class, "member1");
Team team = em.getReference(Team.class, "team1"); //식별자 보관
member.setTeam(team);
실제로 회원 엔티티가 팀 엔티티와 연관관계를 설정할 때 팀 엔티티를 데이터베이스에서 영속성 컨텍스트로 가져오지 않고 팀 엔티티의 식별자 값만 가지고 있는 프록시를 사용하면 데이터베이스 접근 횟수를 줄일 수 있다.
프록시 확인
JPA가 제공하는 PersistenceUnitUtil.isLoaded(Object entity) 메서드를 사용하면 프록시 인스턴스의 초기화 여부 확인할 수 있고, entity.getClass().getName() 메서드로 프록시 클래스를 확인할 수 있다.
// member 식별자 값을 가지고 있는 프록시를 반환
Member member = em.getReference(Member.class, "member1");
// 프록시 초기화
System.out.println("회원이름: " + member.getName()); // 회원이름 : 회원명
boolean isProxy = em.getEntityManagerFactory()
.getPersistenceUnitUtil().isLoaded(member);
System.out.println("isProxy: " + isProxy); // isProxy : true
System.out.println("memberProxy = " + member.getClass().getName()); // memberProxy : com.jpa...Member$HibernateProxy$
지연로딩과 즉시로딩
지연로딩
@Entity
public class Member{
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column( name="name", nullable=false, length=100 )
private String name;
**@ManyToOne(fetch = FetchType.LAZY)**
@JoinColumn(name="team_id")
private Team team;
}
public static void lazy(EntityManager em) {
Member member = em.getReference(Member.class, 1);
Team team = member.getTeam();
System.out.println(member.getName());
System.out.println(team.getName());
}
Member와 Team 엔티티가 연관관계를 가지고 있을때, @ManyToOne 어노테이션의 속성으로 fetch 값이 FetchType.LAZY이면 지연로딩 방식을 의미한다.
Memebr 객체를 조회할 때 select 쿼리가 수행되며, member.getTeam() 메서드가 호출되어야 Team에 대한 조회가 일어난다.
위에서 설명한 것처럼 프록시 객체를 사용하여 지연로딩을 처리하는 것이다.
즉시로딩
@Entity
public class Member{
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column( name="name", nullable=false, length=100 )
private String name;
**@ManyToOne(fetch = FetchType.EAGER)**
@JoinColumn(name="team_id")
private Team team;
}
public static void eager(EntityManager em) {
Member member = em.getReference(Member.class, 1);
Team team = member.getTeam();
System.out.println(member.getName());
System.out.println(team.getName());
}
Member와 Team 엔티티가 연관관계를 가지고 있을때, @ManyToOne 어노테이션의 속성으로 fetch 값이 FetchType.EAGER이면 즉시로딩 방식을 의미한다.
Member를 조회하는 find 메서드를 호출할 때 조인이 일어나며, 그 때 연관관계에 있는 Team도 같이 조회한다.
이 때 외래키의 값에 nul이 허용되어서 outer join이 실행된다.
@JoinColumn 어노테이션에서 nullable 속성을 추가할 수 있으며, 기본 값으로 true를 가진다. @JoinColumn(name="team_id", nullable=false) 로 수정하면 즉시 로딩 할 때 outer join이 아닌 inner join이 수행하게 된다.
outer join보다 inner join이 성능이 더 좋다는 것을 감안하여 외래키의 null 허용 여부를 결정해야 한다.
참고
'spring 🍀 > spring-data-jpa' 카테고리의 다른 글
@MappedSuperclass 사용법 (0) | 2023.11.23 |
---|---|
JPA N+1 문제 해결과정 ( join fetch + CountQuery) (0) | 2023.11.23 |
JPA의 Entity는 기본 생성자가 왜 반드시 필요할까? (1) | 2023.11.20 |
EntityManager와 EntityManagerFactory (0) | 2023.11.20 |
엔티티 생명주기 - 비영속, 영속, 준영속, 삭제 (0) | 2023.11.20 |
한 엔티티 조회시 연관되어 있는 엔티티들을 모두 조회하는 것보다는 필요한 연관관계만 조회해 오는 것이 효과적이다.
Member 객체와 연관되어있는 Team객체가 있다고 했을 때, 비지니스 로직에서 Team객체가 필요하지 않은 경우에 Member와 Team을 같이 조회하게 되면 낭비가 발생한다.
이처럼 우리는 때로 Member 객체만 조회하고 싶을 때가 있다. 이런 상황을 위해 JPA는 프록시와 지연로딩이라는 방식을 지원한다.
❓ 여기서 잠깐! 지연로딩과 즉시로딩을 간단하게 알아보자! (밑에서 더 자세히 설명할 예정)
지연로딩 : 자신과 연관된 엔티티를 실제로 사용할 때 연관된 엔티티를 조회하는 방식
즉시 로딩 : 자신과 연관된 엔티티를 조인을 통해 함께 조회하는 방식
지연로딩과 프록시(Proxy)
JPA 프록시는 연관된 객체들을 데이터베이스에서 조회하기 위해 사용한다.
지연로딩과 프록시를 사용하면 처음에 데이터베이스를 조회할때 연관된 객체들의 조회 결과는 실제 엔티티 객체가 아닌 프록시라는 가짜 엔티티 객체인 것이다. 그 이후에 연관된 객체가 실제 사용하는 시점에서 데이터베이스를 조회해 실제 엔티티 객체를 가져오게 된다.
Team team = new Team("팀");
em.persist(team);
Member member = new Member("멤버");
member.setTeam(team);
em.persist(member);
em.clear();
Member persistMember = em.find(Member.class, member.getId); // select member 쿼리 발생
System.out.println(persistMember.getTeam().getName()); // selcet team 쿼리 발생
지연로딩을 사용하면, member객체를 조회할때 member만 데이터베이스에서 조회한다. 그 이후 member와 연관된 객체인 team이 실제 사용되면 persistMember.getTeam().getName()을 호출했을 때 데이터베이스에서 Team 객체를 조회하게 된다.
정리하자면 연관된 객체들을 처음부터 데이터베이스에서 조회하는 것이 아니라 실제 사용하는 시점에 데이터베이스에서 조회하게 된다.
프록시 호출
프록시를 얻으려면 getReference() 메서드를 사용하면 된다.
em.find() vs em.getReference()
- em.find()메서드는 DB를 통해서 실제 엔티티 객체를 조회하는 메서드이고,
- em.getReference()는 DB조회를 미루는 가짜 프록시 엔티티 객체를 조회하는 메서드이다.
프록시는 실제 객체의 상속본이다.
프록시는 어떻게 실제 객체처럼 동작할 수 있을까?
이는 프록시가 실제 클래스를 상속받아서 만들어지기 때문이다.
프록시 객체는 실제 객체의 참조(target)를 보관하여, 프록시 객체의 메서드를 호출했을 때 실제 객체의 메서드를 호출한다.
실제 클래스와 겉 모습이 같기 때문에 사용자 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용해도 된다.
프록시를 사용하기 때문에 JPA 엔티티 생성의 중요 규칙이 생겼다. 기본 생성자는 반드시 필요하며 최소 protected 접근 제한자를 가져야하며, 프록시가 실제 엔티티 클래스를 상속받아야 하기 때문에 엔티티 클래스를 final로 선언할 수 없다.
이 내용은 아래 포스팅에 자세히 적어놨으니 참고. ✨
JPA의 Entity는 기본 생성자가 왜 반드시 필요할까? https://it-jin-developer.tistory.com/60
프록시 객체의 초기화
최초 지연 로딩시에 프록시는 당연히 참조값을 가지고 있지 않다.
프록시 객체는 member.getName()처럼 실제 객체의 메서드를 호출할 필요가 있을 때 데이터베이스를 조회해서 참조 값을 채우게 된다. 이를 프록시 객체 초기화라고한다.
단, 프록시 객체를 초기화를 한다는 것은 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 프록시 객체를 통해서 실제 엔티티에 접근하는것이 가능해진다는 점을 주의해야한다.
Member member = em.getReference(Member.class, “id1”);
member.getName(); // 프록시 초기화
- 프록시 객체에 member.getName()을 호출하여 실제 데이터를 조회한다.
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에서 실제 엔티티 생성을 요청핳는데 이것을 초기화라고 한다.
- 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버 변수에 보관한다.
- 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
프록시 객체 초기화시 주의점
프록의 초기화는 영속성 컨텍스트의 도움을 받는다.
때문에 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 즉 트랜잭션 범위 밖에서 프록시 객체를 초기화하려고 하면 문제 발생가 발생한다.
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)
프록시 초기화할때에는 반드시 프록시가 영속상태여야 한다는 점을 주의해야한다.
Member member = new Member();
member.setUsername("creator");
em.persist(member);
em.flush();
em.clear();
Member reference = em.getReference(Member.class, member.getId());
em.detach(reference);
//em.close도 동일
System.out.println("findMember.username = " + reference.getUsername());
tx.commit();
프록시 객체와 원본 객체 타입비교
프록시는 원본 엔티티의 자식 타입이므로 Instanceof 연산을 사용해야 한다.
Member find = em.find(Member.class, member1.getId());
Member reference = em.getReference(Member.class, member2.getId());
System.out.println("find == reference : " + (find == reference));
// find == reference : false
System.out.println("find : " + (find instanceof Member));
System.out.println("reference : " + (reference instanceof Member));
// find : true
// reference : true
영속성 컨텍스트 특징 - 동일성 보장
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.**getReference()**를 호출해도 실제 엔티티를 반환한다.
JPA는 하나의 영속성 컨텍스트에서 조회하는 같은 엔티티의 동일성을 보장한다.
→ JPA가 기본적으로 제공하는 매커니즘 중 하나인 반복가능한 읽기(repeatable read)이다.
Member find = em.find(Member.class, member.getId());
Member reference = em.getReference(Member.class, member.getId());
System.out.println(find == reference); // true
만약, 둘 다 getReference()로 가져오면 어떻게 될까?
둘 다 같은 프록시 객체가 된다. 프록시 객체도 위에서 말했던 것처럼 한 트랜잭션에서 조회하는 같은 엔티티의 동일성을 보장한다.
Member reference1 = em.getReference(Member.class, member.getId());
Member reference2 = em.getReference(Member.class, member.getId());
System.out.println(reference1 == reference2); // true
프록시 객체를 먼저 가져온 후 find()로 실제 객체를 조회한다면 어떻게 될까?
한 트랙잭션에 조회하는 같은 엔티티의 동일성을 보장하기 위해서 둘 다 같은 프록시 객체를 반환한다.
Member reference = em.getReference(Member.class, member.getId());
Member find = em.find(Member.class, member.getId());
System.out.println("reference == find : " + (reference == find)); // true
프록시의 equals 재정의
- getClass 비교가 아닌 instanceOf를 이용하여 비교해야한다.
- 프록시 객체의 값을 꺼낼때에는 getter를 사용하도록 변경해야한다.
@Override
public boolean equals(Object obj) {
...
if(this.getClass() != obj.getClass()) return false;
...
Member member = (Member) obj;
...
if(name!=null ? !name.equals(member.name) : member.name != null) return false;
...
}
@Override
public boolean equals(Object obj) {
...
if(!(obj **instanceof** Employee)) return false;
...
Member employee = (Member) obj;
...
if(name!=null ? !name.equals(member.getName()) : member.getName() != null) return false;
...
}
프록시와 식별자
엔티티를 프록시로 조회할 때 식별자(PK) 값을 파라미터로 전달하는데 프록시 객체는 이 식별 값을 보관한다.
Team team = em.getReference(Team.class, "team1"); //식별자 보관
team.getId(); // 초기화 되지 않습니다.
프록시 객체는 식별자 값을 가지고 있으므로 식별자 값을 조회하면 team.getId()를 호출해도 프록시를 초기화 하지 않는다.
연관관계 설정시에 데이터베이스의 접근 횟수를 줄일 수 있다.
Member member = em.getReference(Member.class, "member1");
Team team = em.getReference(Team.class, "team1"); //식별자 보관
member.setTeam(team);
실제로 회원 엔티티가 팀 엔티티와 연관관계를 설정할 때 팀 엔티티를 데이터베이스에서 영속성 컨텍스트로 가져오지 않고 팀 엔티티의 식별자 값만 가지고 있는 프록시를 사용하면 데이터베이스 접근 횟수를 줄일 수 있다.
프록시 확인
JPA가 제공하는 PersistenceUnitUtil.isLoaded(Object entity) 메서드를 사용하면 프록시 인스턴스의 초기화 여부 확인할 수 있고, entity.getClass().getName() 메서드로 프록시 클래스를 확인할 수 있다.
// member 식별자 값을 가지고 있는 프록시를 반환
Member member = em.getReference(Member.class, "member1");
// 프록시 초기화
System.out.println("회원이름: " + member.getName()); // 회원이름 : 회원명
boolean isProxy = em.getEntityManagerFactory()
.getPersistenceUnitUtil().isLoaded(member);
System.out.println("isProxy: " + isProxy); // isProxy : true
System.out.println("memberProxy = " + member.getClass().getName()); // memberProxy : com.jpa...Member$HibernateProxy$
지연로딩과 즉시로딩
지연로딩
@Entity
public class Member{
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column( name="name", nullable=false, length=100 )
private String name;
**@ManyToOne(fetch = FetchType.LAZY)**
@JoinColumn(name="team_id")
private Team team;
}
public static void lazy(EntityManager em) {
Member member = em.getReference(Member.class, 1);
Team team = member.getTeam();
System.out.println(member.getName());
System.out.println(team.getName());
}
Member와 Team 엔티티가 연관관계를 가지고 있을때, @ManyToOne 어노테이션의 속성으로 fetch 값이 FetchType.LAZY이면 지연로딩 방식을 의미한다.
Memebr 객체를 조회할 때 select 쿼리가 수행되며, member.getTeam() 메서드가 호출되어야 Team에 대한 조회가 일어난다.
위에서 설명한 것처럼 프록시 객체를 사용하여 지연로딩을 처리하는 것이다.
즉시로딩
@Entity
public class Member{
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column( name="name", nullable=false, length=100 )
private String name;
**@ManyToOne(fetch = FetchType.EAGER)**
@JoinColumn(name="team_id")
private Team team;
}
public static void eager(EntityManager em) {
Member member = em.getReference(Member.class, 1);
Team team = member.getTeam();
System.out.println(member.getName());
System.out.println(team.getName());
}
Member와 Team 엔티티가 연관관계를 가지고 있을때, @ManyToOne 어노테이션의 속성으로 fetch 값이 FetchType.EAGER이면 즉시로딩 방식을 의미한다.
Member를 조회하는 find 메서드를 호출할 때 조인이 일어나며, 그 때 연관관계에 있는 Team도 같이 조회한다.
이 때 외래키의 값에 nul이 허용되어서 outer join이 실행된다.
@JoinColumn 어노테이션에서 nullable 속성을 추가할 수 있으며, 기본 값으로 true를 가진다. @JoinColumn(name="team_id", nullable=false) 로 수정하면 즉시 로딩 할 때 outer join이 아닌 inner join이 수행하게 된다.
outer join보다 inner join이 성능이 더 좋다는 것을 감안하여 외래키의 null 허용 여부를 결정해야 한다.
참고
'spring 🍀 > spring-data-jpa' 카테고리의 다른 글
@MappedSuperclass 사용법 (0) | 2023.11.23 |
---|---|
JPA N+1 문제 해결과정 ( join fetch + CountQuery) (0) | 2023.11.23 |
JPA의 Entity는 기본 생성자가 왜 반드시 필요할까? (1) | 2023.11.20 |
EntityManager와 EntityManagerFactory (0) | 2023.11.20 |
엔티티 생명주기 - 비영속, 영속, 준영속, 삭제 (0) | 2023.11.20 |