@Configuration
public class JpaMain {
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
tx.begin();
Member member = new Member();
member.setName("abc");
member.setEmail("aaa@gmail.com");
member.setPhone("010-5555-5555");
Point point = new Point();
point.setPointCount(5);
member.setPoint(point);
em.persist(member);
tx.commit();
em.clear();
Member refMember = em.getReference(Member.class, member.getMemberId());
System.out.println("refMember = " + refMember.getClass());
System.out.println("==================================================");
System.out.println("refMember.getMemberId() = " + refMember.getMemberId());
System.out.println("==================================================");
System.out.println("refMember.getEmail() = " + refMember.getEmail());
System.out.println("==================================================");
System.out.println("refMember = " + refMember.getClass());
};
}
}
persist()
로 멤버를 영속화 시켜 주고 commit()
까지 완료 하였다.clear()
로 초기화 후 getReference()
를 호출하여 프록시 객체를 가져 왔다.getReference()
해도 영속화 된 실제 엔티티 데이터를 가져온다.refMember = class com.example.mappingprac.domain.Member$HibernateProxy$jpogeW8z
를 살펴 보면 getReference()
를 호출하여 HibernateProxy 객체가 할당 된 것을 볼수 있다.refMember.getMemberId()
가 호출되는 시점에는 쿼리문이 날아가지 않고 데이터가 출력되는 것을 확인 할수 있는데, 이는 프록시로 엔티티를 조회할때, PK 값은 파라미터로 전달되어 프록시 객체를 식별할수 있는 식별자 값으로 보관하기 때문 이다.refMember.getEmail()
를 호출하는 시점에 쿼리문이 날아간 것을 볼수 있는데,refMember = class com.example.mappingprac.domain.Member$HibernateProxy$jpogeW8z
를 살펴 보면 첫번째 줄의 내역과 같은 것을 확인 할수 있다.PersistenceUnitUtil.isLoaded(Object object)
메소드를 사용하여 boolean
타입으로 조회 할수 있다.// Member.java
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member extends Audit{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String phone;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "point_id")
private Point point;
}
// Point.java
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Point extends Audit{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long pointId;
private int pointCount;
}
@OneToOne(fetch = FetchType.EAGER)
와 같이 에트리뷰트를 설정 해 주면 된다.@XxxToOne
어노테이션은 default 값이 EAGER 라서 설정할 필요가 없다.@OneToOne(fetch = FetchType.LAZY)
와 같이 에트리뷰트를 설정 해 주면 된다.@XxxToMany
어노테이션은 default 값이 LAZY다.//Member.java
//........
@OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name = "point_id")
private Point point;
//......
//JpaMain.java
@Configuration
public class JpaMain {
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
tx.begin();
Member member = new Member();
member.setName("abc");
member.setEmail("aaa@gmail.com");
member.setPhone("010-5555-5555");
Point point = new Point();
point.setPointCount(5);
member.setPoint(point);
em.persist(member);
tx.commit();
em.clear();
System.out.println("============== FindMember ===================");
Member findMember = em.find(Member.class, member.getMemberId());
System.out.println("findMember.Point = " + findMember.getPoint().getClass());
System.out.println("==================================================");
System.out.println("refMember.getMemberId() = " + findMember.getPoint().getPointCount());
System.out.println("==================================================");
System.out.println("findMember.Point = " + findMember.getPoint().getClass());
};
}
}
@OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
로 포인트 객체를 참조하고 있는 필드에 에트리뷰트를 추가 해 주었다.em.find(Member.class, member.getMemberId());
를 호출할떄, 멤버 테이블에 쿼리문이 한번 날아가고 프록시 예제와 다르게 포인트 테이블에 대한 조인 쿼리문은 날아가지 않았다.findMember.Point = class com.example.mappingprac.domain.Point$HibernateProxy$nC7VQr4h
로 프록시 객체를 가지고 있다.findMember.getPoint().getPointCount()
를 호출할때 포인트 테이블에 쿼리문이 날아가는 것을 확인할 수 있다. 지연 로딩의 쿼리문 호출 시점이다.단순한 엔티티 연관 관계에서는 필요성을 확인할 수 없지만, 복잡한 엔티티 관계에서는 필히 지연 로딩을 설정하는 것이 좋다.
즉시 로딩을 사용하게 되면 예상하지 못한 Join 쿼리문이 날아가게 된다.
즉시 로딩은 동적 쿼리문을 사용하거나 복잡한 쿼리문을 날릴떄 N+1 문제를 야기한다.
코드가 길어 아래 블럭을 확인을 하면 된다.
@Configuration
public class JpaMain {
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
tx.begin();
Member member = new Member();
member.setName("abc");
member.setEmail("aaa@gmail.com");
member.setPhone("010-5555-5555");
Member member1 = new Member();
member1.setName("bbb");
member1.setEmail("bbb@gmail.com");
member1.setPhone("010-8888-5555");
Member member2 = new Member();
member2.setName("ccc");
member2.setEmail("ccc@gmail.com");
member2.setPhone("010-3333-5555");
Member member3 = new Member();
member3.setName("ddd");
member3.setEmail("ddd@gmail.com");
member3.setPhone("010-4444-5555");
Member member4 = new Member();
member4.setName("eee");
member4.setEmail("eee@gmail.com");
member4.setPhone("010-1234-5555");
Point point = new Point();
Point point1 = new Point();
Point point2 = new Point();
Point point3 = new Point();
Point point4 = new Point();
point.setPointCount(5);
point1.setPointCount(4);
point2.setPointCount(3);
point3.setPointCount(2);
point4.setPointCount(1);
member.setPoint(point);
member1.setPoint(point1);
member2.setPoint(point2);
member3.setPoint(point3);
member4.setPoint(point4);
em.persist(member);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
tx.commit();
em.clear();
tx.begin();
List<Member> members = em
.createQuery("select m from Member m", Member.class)
.getResultList();
tx.commit();
};
}
}
em.createQuery("select m from Member m", Member.class)
를 이용하여 쿼리문을 날리면 출력물과 같이 Select 문이 한번 날아가고 5개의 포인트 Select 문이 날아가는 것을 확인할 수 있다.