
@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 문이 날아가는 것을 확인할 수 있다.