스프링

 

✍️
이런 걸
배워요!

JPA의 기본기를 탄탄하게 다질 수 있습니다.

JPA의 내부 동작 방식을 이해할 수 있습니다.

객체와 DB 테이블을 올바르게 설계하고 매핑할 수 있습니다.

실무에서 자신있게 JPA를 사용할 수 있습니다.

 

 

인프런 강의 :  https://www.inflearn.com/course/ORM-JPA-Basic

 

강의 자료 : 

https://github.com/braverokmc79/jpa-basic-lecture-file

 

 

소스 :

https://github.com/braverokmc79/ex-jpa-13-1

 

https://github.com/braverokmc79/jpa-shop

 

 

 

 

 

 

 

[7] 프록시와 연관관계 관리

 

 

25.  프록시

 

 

강의:

https://www.inflearn.com/course/lecture?courseSlug=ORM-JPA-Basic&unitId=21708&tab=curriculum

 

 

 

 

 

프록시의 특징


프록시 객체는 처음 사용할 때 한 번만 초기화

 


• 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

		Member member=new Member();
			member.setUsername("user");
			member.setCreatedBy("kim");
			member.setCreatedDate(LocalDateTime.now());
			
			em.persist(member);
			em.flush();
			em.clear();
			
			Member findMemberOne=em.getReference(Member.class, member.getId());
			System.out.println("before findMember = " +findMemberOne.getClass());
			System.out.println("findMember.username  " +findMemberOne.getUsername());
			System.out.println("after findMember = " +findMemberOne.getClass());

 


• 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)

	/**
	 * 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
	 */
	private static void logic(Member m1, Member m2) {
		System.out.println("m1 === m2 : " + (m1 instanceof Member));
		System.out.println("m1 === m2 : " + (m2 instanceof Member));
	}
	

 


• 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해 도 실제 엔티티 반환

			/**
			 * 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
			 */
			em.flush();
			em.clear();
			Member m1=em.find(Member.class, member.getId());
			System.out.println("m1 = "+ m1.getClass());
			
			Member reference =em.getReference(Member.class, member.getId());
			System.out.println("reference = " +reference.getClass());
			System.out.println("a == a: " +(m1==reference) );

 


• 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

			/**
			 * 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면문제 발생
				(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)
			 */
			Member refMember=em.getReference(Member.class, member.getId());
			System.out.println("refMember = " +refMember.getClass()); //Proxy
			System.out.println("isLoadded1 = " +emf.getPersistenceUnitUtil().isLoaded(refMember));
			em.clear();
			refMember.getUsername();

 

=>

m1 = class jpashop.home.domain.Member
reference = class jpashop.home.domain.Member
a == a: true
refMember = class jpashop.home.domain.Member$HibernateProxy$vFtRdzhn
isLoadded1 = false
org.hibernate.LazyInitializationException: could not initialize proxy [jpashop.home.domain.Member#1] - no Session
	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
	at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
	at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
	at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
	at jpashop.home.domain.Member$HibernateProxy$vFtRdzhn.getUsername(Unknown Source)
	at jpashop.home.JpaMain.main(JpaMain.java:106)
2월 24, 2023 5:01:01 오후 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]

 

 

 

 

 

 

 

 

 

 

프록시 확인


• 프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object entity)

			Member refMember=em.getReference(Member.class, member.getId());
			System.out.println("refMember = " +refMember.getClass()); //Proxy
			System.out.println("isLoadded1 = " +emf.getPersistenceUnitUtil().isLoaded(refMember));
			em.clear();
			refMember.getUsername();
			System.out.println("isLoadded2 = " +emf.getPersistenceUnitUtil().isLoaded(refMember));

 


• 프록시 클래스 확인 방법
entity.getClass().getName() 출력(..javasist.. orHibernateProxy…)

 

 


• 프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);

	//강제초기화
			Hibernate.initialize(refMember);


• 참고: JPA 표준은 강제 초기화 없음
강제 호출: member.getName()

 

 

 

 

 

 

 

 

 

 

 

 

26.  즉시 로딩과 지연 로딩

 

 

강의:

https://www.inflearn.com/course/lecture?courseSlug=ORM-JPA-Basic&unitId=21709&tab=curriculum

 

 

 

			
			Team team =new Team();
			team.setName("teamA");
			em.persist(team);
			
			
			Member member1=new Member();
			member1.setUsername("user");
			member1.setCreatedBy("kim");
			member1.setCreatedDate(LocalDateTime.now());
			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();
			System.out.println("==============================");

 

 

프록시와 즉시로딩 주의


가급적 지연 로딩만 사용(특히 실무에서)
• 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
• 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
@ManyToOne, @OneToOne은 기본이 즉시 로딩
 -> LAZY로 설정

