em.find()
: 데이터베이스를 통해서 실제 엔티티 객체 조회em.getReference()
: 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회EntityManager.find()
를 사용한다. 이 메서드는 영속성 컨텍스트에 엔티티가 없으면 데이터베이스를 조회한다. 이렇게 엔티티를 직접 조회하면 조회한 엔티티를 실제 사용하든 사용하지 않든 데이터베이스를 조회하게 된다.EntityManager.getReference()
를 사용한다. 이 메서드를 호출할 때 JPA는 데이터베이스를 조회하지 않고 실제 엔티티 객체도 생성하지 않는다. 대신에 데이터베이스 접근을 위임한 프록시 객체를 반환한다. Ex. member.getName()
) 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는데 이것을 프록시 객체의 초기화라고 한다.instanceOf
를 사용해서 비교em.getReference()
를 호출해도 프록시가 아닌 실제 엔티티가 반환된다.LazyInitializationException
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Team team = new Team();
team.setName("그냥 팀");
em.persist(team);
Member member1 = new Member();
member1.setName("그냥 이름");
member1.setTeam(team);
member1.addTeamMember(team);
em.persist(member1);
Member member2 = new Member();
member2.setName("그냥 이름2");
member2.setTeam(team);
member2.addTeamMember(team);
em.persist(member2);
em.flush();
em.clear();
Member findMember1 = em.find(Member.class, member1.getId());
Member findMember2 = em.getReference(Member.class, member2.getId());
logic(findMember1, findMember2);
// findMember1.class = class hellojpa.domain.Member
// findMember2.class = class hellojpa.domain.Member$HibernateProxy$AyFTuwby
// false
// findMember1 == findMember2 → true
// findMember1 == findMember2 → true
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
em.close();
}
emf.close();
}
private static void logic(Member findMember1, Member findMember2) {
System.out.println("findMember1.class = " + findMember1.getClass());
System.out.println("findMember2.class = " + findMember2.getClass());
System.out.println(findMember1.getClass() == findMember2.getClass());
System.out.println("findMember1 == findMember2 → " + (findMember1 instanceof Member));
System.out.println("findMember1 == findMember2 → " + (findMember2 instanceof Member));
}
}
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("ex");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try {
Member member1 = new Member();
member1.setName("hello1");
em.persist(member1);
Member member2 = new Member();
member2.setName("hello2");
em.persist(member2);
em.flush();
em.clear();
Member m1 = em.getReference(Member.class, member1.getId());
em.detach(m1);
System.out.println(m1.getName());
et.commit();
} catch (Exception e) {
et.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행 결과
org.hibernate.LazyInitializationException: could not initialize proxy [org.example.Member#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:314)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102)
at org.example.Member$HibernateProxy$Nx2kSwiV.getName(Unknown Source)
at org.example.Main.main(Main.java:27)
6월 19, 2024 5:27:21 오후 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/ex]
프록시 객체에서member1.getId()
메서드를 호출하면 실제 엔티티 객체를 영속성 컨텍스트에서 찾게 되나 flush()
메서드와 clear()
메서드에 의해 영속성 컨텍스트는 비워진 상황이므로 영속성 컨텍스트에게 데이터베이스에서 실제 엔티티 객체를 찾아달라고 요청을 하면 영속성 컨텍스트가 데이터베이스를 조회해서 찾게 된다.
하지만 영속성 컨텍스트에서 Member
객체를 준영속 상태로 전환하면 영속성 컨텍스트가 관리할 수 있는 범주를 벗어가게 되는데 이 때, getName()
메서드를 호출하면 초기화할 수 없다는 오류가 발생하게 되는 것이다.
PersistenceUnitUtil.isLoaded(Object Entity)
entity.getClass()
org.hibernate.Hibernate.initalize(entity)
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Team team = new Team();
team.setName("그냥 팀");
em.persist(team);
Member member1 = new Member();
member1.setName("그냥 이름");
member1.setTeam(team);
member1.addTeamMember(team);
em.persist(member1);
Member member2 = new Member();
member2.setName("그냥 이름2");
member2.setTeam(team);
member2.addTeamMember(team);
em.persist(member2);
em.flush();
em.clear();
Member findMember2 = em.getReference(Member.class, member2.getId());
System.out.println("isLoaded : " + emf.getPersistenceUnitUtil().isLoaded(findMember2));
System.out.println(findMember2.getClass());
org.hibernate.Hibernate.initialize(findMember2);
System.out.println("isLoaded : " + emf.getPersistenceUnitUtil().isLoaded(findMember2));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
em.close();
}
emf.close();
}
}
@Entity
@Table(name = "MEMBERS")
public class Member extends BaseEntity {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
@ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 옵션 적용
@JoinColumn(name = "TEAM_ID")
private Team team;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Team extends BaseEntity{
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "team")
private List<Member> members = new ArrayList<>();
}
실행 결과
Hibernate:
select
m1_0.MEMBER_ID,
m1_0.city,
m1_0.name,
m1_0.street,
m1_0.TEAM_ID,
m1_0.zipcode
from
MEMBERS m1_0
where
m1_0.MEMBER_ID=?
EntityManager.find()
메서드를 호출하면 회원만 조회하고 팀은 조회하지 않는다.Team
멤버변수에 프록시 객체를 넣어둔다.public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("shop");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try {
Team team = new Team();
team.setName("team");
em.persist(team);
Member member = new Member();
member.setName("member");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Member reference = em.find(Member.class, member.getId());
System.out.println("reference = " + reference.getTeam().getClass());
System.out.println("=======================");
reference.getTeam().getName(); // 초기화
System.out.println("=======================");
et.commit();
} catch (Exception e) {
et.rollback();
} finally {
em.close();
}
emf.close();
}
}
실행 결과
// Member reference = em.find(Member.class, member.getId()); 실행 결과
// Member만 조회
Hibernate:
select
m1_0.MEMBER_ID,
m1_0.city,
m1_0.createdBy,
m1_0.createdDate,
m1_0.modifiedBy,
m1_0.modifiedDate,
m1_0.name,
m1_0.street,
m1_0.TEAM_ID,
m1_0.zipcode
from
MEMBERS m1_0
where
m1_0.MEMBER_ID=?
// LAZY 지연로딩 옵션을 적용하면 프록시로 조회하게 된다.
// LAZY 옵션 적용 후 클래스 타입 확인 → 프록시
// System.out.println("reference = " + reference.getTeam().getClass());
// reference = class org.example.domain.Team$HibernateProxy$WIG5TOzh
=======================
// 프록시 객체에서 실제 엔티티 객체의 메서드를 호출하는 순간 팀에 대해서 조회함
// reference.getTeam().getName();
Hibernate:
select
t1_0.TEAM_ID,
t1_0.createdBy,
t1_0.createdDate,
t1_0.modifiedBy,
t1_0.modifiedDate,
t1_0.name
from
Team t1_0
where
t1_0.TEAM_ID=?
=======================
@Entity
@Table(name = "MEMBERS")
public class Member extends BaseEntity {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("shop");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try {
Team team = new Team();
team.setName("team");
em.persist(team);
Member member = new Member();
member.setName("member");
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
Member reference = em.find(Member.class, member.getId());
System.out.println("reference = " + reference.getTeam().getClass());
System.out.println("=======================");
reference.getTeam().getName(); // 초기화
System.out.println("=======================");
et.commit();
} catch (Exception e) {
et.rollback();
} finally {
em.close();
}
emf.close();
}
}
실행 결과
Hibernate:
select
m1_0.MEMBER_ID,
m1_0.city,
m1_0.createdBy,
m1_0.createdDate,
m1_0.modifiedBy,
m1_0.modifiedDate,
m1_0.name,
m1_0.street,
t1_0.TEAM_ID,
t1_0.createdBy,
t1_0.createdDate,
t1_0.modifiedBy,
t1_0.modifiedDate,
t1_0.name,
m1_0.zipcode
from
MEMBERS m1_0
left join
Team t1_0
on t1_0.TEAM_ID=m1_0.TEAM_ID
where
m1_0.MEMBER_ID=?
reference = class org.example.domain.Team
=======================
=======================
@JoinColumn
어노테이션에 제약 조건으로 nullable = false
를 사용하면 JPA는 외부 조인 대신에 내부 조인을 사용하게 된다@ManyToOne
, @OneToOne
은 기본이 즉시 로딩이므로 지연 로딩으로 바꿔야 한다.@OneToMany
, @ManyToMany
는 기본이 지연 로딩@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
public void addChild(Child child) {
children.add(child);
child.setParent(this);
}
// Getter/Setter ...
}
@Entity
public class Child {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
// Getter/Setter ...
}
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("ex");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try {
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);
et.commit();
} catch (Exception e) {
et.rollback();
} finally {
em.close();
}
emf.close();
}
}
실행 결과
Hibernate:
/* insert for
org.example.Parent */insert
into
Parent (name, id)
values
(?, ?)
Hibernate:
/* insert for
org.example.Child */insert
into
Child (name, parent_id, id)
values
(?, ?, ?)
Hibernate:
/* insert for
org.example.Child */insert
into
Child (name, parent_id, id)
values
(?, ?, ?)
cascade = CascadeType.ALL
옵션을 사용하면 Parent 클래스의 객체만 영속성 컨텍스트에 저장하면 연결된 Child 클래스의 객체들까지 같이 영속성 컨텍스트에 저장이 되는 것을 볼 수 있다.@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
///////////////////////////////////////////////////////////
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
// 연관관계 편의 메서드
public void addChild(Child child) {
children.add(child);
child.setParent(this);
}
///////////////////////////////////////////////////////////
// Getter/Setter ...
}
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("ex");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
try {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
///////////////////////
em.persist(parent); // 영속성 컨텍스트 저장
///////////////////////
et.commit();
} catch (Exception e) {
et.rollback();
} finally {
em.close();
}
emf.close();
}
}
실행 결과
Hibernate:
/* insert for
org.example.Parent */insert
into
Parent (name, id)
values
(?, ?)
Hibernate:
/* insert for
org.example.Child */insert
into
Child (name, parent_id, id)
values
(?, ?, ?)
Hibernate:
/* insert for
org.example.Child */insert
into
Child (name, parent_id, id)
values
(?, ?, ?)
@OneToOne
, @OneToMany
만 가능@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
// Getter/Setter ...
}
EntityManager.persist()
로 영속화, EntityManager.remove()
로 제거@Entity
@Table(name = "ORDERS")
public class Order extends BaseEntity {
@Id @GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MEMBER_ID")
private Member member;
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
}