- 인터페이스의 모음
- 3가지의 구현체 존재(Hibernate, EclipseLink,DataNucleus) => 대부분 하이버네이트
- SQL 중심적인 개발에서 -> 객체 중심의 개발
- 생산성 / 유지보수 / 패러다임의 불일치 해결 / 성능 / 데이터 추상화와 벤더 독립성 / 표준
- 생산성
- 저장 : jpa.persist(member)
- 조회 : Member member = jpa.find(memberId)
- 수정 : member.setName("변경할 이름")
- 삭제 : jpa.remove(member)
- JPA의 성능 최적화 기능
- 1차 캐시와 동일성(identity) 보장
=> 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
ex) String memberId= "100";
Member m1 = jpa.find(Member.class, memberId); // SQL
Member m2 = jpa.find(Member.class, memberId); // 캐시
m1 == m2 (같다!!!!)
=> SQL은 1번만 실행됨 (캐싱을 하고있음)
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) => 버퍼링
ex) em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//커밋하는 순간 데이터베이스에 insert sql을 모아서 보낸다.
transaction.commit(); //커밋
- 지연 로딩(Lazy Loading)
=> 지연로딩(Lazy) vs 즉시로딩(Eager)
ㅇ 지연로딩
- 엔티티 조회 시점이 아닌 엔티티 내 연관관계를 참조할 때 해당 연관관계에 대한 SQL이 질의된는 기능이며, fetch = FetchType.LAZY 옵션으로 지정할 수 있다.
- 엔티티 조회 시, 연관관계 필드는 프록시 객체로 제공된다.
- 쿼리가 2번 나감
ㅇ 즉시로딩
- 엔티티 조회 시 연관관계에 있는 데이터까지 한 번에 조회해오는 기능이며, fetch = FetchType.EAGER 옵션으로 지정할 수 있다.
- 즉시 로딩으로 조회된 엔티티의 연관관계 필드에는 실제 엔티티 객체가 반환된다.
- 쿼리가 조인해서 한 번 나감
ㅇ 주의사항
- 실무에서는 가급적 지연로딩만 사용한다.
- 즉시로딩을 사용하면 예상치 못한 SQL이 발생할수 있기 때문.
EX)다른 개발자가 Member를 조회할 때는 Team이 조회될 거라고 생각하지 못하기 때문에
ㅇ 연관관계별로 fetch 옵션의 기본값이 다르다.
- @ManyToOne : EAGER
- @OneToOne : EAGER
- @ManyToMany : LAZY
- @OneToMANY :LAZY
- JPA 영속성 관리
ㅇ 영속성 컨텍스트의 이점
- 1차 캐시와 쓰기 지연 SQL 저장소를 가짐
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연로딩
ㅇ 플러시
- 변경 감지(더티체킹) - update
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저자소의 쿼리를 데이터 베이스랑 동기화(전송) ->등록,수정,삭제 쿼리
- 영속성 컨텍스트를 비우지 않음
- 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨
ㅇ 준영속 상태
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
- 영속성 컨텍스트가 제공하는 기능을 사용못함
- ex) em.detached(entity), em.clear(), em.close
- JPA 엔티티 매핑
ㅇ 데이터베이스 스키마 자동 생성
- <property name="hibernate.hbm2ddl.auto" value="create / create-drop / update / validate / none" /> 설정
- create : 기존 테이블 삭제 후 다시 생성 (drop + create)
- create-drop : create와 같으나 종료 시점에 테이블 drop
- update : 변경분만 반영 (운영 DB에는 사용하면 안됨)
- validate : 엔티티와 테이블이 정상 매핑되어있는지만 확인
- none : 사용하지 않음
ㅇ 주의사항
- 개발 초기 단계 : create , update
- 테스트 서버 : validate
- 운영 서버 : validate, none
=> 데이터가 적재되어 있는데 create를 하면 drop 하기 때문에 테이블을 날린다.
=> update시 테이블 alter로 변경하지만 몇분간 데이터베이스 테이블 락이 걸린다.
- 기본키 매핑
ㅇ@GeneratedValue(strategy = GenerationType)
- IDENTITY
=> em.persist시 insert 쿼리 날리고 id 반환(유일하게 persist 때 insert 쿼리 날림)
이유: insert후 id(pk값을 얻어야 영속성 컨텍스트에 저장할 수 있기 때문)
- SEQUENCE
=> em.persist시 SEQ next value 가져오고 em.commit 시 insert 쿼리 날림
=> allocationSize : 기본 50 -> db에 미리 50개를 올려놓고 우리가 persist할때마다 메모리에 1,2씩 쌓임 ->50개에 다다르면 그 때 다시 next value를 50개씩 가져옴(51번~100번 세팅)
=> 1번쨰 next value 호출 : 1 | 1
=> 2번째 next value 호출 : 51 | 2
=> 3번째 " : 51 | 3
... 51 만나면: 101 | . ..
※ 여러 웹서버가 있어도 동시성 이슈 없이 다양한 문제가 해결됨
- 양방향 연관관계와 연관관계의 주인 (★★★★★)
ㅇ 연관관계의 주인과 mappedBy
- mappedBy = JPA의 멘탈 붕괴 난이도
- 객체와 테이블간에 연관관계를 맺는 차이를 이해해야한다.
ㅇ 객체의 양방향 관계
- 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야한다. (A,B 클래스가 있다면 A.getB(), B.getA())
ㅇ 연관관계의 주인(Owner)
※ 양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리 (등록,수정)
- 주인이 아닌 쪽은 읽기만 가능
- 주인은 mappedBy 속성 사용 X
- 주인이 아니면 mappedBy 속성으로 주인
※ 누구를 주인으로?
- 외래키가 있는 곳을 주인으로 정해라
- Member, Team / @OneToMany, @ManyToOne 관계일경우
- Member.team이 연관관계 주인
- @OneToMany, @ManyToOne 등 관계에서 무조건 다(Many)쪽이 연관관계의 주인이 됨
- 성능이슈, 외래키랑 같은 테이블에서 관리할 수 있음
ㅇ 양방향 매핑 시 가장 많이 하는 실수
- mappedBy (연관관계 주인이 아닌 역방향에서 연관관계 설정)
ㅇ 양방향 매핑(★★★★★)
- 양쪽 (주인과 역방향)에 둘 다 값을 넣어주는 것이 맞음 (순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자)
- em.flush , clear를 하지 않고 다시 해당 객체를 조회했을 경우 1차 캐시된(메모리에만 올라가있는) 객체를 가져오기 때문에 해당 객체는 빈 객체로 문제가 된다.
- 주인 객체에서 setTeam(역방향) 편의 메서드를 만든다. 하나만 호출해도 양쪽 다 세팅이 된다.
=> chageTeam(Team team){ (주인)
this.team = team;
team.getMembers.add(this)
}
=> addOrderItem(OrderItem orderItem){
orderItems.add(orderItem);
orderItem.setOrder(this);
}
- 애플리케이션 만들 때마다 상황이 다르기 때문에 상황마다 주인에 설정할 것 것인지 역방향에 설정할 것인지 정해야한다.
- 무한루프를 조심하자
=> 1. toString() 양쪽 다 설정하면 양쪽에서 무한으로 toString 호출 -> stackOverFlow
=> 해결: 롬복에서 toString은 거의 쓰지마라. 쓰더라도 양방향 관계는 빼고 써라.
2. JSON 생성 라이브러리 (toString과 마찬가지로 양방향 관계일 때, Entity를 JSON으로 바꾸는 순간 무한 루프) members -> team -> members -> team -> .....
=> 해결: 컨트롤러에서 엔티티를 절대 반환하지 마라
(이유 : 1.무한루프, 2.엔티티가 변경되는 순간 해당 api 스펙이 바뀌어버림)
ㅇ 양방향 매핑 정리
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료(중요, 웬만하면 단방향 매핑만으로 설계를 끝내야한다. 처음엔 무조건 단방향으로 설계를 끝내라)
- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가가 된 것 뿐이다!!!
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘하고 양방향 매핑은 필요할 때만 추가해도 됨 (테이블에 영향을 주지 않음)
ㅇ 양방향 VS 단방향
- 방법 1 (양방향 연관관계)
Order order = new Order();
order.addOrderItem(new OrderItem());
- 방법 2 (단방향 연관관계)
Order order = new Order();
em.persist(order);
OrderItem orderItem = new OrderItem();
orderItem.setOrder(order);
em.persist(orderItem);
//단방향 연관관계로도 애플리케이션을 만드는데에 지장이 없다.
//양방향 연관관계로 하는 이유는 조회를 더 편하게 하기 위해서
//나중에 JPQL로 조인을 더 복잡하게 해야하는데 이 작업을 더 간단히 하기위해 양방향으로 많이 짠다.
//핵심은 단방향으로 할 수 있으면 최대한 단방향으로 해라!
//하지만 실무에서 사용할 때 조회를 간단히 하기 위해 양방향으로 하기 위한 작업이 많아진다.
- 다양한 연관관계 매핑
ㅇ 연관관계 매핑 시 고려사항 3가지
- 다중성 :
=> 다대일 @ManyToOne : 실무에서 가장 많이 사용함
일대다 @OneToMany : 필요할 때 많이 씀
일대일 @OneToOne : 가끔나옴
다대다 @ManyToMany : 실무에서 사용하면 안됨!!
- 단방향, 양방향
=> ㅇ 테이블
- 외래키 하나로 양쪽으로 조인 가능
- 사실 방향이라는 개념이 없음
ㅇ 객체
- 참조용 필드가 있는 쪽으로만 참조 가능 (한쪽 - 단방향 , 양쪽 - 양방향)
- 한쪽만 참조하면 단방향
- 양쪽이 서로 참조하면 양방향 (사실 양방향이라는 것은 없고 단방향이 2개)
- 연관관계 주인
=> - 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
- 객체 양방향 관계는 A->B, B->A 참조가 2개
- 객체 양방향 관계는 참조가 2군데 있음. 둘 중 테이블의 외래 키를 관리할 곳을 지정해야함
- 연관관계의 주인 : 외래키를 관리하는 곳
- 주인의 반대편(역방향) : 외래 키에 영향을 주지 않음, 단순 조회만
ㅇ 다대일[N:1]
- 항상 다(N)쪽에 FK가 있어야함(EX: TEAM <-> MEMBER 일 경우, MEMBER에 FK 보유)
- MEMBER가 연관관계 주인
- 가장 많이 사용, 일대다의 반대
- 양쪽을 서로 참조할 때 필요
ㅇ 일대다[1:N]
- 일이 연관관계 주인이 되는 것
- 이 모델은 김영한님은 권장하지 않음. (실무에서 거의 사용하지 않음!!)
- 일대다 양방향 정리 @JoinColumn(name="TEAM_ID",insertable=false,updatable=false)
- 실무에선 테이블이 몇십개가 엮어서 돌아가는데 운영하기 힘들어짐 (Team 객체를 만졌는데 Update Member 가 나감)
- 다[N] 테이블에 update 쿼리가 한 번 더 나가야한다.
- 해결 : Member 다대일을 양방향 관계로 바꿔서 가면 됨, 다대일 양방향을 사용하자 (★)
ㅇ 일대일[1:1]
- 일대일 관계는 그 반대도 일대일
- 주 테이블이나 대상 테이블에 둘 다 외래키를 선택해서 넣어도 된다.(어디에 넣든 상관없음)
- 외래키에 데이터베이스 유니크(UNI) 제약조건이 추가되어야 가능
- 외래키가 대상 테이블에 있을경우 -> 양방향을 걸고 조회한다.
- 주 테이블에 외래키 (영한님은 이 방법을 선호하심 - DBA분들이 싫어할 수 있음)
ㅇ 객체지향 개발자 선호 / JPA 매핑 편리
ㅇ 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
ㅇ 단점 : 값이 없으면 외래 키에 NULL 허용
- 대상 테이블에 외래키
ㅇ 전통적인 데이터베이스 개발자 선호
ㅇ 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지
ㅇ 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨 (대상 테이블이 있는지 없는지 먼저 확인해봐야하기 때문에 WHERE문으로 확인함(쿼리가 먼저 나감))
ㅇ 다대다 [N:M]
- 영한님은 실무에서 절대 사용하지 않는다!!
- 객체는 다대다 관계가 가능하지만 관계형 데이터베이스는 중간에 테이블을 하나 두고 일대다 다대일로 풀어내야함(MEMBER <-> MEMBER_PRODUCT <-> PRODUCT)
- 편리해보이지만 연결테이블(MEMBER_PRODUCT)에 추가 정보를 넣을 수 없음
=> 극복
ㅇ 연결 테이블용 엔티티 추가 (연결테이블을 엔티티로 승격)
- @ManyToMany -> @OneToMany <-> @ManyToOne @ManyToOne <-> @OneToMany
- ORDER 엔티티생성 -> ORDER(DB)
- PK를 GeneratedValue / PK,FK 조합 2가지로 설정할 수 있다 하지만 김영한님은 GeneratedValue로 PK를 계속 가져가는게 애플리케이션을 계속 운영할 때 유연하다. PK,FK의 조합은 어디에 종속적이여서 묶여 있고 유연하지 못하다.
- 고급 매핑
ㅇ 상속관계 매핑
ㅇ @MappedSuperclass
-
상속관계 매핑
-
관계형 데이터 베이스는 상속 관계 x <-> 객체는 o
-
슈퍼 타입, 서브타입 관계라는 모델링 기법이 객체 상속과 유사
-
상속관계 매핑 : 객체의 상속 구조와 DB의 슈퍼타입, 서브타입 관계를 매핑
- 슈퍼타입 : 물품 서브타입 : 음반, 영화, 책
- 객체는 상속 - Item 자식 - Album, movie , book
-
슈퍼타입 브타입 논리 모델을 실제 물리 모델로 구현하는 3가지 방법
- 조인 전략 - 테이블을 각각 조인 item_id로 insert를 각각 테이블에 2번 함 / JPA랑 가장 유사한 모델 => 상속 관계일 경우 자식 엔티티 조회시 JPA가 부모를 자동으로 조인해줌
- 단일 테이블 전략 : 논리 모델을 한 테이블로 합치는 것 (Item에 album, movie, book 컬럼을 다 넣음) (JPA 기본 전략)
- 구현 클래스마다 테이블 전략 : album,movie,book에 item_id를 다 가지고 있음
-
DB의 논리 모델링 3가지를 어떤 것으로 구현을 하든 JPA는 다 지원을 해줌
- 기본 전략은 싱글 테이블 전략(한 테이블에 다 떄려박음)
-
조인 전략 (JOINED) 정석★
- @Inheritance(strategy = InheritanceType.JOINED)
- @DiscriminatorColumn (DTYPE) - 엔티티타입 (부모엔티티에 넣어주면 조회하기 좋음)
- @DiscriminatorValue(name="")를 활용해 자식 엔티티의 이름을 지정할 수 있다.
- 장점 :
- 정규화가 되어있음
- 외래키 참조 무결설 제약 조건 가능 (아예 다른 테이블에서 조회할때 부모(item)클래스만 보면 됨)
- 저장 공간 효율
- 단점 :
- 조회 시 조인을 많이 사용 / 성능 저하
- 조회 쿼리가 복잡
- 데이터 저장시 SQL 2번 호출 (하지만 이 모든 것이 그렇게 크게 영향을 미치지 않는다.)
-
단일 테이블 (SINGLE_TABLE)
- @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
- 한 테이블에 다 때려 넣고 실행
- @DiscriminatorColumn 꼭 넣어줘야함(필수) DTYPE으로 구분하기 때문 -> @DiscriminatorColumn이 없어도 자동으로 DTYPE이 들어감
- 부모 테이블에 전체 데이터가 들어감 (자식테이블은 하나도 안 들어감)
- 성능은 제일 잘 나옴
- 장점 :
- 조인이 필요 없어서 성능이 빠름
- 조회 쿼리가 단순함
- 단점 :
- 자식 엔티티가 매핑한 컬럼은 모두 NULL 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커지고 조회 성능이 오히려 느려질 수 있다.
-
구현 클래스마다 테이블 전략
- @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
- item 테이블을 없애고 album,movie,book 테이블에 각각 다 name,price 필드를 추가
- 부모(item)클래스는 abstract(추상클래스)로 설정
- @DiscriminatorColumn 쓸모 없음
- 부모 객체로 찾을 때 Union All로 테이블을 다 뒤짐(상당히 복잡한 쿼리)
- 절대 사용하면 안되는 전략!!! (개발자,DBA 둘 다 싫어함 겹치는 게 없음)
- 장점 :
- 서브 타입을 명확하게 구분해서 처리할 때 효과적
- NOT NULL 제약조건 사용 가능
- 단점 :
- 여러 자식 테이블 조회할때 UNION 성능 느림
- 자식 테이블 통합해서 쿼리하기 어려움
-
※결론
- 단순하면 단일테이블
- 비즈니스 로직, 복잡하면 조인전략
-
-
@MappedSuperClass
- 공통적 매핑 정보가 필요할 때
- 매핑 정보를 받는 슈퍼 클래스
- 생성 날짜, 수정 날짜 등 -> 하지만 이 생성 정보, 날짜들은 JPA에서 기본적으로 제공해주는 것을 사용 (JPA에 이벤트가 있어서 영속하기 직전에 어떤걸 호출해! 이게 가능하다)
- 엔티티가 X , 테이블과 매핑 X
- 자식 클래스에 정보만 제공
- 직접 사용할 일이 없으므로 추상클래스로 사용하는 것을 권장
-
프록시
ㅇ 프록시 기초
ㅇ 프록시 특징
ㅇ 프록시 객체의 초기화
ㅇ 프록시 중요 특징 ★
ㅇ 프록시 확인
-
프록시 기초
- em.find() vs em.getReference()
- em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프록세) 엔티티 객체 조회 -> em.getReference를 호출하는 시점에서는 쿼리를 안날림, 해당 객체를 사용하는 시점엔 쿼리 나감
- em.find() vs em.getReference()
-
프록시 특징
- 하이버네이트가 어떤 내부 라이브러리를 사용해서 프록시 객체 설정
- 실제 클래스를 상속 받아서 만들어진다.
- 실제 클래스와 겉모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
- 프록시 객체는 실제 객체의 참조(target)를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
-
프록시 객체의 초기화
Member member = em.getReference(Member.class,"id1"); member.getName();
- em.getReference()로 프록시 객체를 가져온 다음에, getName() 메서드를 호출
- MemberProxy 객체에 처음에 target 값이 존재하지 않는다. JPA가 영속성 컨텍스트에 초기화 요청을 한다.
- 영속성 컨텍스트가 DB에서 조회
- 실제 Entity를 생성
- 프록시 객체가 가지고 있는 target(실제 Member)의 getName()을 호출해서 결국 member.getName()을 호출한 결과를 받을 수 있다.
- 프록시 객체에 target이 할당 되고 나면, 더이상 프록시 객체의 초기화 동작은 없어도 된다. 실제로는 위와 같이 프록시 객체가 동작 한 것이다.
-
프록시 중요 특징 ★
- 프록시 객체는 처음 사용할 때 한 번만 초기화
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님!!!! 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근 가능
- 프록시 객체는 원본 엔티티를 상속 받음, 따라서 타입 체크시 주의해야함( 타입 비교 시 == 비교 실패, 대신 instanceof 사용) ★
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환 => find(실제 엔티티)로 가져오든 reference(가짜 엔티티)로 가져오든 ==비교하면 무조건 true 가 나와야 하기 때문 ★ => proxy를 먼저 올리고 find하면 둘 다 proxy 객체 => find 먼저 올리고 proxy 가져오면 둘 다 실제 Member 객체 => JPA 한 트랜잭션에 객체 타입이 같음을 보장해준다!! ★★
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화할 때 문제 발생 (em.detach) ★★ => (하이버네이트는 org.hibernate.LazyInitializationException
-
프록시 확인
- 프록시 인스턴스 초기화 여부 확인
- PersistenceUnitUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법
- entity.getClass().getName() 출력(..javasist.. or HibernateProxy..) => proxy.getClass()
- 프록시 강제 초기화
- org.hibernate.Hibernate.initialize(entity);
- 참고 : JPA 표준은 강제 초기화 없음
- 강제 호출 : member.getName()
- 프록시 인스턴스 초기화 여부 확인
-
즉시로딩, 지연로딩
ㅇ 지연로딩 LAZY
ㅇ 즉시로딩 EAGER
ㅇ 지연로딩과 즉시로딩 주의 ★★★★★
ㅇ 지연로딩 활용 - 실무
-
지연로딩 LAZY
- 비즈니스 로직 상 MEMBER와 TEAM을 같이 사용하지 않을 때 성능상 유리
- fetch = FetchType.LAZY
- 지연로딩된 객체를 실제 사용할 시점에 DB초기화
- 프록시 객체가 나감
-
즉시로딩 EAGER
- MEMBER와 TEAM을 같이 많이 사용할 때, MEMBER와 TEAM을 조인해서 조회함 (하지만 실무에선 전부 다 지연로딩 사용!!)
- fetch = FetchType.EAGER
- 즉시로딩하기 때문에 프록시 객체가 아닌 진짜 객체가 나옴
-
지연로딩과 즉시로딩 주의 ★★★★★
- 가급적 지연 로딩(LAZY)만 사용(특히 실무에서)
- 즉시 로딩을 적용하면 예상치 못한 SQL이 발생 (find할 때 조인된 테이블이 다 조인돼서 조회됨(나도 모르는 쿼리가 나옴))
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- JPQL은 쿼리문을 그대로 SQL로 번역이 된다. 따라서 MEMBER만 셀렉트되고 EAGER이기 때문에 TEAM도 셀렉트함
- 따라서 MEMBER 조회 1번 TEAM 조회 1번 => 총 2번 셀렉트문이 나감
- N + 1 이란 조인된 TEAM N개 + 최초 쿼리 MEMBER 1개
- 해결 3가지 방법 (일단 지연로딩으로 발라야 됨)
- 패치조인 (필요할 때 join fetch로 조인해서 가져온다) - 대부분 이것으로 다 해결
- 엔티티 그래프, 어노테이션
- 배치 사이즈
- @ManyToOne, @OneToOne은 기본이 즉시로딩(EAGER)
- LAZY로 설정
- @OneToMany, @ManyToMany는 기본이 지연로딩(LAZY)
-
지연로딩 활용 - 실무 ★
- 모든 연관관계에 지연로딩을 사용해라!!
- 실무에서 즉시로딩을 사용하지마라!!
- JPQL fetch 조인이나 엔티티 그래프 기능을 사용해라!
- 즉시로딩은 상상하지 못한 쿼리가 나간다
-
영속성 전이(CASCADE)
ㅇ 영속성 전이란?
ㅇ 주의사항
ㅇ 종류
-
영속성 전이란(CASCADE)?
- 특정 엔티티를 영속 상태로 만들 때 [연관된 엔티티도 함께 영속 상태]로 만들고 싶을 때
- 연관관계인 엔티티도 persist 날려줄거야!
- 예) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
- 실무에서 많이 씀
-
주의사항
- 영속성 전이는 연관관계 매핑하는 것과는 아무 관련이 없음
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
-
종류 (이 세가지만 자주 씀)
- ALL : 모두 적용 (영속 + 삭제)
- PERSIST : 영속 (삭제하면 안될때)
- REMOVE : 삭제
-
영한님이 느낀 점
- 하나의 부모가 자식들을 관리할 때 의미가 있음
- 게시판이랑 첨부파일 데이터, 경로는 사용할 수 있음 사용 O
- 파일을 다른 엔티티에서 관리하고 여러 엔티티에서 관리할 경우 사용 X
- 단일 엔티티에 종속적일 때 사용해야함!!
- 두 엔티티의 라이프 사이클이 동일할 때, 단일 소유자일 경우 사용
-
고아 객체
ㅇ 고아 객체란?
ㅇ 주의사항
-
고아 객체란?
- 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
- orthpanRemoval = true
- orthpanRemoval = true일 때, findParent.getChildList().remove(0); 하면 delete쿼리 나감, false일 때는 delete 쿼리 안 나감 (컬렉션에서 빠지면 삭제 됨)
-
주의사항
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 참조하는 곳이 하나일 때 사용해야함 ★
- 특정 엔티티가 개인 소유할 때 사용
- @OneTtoOne, @OneToMany만 사용
- 참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.
-
영속성 전이 + 고아 객체 생명주기
- CascadeType.ALL + orphanRemoval = true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기도 관리할 수 있음 (Parent는 영속성 컨텍스트가 관리하지만, Child는 Parent가 관리함 ,Parent는 Dao나 Repository가 없어도 됨)
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용 (레파지토리는 Aggregate Root(Parent)만 접근함, child는 Aggregate Root의 접근으로만 생명주기를 관리함)
-
연관관계 정리
ㅇ 글로벌 페치 전략 설정
ㅇ 영속성 전이 설정
-
글로벌 페치 전략 설정 ★
- 모든 연관관계를 지연 로딩으로!!!
- @ManyToOne, @OneToOne은 기본이 즉시로딩이므로 지연 로딩으로 변경
-
영속성 전이 설정
- Order -> Delivery 영속성 전이 ALL -> 주문을 하는 동시에 배달 엔티티의 라이프 사이클을 맞추겠다
- Order -> OrderItem 영속성 전이 ALL