자바 ORM 표준 JPA 프로그래밍 - 기본편 - sec08
출처 : JPA 기본편
만약 멤버를 조회할 때 팀도 같이 조회하고 싶을 때가 생기면 어떻게 하면 좋을까?!
이걸 해결하려면, 프록시의 기초부터 알아봐야한다고 한다
em.find() VS em.getReference()
em.find() => 데이터베이스를 통해서 실제 엔티티 객체 조회
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
jpa가 데이터를 다 이미 조인해서와서 가져와줌, 값이 출력되긴 함
em.getReference() => 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
위에 코드와 동일하게 가되, 이제 getReference()로 변경할 경우
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
Member findMember = em.getReference(Member.class, member.getId());
결과가 출력되지 않음...
하지만!
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
쿼리가 나가고 결과가 출력된 것을 볼 수 있음!
id의 경우, 이미 우리가 이 값을 찾을 때 파라미터로 값을 넣었기 때문에 쿼리가 나가지 않고 바로 출력되었음! 하지만, username의 경우에는 db에 있기 때문에 username을 실제로 호출하는 시점(getUsername())에 쿼리를 날려서 값을 가져옴!
실제로 findMember의 정체가 무엇인지 클래스를 출력해보면
class hellojpa.Member$HibernateProxy$odcVHpjy
라고 나옴! => 하이버네이트가 강제로 만든 가짜 클래스라는 뜻
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass());
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
반대의 경우도 동일!
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
Member findMember = em.find(Member.class, member1.getId());
System.out.println("m1 = " + m1.getClass());
System.out.println("refMember == findMember: " + (refMember == findMember)); //JPA에서는 이 둘이 true가 나오도록 보장해줘야 함
그래서 둘다 proxy로 조회가 됨!
org.hibernate.LazyInitializationException
예외를 터뜨림!)Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass());
em.detach(refMember); //너 더이상 영속성 컨텍스트에서 관리안할거야 하고 끄집어냄!
refMember.getUsername(); //당연히 출력이 안되쥬
PersistenceUnitUtil.isLoaded(Object entity)
entity.getClass().getName() 출력(..javasist.. or HibernateProxy…)
org.hibernate.Hibernate.initialize(entity);
강제 호출: member.getName()
다시 처음으로 돌아가서 우리가 멤버를 조회할 때 팀까지 같이 조회하고 싶어지면 어떻게 해야할까?
지연 로딩 LAZY를 사용해서 프록시로 조회하자!
@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;
}
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();
Member m = em.find(Member.class, member1.getId());
System.out.println("m = " + m.getTeam().getClass());
//프록시로 출력됨
m.getTeam().getName();
//프록시가 초기화됨, 실제 team을 사용하는 시점
근데 만약에 우리가 팀과 멤버가 항상 같이 나와야하는 경우라면 어떻게 하면 좋을까?!
즉시 로딩 EAGER를 사용해서 함께 조회하자!
@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;
}
JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때 ex)부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);
//3번을 써야 쿼리가 다 나가서 저장이 됨
하지만 cascade를 써주게 되면 em.persist(parent)
만 해도 child까지 다 저장이 됨!
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거
• DELETE FROM CHILD WHERE ID=?
CascadeType.REMOVE
처럼 동작함CascadeType.ALL + orphanRemovel=true
em.persist()
로 영속화, em.remove()
로 제거