인프런 김영한 강사님의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 을 수강하고 정리한 글입니다.
📌 프록시
✅ em.find() vs em.getReference()
em.find() : DB를 통해 실제 엔티티 객체를 조회한다.
em.getReference() : DB가 아니라, 가짜(프록시) 엔티티 객체를 조회한다.
em.getReference()의 경우, 분명 DB에 쿼리가 나가진 않는데, 객체 조회는 성공한다.
✅ 프록시
프록시 객체는 실제 클래스를 상속 받아 만들어졌기 때문에, 겉 모습이 같다.
사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하게 된다.
프록시 객체는 실제 객체의 참조(target)를 보관한다.
프록시 객체를 호출하게 되면, 프록시가 보관하던 참조에 의해 프록시 객체는 실제 객체의 메소드를 호출하게 된다.
✅ 프록시 객체 초기화
Member member = em.getReference(Member.class, "id1");
member.getName();
✅ 프록시 특징
프록시 객체는 처음 사용할 때 최초 한 번만 초기화한다.
프록시 객체가 초기화되면, 프록시 객체를 통해 실제 엔티티에 접근이 가능해진다.
프록시 객체는 원본 엔티티를 상속받기 때문에 타입 체크 시 주의해야 하는데, JPA에서는 왠만하면 == 비교 말고 instanceof를 추천한다.
영속성 컨텍스트에 찾고있는 엔티티가 이미 존재한다면, em.getReference()를 호출하더라도 DB 자체에 접근을 안하기 때문에 1차 캐시에 존재하는 실제 엔티티를 반환하게 된다.
다만, 영속성 컨텍스트의 기능을 이용할 수 없는 준영속 상태에서는 프록시를 초기화하면 문제가 생긴다.
Member member1 = new Member();
member1.setName("member1");
em.persist(member1);
em.flush();
em.clear();
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass());
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getClass());
System.out.println("a == b : " + (refMember == findMember)); // 같은 트랜잭션 안에서의 동일성 보장 -> 이 문장은 항상 true여야 함. 위 두개가 find getReference 중 어떤 조합들이 오더라도..;
// JPA에서 위 문장은 항상 참을 보장해야 함
// Dabase Transaction End!
tx.commit();
위 코드의 결과로, refMemer.getClass()와 findMember.getClass()는 서로 다른 클래스를 반환하게 된다.
getReference()는 프록시 클래스의 인스턴스를 반환하는 반면, find()는 실제 Member 클래스의 인스턴스를 반환하게 된다.
refMember == findMember는 true를 반환한다.
✅ 프록시 인스턴스 초기화 확인
PersistenceUnitUtil.isLoaded(Object entity) 명령어를 통해 프록시 인스턴스가 초기화 됐는지 여부를 확인할 수 있다.
프록시 클래스를 확인하는 방법은 entity.getClass().getName()을 출력하면 된다.
프록시를 강제 초기화 하고 싶다면 org.hibernate.initialize(entity) 명령어를 사용해라.
강제로 호출하고 싶다면 member.getName() 명령어를 사용해라.
📌 즉시 로딩과 지연 로딩
✅ 지연 로딩을 활용한 프록시 조회
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
지연 로딩을 세팅하면 기본적으로 가짜 프록시 객체를 조회한다.
그리고 실제로 그 객체를 사용하는 시점에 실제 객체로 초기화한다. (DB 조회)
✅ 즉시 로딩을 활용한 조회
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
즉시 로딩을 세팅하면, member를 조회할 때 Team도 같이 조회하게 됩니다. (프록시 말고 진짜 객체)
✅ 지연 로딩에 관한 주의점
실무에서는 가급적 지연 로딩만 사용한다.
: 즉시 로딩은 예상하지 못한 쿼리가 나갈 확률이 높기 때문이다.
: 또한 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
@ManyToOne과 @OneToOne은 기본 값으로 즉시 로딩이 세팅되어 있다.
=> 개발자가 임의로 지연 로딩을 설정해야 한다.
@OneToMany와 @ManyToMany는 기본 값이 지연 로딩으로 세팅되어 있어 별도 설할 필요가 없다.
📌 영속성 전이(CASCADE)와 고아 객체
✅ 영속성 전이 : CASCADE
특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용한다.
ex> 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장하는 경우.
영속성 전이는 연관관계를 매핑하는 것과는 아무런 관련이 없다.
그저 엔티티 영속화 시 연관된 엔티티도 함께 영속화해주는 편리함을 제공할 뿐이다.
✅ 영속성 전이 : 저장
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
✅ CASCADE의 종류
- ALL : 모두 적용
- PERSIST : 영속
- REMOVE : 삭제
- MERGE : 병합
- REFRESH : 엔티티의 상태를 DB의 현재 상태와 동기화
- DETACH : 엔티티를 영속성 컨텍스트에서 제거 (준영속)
✅ 고아 객체
부모 엔티티와 연관관계가 끊어진 자식을 말한다.
이 고아 객체를 자동으로 삭제해주는 편리한 옵션이 있다.
orphanRemoval = true
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 판단하고 삭제하는 기능이다.
단, 참조하는 곳이 하나일 때 사용해야 한다. (특정 엔티티가 개인 소유할 때)
@OneToOne, @OneToMany만 사용 가능하다.
✅ 영속성 전이 + 고아 객체
CascadeType.ALL + orphanRemoval = true
두 가지를 같이 사용하는 것
두 옵션을 모두 활성화하면 부모 엔티티를 통해 자식의 생명 주기를 관리할 수 있다.
'Backend > JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 10. 객체 지향 쿼리 언어 1 (0) | 2024.01.05 |
---|---|
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 9. 값 타입 (0) | 2024.01.05 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 7. 고급 매핑 (0) | 2024.01.04 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 6. 다양한 연관관계 매핑 (0) | 2024.01.04 |
[자바 ORM 표준 JPA 프로그래밍 - 기본편] 5. 연관관계 매핑 기초 (0) | 2024.01.04 |
개발자가 되고 싶어요.