jpa에서 타입 비교시에는 ==비교 대신,instance of를 사용해야한다.
(프록시가 충분히 넘어올 수 있으므로 주의하자!)
reference인데 왜 Member가 나오지?
1.Member를 영속성 컨텍스트에 올려놨으니까 원본을 반환하는게 성능 최적화 측면에서 훨씬 낫다.
2.m1이 실제 객체이든, 프록시이던 상관없이 jpa는 컬렉션에서 가져오듯이, 같은 영속성 컨텍스트 내에서 == 비교는 항상 true가 나오게된다.
JpaMain.java
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("user1");
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
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());
System.out.println("a = a :" + (m1 == reference));
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해 도 실제 엔티티 반환-> 그 반대도 마찬가지!
JpaMain.java
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("user1");
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass()); //Proxy
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getClass()); //Member
System.out.println("refMember == findMember:" + (refMember == findMember));
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
em.find로 찾은 findMember또한 프록시객체를 반환하는 것을 확인할 수 있다.
영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)
JpaMain.java
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("user1");
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass()); //Proxy
em.clear();
refMember.getUsername();
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
JpaMain.java
try {
Member member1 = new Member();
member1.setUsername("user1");
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass()); //Proxy
//refMember 얘가 로딩이 됐냐 안됐냐
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
tx.commit();
}
isLoaded = false, 초기화가 안되었다고 나옴.
초기화를 해보자
isLoaded = true가 나온 것을 확인 가능
Member.java
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Team team;
JpaMain.java
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("user1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
Member m = em.find(Member.class, member1.getId());
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
Member는 member대로 조회하고, 팀은 프록시로 조회된것을 확인할 수 있다.
JpaMain.java
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("user1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
Member m = em.find(Member.class, member1.getId());
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass());
System.out.println("=================");
m.getTeam().getName(); //팀을 건드려보자
System.out.println("=================");
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
팀의 속성을 건드릴때 쿼리가 나가는 것을 볼 수 있다.
팀의 속성을 사용하는 시점에 프록시 객체가 초기화 되면서, db에서 이 값을 가지고 오는 것.
팀을 가져올 때가 아니라 실제 team을 사용하는 시점에 초기화 된다.!
지연 로딩으로 세팅하면 연관된것을 프록시로 가지고 오는 것이다.
m,getTeam(); 은 단순히 프록시 객체를 가지고 오는 것이므로 이때 쿼리가 나가진 않는다. 프록시를 가져와서 어떠한 메소드(.getName())를 터치할 때, 프록시 내부 타겟에 값이 없기 때문에 이때 초기화가 일어나는 것이다.
membet와 team을 조인해서 member와 team을 한꺼번에 끌고온다.
이건 즉시로딩이므로 프록시가 필요 없다.
한방에 team까지 가지고 온 것이므로!
그래서 team의 클래스 타입을 조회해보면 프록시가 아니라 실제 Team객체가 나온것이다.
90프로 이상 대부분 멤버를 쓸 때 팀을 같이 쓰는 경우엔 즉시로딩이 유리.
즉시로딩은 멤버를 로딩할때, 팀까지 같이 조인해서 가지고 온다.
N+1문제 : 처음 쿼리가 한개 나갔는데 추가 쿼리가 N개가 나간다.
JpaMain.java
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("user1");
member1.setTeam(team);
em.persist(member1);
em.flush();
em.clear();//영속성 컨텍스트에 있는걸 db로 날리고,영속성 컨텍스트에 있는걸 깔끔하게 제거하니까 1차캐시에 아무것도 안남음.
//Member m = em.find(Member.class, member1.getId());
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
eager로 설정했는데 쿼리가 두번 나간다.
em.find는 딱 pk를 찍어서 가져오는 것이기 때문에 jpa가 내부적으로 다 찾아서 할 수 있다.
하지만 jpql은 그대로 sql로 번역이 된다.
"select m from Member m" -> select * from member
그럼 Member만 select한다. Member 데이터를 쭉 가져온다.
Member를 가지고 왔더니, team이 즉시로딩으로 되어있네?
즉시 로딩이라는건 가지고 올 때, 값이 무조건 다 들어있어야 함.
멤버 쿼리가 나가고, 멤버의 개수가 10개이면 10개 만큼, 쿼리가 별도로 나간다.
Member.java
모든 연관관계를 LAZY로 설정해두고 패치 조인을 통해 동적으로 해결
JpaMain.java
LAZY로 설정해뒀는데 join쿼리로 한방에 가져오는것을 확인 가능.
실무에서는 다 지연로딩을 사용!!
JpaMain.java
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);
tx.commit();
}
이렇게 persist 하나하나 따로 해주는게 번거로움.
parent를 persist할때 자동으로 child도 persist 되었음 좋겠어.
CascadeType.ALL을 해주면
JpaMain.java
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 {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
em.persist(parent);만 해주어도
쿼리 3개가 모두 나간다.
db확인 시 제대로 저장이 된 것을 볼 수 있다.
Cascade란? Cascade로 선언한 밑에 애들도 다 persist 날려줄거야
한마디로 연쇄! 저장할 때 연관된 애도 저장할 거야!
ALL, PERSIST 두개 정도 옵션만 쓰면 됨.
PERSIST : 저장만 같이 할거야.
하나의 부모가 자식들을 관리할때!
게시판과 첨부파일 경로의 경우 쓸 수 있다
그 첨부파일의 경로는 그 게시판에서만 관리하니까!
근데 만약 그 첨부파일의 경로를 다른 엔티티에서도 관리한다면 쓰면 안됨.
parent만 child를 관리한다면 사용 가능.
소유자가 하나일때 or 라이프사이클이 똑같을때 CASCADE를 써도 된다.
orphanRemoval = true로 설정한다.
JpaMain.java
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 {
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();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}finally {
em.close();
}
emf.close();
}
}
delete쿼리가 나가고,
db에도 잘 반영된것을 확인할 수 있다.(ID 2번이 0번이었는데 삭제됨)
지난 프로젝트에 @ManyToOne, @OneToOne의 fetch를 LAZY로 바꿔준다.
Order 생성될때 OrderItem,delivery도 생성될 수 있게 cascade = ALL 도 추가해준다.
Order.java
@OneToOne
@JoinColumn(name = "DELIBERY_ID", cascade = CascadeType.ALL)
private Delivery delivery;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();