이 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 듣고 정리한 글입니다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = em.find(Member.class, 1L);
printMember(member);
printMemberAndTeam(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
private static void printMember(Member member) { // Member 호출
System.out.println("username = " + member.getUsername());
}
private static void printMemberAndTeam(Member member) { // Member와 Team 호출
String username = member.getUsername();
System.out.println("username = " + username);
Team team = member.getTeam();
System.out.println("team = " + team);
}
em.find()
vs em.getReference()
em.find()
: DB를 통해 실제 엔티티 객체 조회em.getReference()
: DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회 (DB에 쿼리가 안 날라가는데 객체가 조회됨)public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setUsername("hello");
em.persist(member);
em.flush();
em.clear();
// Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId()); // 💡
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
System.out.println("findMember = " + findMember.getClass());
Member member = em.getReference(Member.class, "id1"); // 프록시 객체를 조회
member.getName(); // 실제로 사용할 때, 초기화 과정이 일어남 (SELECT 쿼리 날림)
member.getName(); // 엔티티가 이미 있기 때문에 초기화가 필요X
==
비교 대신 instanceof
사용 (JPA에서 타입 체크시 유용) System.out.println((m1 instanceof Member)); // true
System.out.println((m2 instanceof Member)); // true
프록시가 아닌 Member와 프록시인 Member와 타입이 맞지 않기 때문
Member m1 = em.find(Member.class, member1.getId()); Member m2 = em.find(Member.class, member2.getId()); System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass())); // true ㅤ Member m1 = em.find(Member.class, member1.getId()); Member m2 = em.getReference(Member.class, member2.getId()); System.out.println("m1 == m2 : " + (m1.getClass() == m2.getClass())); // false
Member member = em.find(Member.class, member1.getId());
System.out.println("member = " + m1.getClass());
Member reference = em.getreference(Member.class, member1.getId());
System.out.println("reference = " + reference.getClass()); // 실제 엔티티 반환
// JPA에서 == 비교시 한 영속성 컨텍스트에서 가져왔거나, pk가 같으면 항상 true 반환
System.out.println("a == a" : + (m1 == reference));
- 프록시로 한 번 조회가 되면, em.find()에서도 프록시를 호출. (JPA에서 ==을 보장하기 위해)
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("refMember == findMember" : + (refMember == findMember));
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member1 = new Member();
member1.setUsername("hello");
em.persist(member1);
em.flush();
em.clear();
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass());
em.detach(refMember);
// 혹은, em.close();
// 혹은, em.clear();
refMember.getUsername();
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace(); // 예외 출력
} finally {
em.close();
}
emf.close();
}
}
PersistenceUnitUtil.isLoaded(Object entity)
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember)); // false 출력
refMember.getUsername(); // 강제 초기화
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember)); // true 출력
entity.getClass().getName()
출력 (..javasist.. or HibernateProxy…)org.hibernate.Hibernate.initialize(entity);
member.getName()
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY) // 프록시 객체 조회
@JoinColumn
private Team team;
}
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
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());
System.out.println("============");
m.getTeam().getName(); // 💡 실제 team을 사용하는 시점
System.out.println("============");
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace(); // 예외 출력
} finally {
em.close();
}
emf.close();
}
}
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.EAGER) // 프록시 객체 조회
@JoinColumn
private Team team;
}
try {
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();
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
// SQL: select * from Member
// SQL: select * from Team where TEAM_ID = XXX
tx.commit();
}
@ManyToOne
, @OneToOne
은 기본이 즉시 로딩@OneToMany
, @ManyToMany
는 기본이 지연 로딩@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
}
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);
// CASCADE를 설정하면 persist하지 않아도 자동으로 영속화됨
tx.commit();
}
orphanRemoval = true
@Entity
public class Parent {
@Id
@GeneratedValue
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);
}
}
try {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.flush();
em.clear();
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0); // 고아 객체 제거
tx.commit();
}
@OneToOne
, @OneToMany
만 가능em.remove(findParent);
)할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE
처럼 동작한다em.persist()
로 영속화, em.remove()
로 제거할 수 있음CascadeType.ALL
+ orphanRemovel=true
DDD(Domain Driven Design)의 Aggregate Root
DDD에서 Entity마다 Repository를 만드는 경우가 많은데, 이럴 때 여러 Entity를 묶어 하나처럼 사용하는 경우가 많다. Aggregate는 이러한 연관 객체의 묶음이다. 이 안에 포함되어 있는 특정 Entity를 Aggregate Root라고 한다. 여러 엔티티를 묶어서 가져오는 경우가 많을 땐 개발에서 Aggregate Root에 해당하는 Entity에 대해서만 Repository를 만드는 경우가 많다.
- Repository는 Aggregate Root만 선택하고, 나머지는 Repo를 만들지 않는 것이 더 낫다.
- Aggregate Root를 통해 하위의 Repo의 생명 주기를 관리한다.