• @OneToMany, @ManyToMany는 기본이 지연 로딩

 

 

 

 

 

	
			List<Member> members=em.createQuery("select  m from Member m join fetch m.team ", Member.class).getResultList();
			
			

 

 

 

지연 로딩 활용 - 실무


모든 연관관계에 지연 로딩을 사용해라!
실무에서 즉시 로딩을 사용하지 마라!
• JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라! (뒤에서 설명)
• 즉시 로딩은 상상하지 못한 쿼리가 나간다.


 

 

 

 

 

 

 

 

 

27.  영속성 전이(CASCADE)와 고아 객체

 

(별로 사용하지 않는다.)

 

강의:

https://www.inflearn.com/course/lecture?courseSlug=ORM-JPA-Basic&unitId=21710&tab=curriculum

 

 

 

영속성 전이: CASCADE - 주의!


영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음


• 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함 을 제공할 뿐

 

 

 

CASCADE의 종류


• ALL: 모두 적용
• PERSIST: 영속
• REMOVE: 삭제
• MERGE: 병합
• REFRESH: REFRESH
• DETACH: DETACH

 

 

Parent

import lombok.Getter;
import lombok.Setter;

@Entity
@Setter
@Getter
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);
	}
}

 

 

Child

package jpashop.home.domain;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import lombok.Getter;
import lombok.Setter;

@Entity
@Setter
@Getter
public class Child {
	
	@Id
	@GeneratedValue
	private Long id;
	private String name;
	
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "PARENT_ID")
	private Parent parent;
	
	
}

 

 

 

JpaMain2

 

package jpashop.home;


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import jpashop.home.domain.Child;
import jpashop.home.domain.Parent;

public class JpaMain2 {
	public static void main(String[] args) {
		EntityManagerFactory emf=Persistence.createEntityManagerFactory("hello");
		
		EntityManager em=emf.createEntityManager();
		
		//JPA 의 모든 데이터 변경은  트랜잭션 안에서 실행 되어야 한다.
		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.persist(child1);
			//em.persist(child2);

			tx.commit();
		}catch(Exception e) {
			tx.rollback();
			e.printStackTrace();
		}finally {
			em.close();
		}	
		emf.close();	
	}
	
}

 

 

@OneToMany(mappedBy = "parent",cascade = CascadeType.ALL )

em.persist(parent);

실행해도   

//em.persist(child1);

//em.persist(child2); 

실행된다.

 

 

 

 

 

 

 

 

고아 객체


고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티 를 자동으로 삭제

• orphanRemoval = true
• Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);


//자식 엔티티를 컬렉션에서 제거

• DELETE FROM CHILD WHERE ID=?


 

 

 

 

고아 객체 - 주의


• 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
• 참조하는 곳이 하나일 때 사용해야함!
• 특정 엔티티가 개인 소유할 때 사용


• @OneToOne, @OneToMany만 가능
• 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고 아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다.

이것은 CascadeType.REMOVE처럼 동작한다.

 

 

 

영속성 전이 + 고아 객체, 생명주기


• CascadeType.ALL + orphanRemoval=true


• 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거


• 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음


• 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용

 

 

 

 

 

 

 

28.  실전 예제 5 - 연관관계 관리

 

 

강의:

https://www.inflearn.com/course/lecture?courseSlug=ORM-JPA-Basic&unitId=21711&tab=curriculum

 

 

 

글로벌 페치 전략 설정


• 모든 연관관계를 지연 로딩으로


• @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경

 

 


영속성 전이 설정


• Order -> Delivery를 영속성 전이 ALL 설정


• Order -> OrderItem을 영속성 전이 ALL 설정

 

 

ManyToOne 과 OneToOne 은 반드시 FetchType.LAZY  적용

@ManyToOne(fetch = FetchType.LAZY)

@OneToOne(fetch = FetchType.LAZY)

 

package jpashop.home.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name="ORDERS")  //ORDER DB 에서 예약어로 사용되는 경우 많아서 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;
	
	@OneToMany(mappedBy = "order" ,cascade = CascadeType.ALL)
	private List<OrderItem> orderItems=new ArrayList<>();
	private LocalDateTime orderDate;
	
	//ORDINAL 숫자 타입으로 하면 순서가 꼬이므로 무저건 String 한다
	@Enumerated(EnumType.STRING)
	private OrderStatus status;

	//양방향 연관 관계 설정
	public void addOrderItem(OrderItem orderItem) {
		orderItems.add(orderItem);
		orderItem.setOrder(this);
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

어떤 일에서나 그 이익되는 면만 생각하고 해되는 일은 생각하지 않으면 반드시 실패하게 된다. 손자(孫子)의 오권(五權)의 하나. -순자

댓글 ( 4)

댓글 남기기

작성