1. JPA 등장 배경
1) JPA 사용하는 이유?**
- 객체와 RDB(관계형 데이터 베이스)의 패러다임을 줄이고자 사용!!
- SQL 중심의 개발에서 객체중심의 개발로 하기위해서
2) queryDSL 사용 이유?
- 컴파일 단계에서 오류를 잡아줌
- 변수명이나 쿼리가 길어지기 때문에 이를 줄이고자 등장
3) spring 사용하는 이유?
- Bean -> IOC -> DI // filter, AOC, intercept
- 빈이 객체의 생명주기를 관리해줘서
2. JPA 동작원리
EntityManager
안에 영속성 컨텍스트가 있으며persist()
로 영속화를 시켜서 영속성 컨텍스트 내부에 엔티티 영속
- 영속성 컨텍스트에 있는 엔티티만 DB의 테이블로 만든다.
flush()
는 영속성 컨텍스트의 엔티티를 없애지 않는다. 영속성 컨텍스트와 DB 사이에서 테이블과 쿼리 상황을 일치시킨다.
- 1차 캐시로 인해서 한번 실행한 쿼리는 DB에서 직접 불러오지 않아서 성능이 약간 좋다.
- 한 트랜잭션 내부에서는
EntityManager
를 공유하며 또다른 트랜잭션의 경우EntityManager
를 또 만들어야 한다.- 트랜잭션 내부에서 영속된 엔티티의 동일성을 보장한다.
- 쓰기 지연 DB에 의해 한 번에 모아서 트랜잭션 종료시에 한방에 쿼리를 날린다.
3. JPA 정책
1) @generatedValue
a. squence 정책
- 값을 모아서 쿼리를 날리는데 미리 크기가 ‘50’정도의 크기를 값으로 받아 올 수도 있다.
b. identity 정책
a) 단점 :
- insert query 날릴 시점마다 직접 db에 들어가봐랴야 pk값 확인 가능
- insert 시점에는 jpa에서는 pk는 null로 확인됨.(insert 시, db에 직접 들어가면 pk값 확인 가능.
- 이후, select 쿼리를 날리면, 값 확인 가능
2) JPA 엔티티 설계법 **
- order에서 member 객체를 사용하기 위해서는 id인 식별자를 참조하는게 아니라 객체 자체를 참조해야 한다.
- order.getMemberId() : 객체를 관계형 DB에 맞춘 형태
- order.getMember() : 관계형DB를 객체에 맞추기(Ok)
4. JPA 연관관계
1) JPA 연관관계 개념**
- 연관관계의 주인은 FK를 가진 객체이다. **
- 이것은 JPA가 RDB처럼 테이블 관계가 아니라 객체 간의 관계라서 어쩔 수 없이 억지로 관계 맺음.
- 주인이 아닌 가짜 주인공에게는 그에 반하도록
@mappedBy
를 걸어준다!**
- 주인공이 되는 이유는 자주 변경되고 수정되고 삭제되는 부분이라서 이다!
- 보통, 1:N 관계에서 ‘N’이 주인공이고 ‘1’에 해당하는 것이 거울에 비추는 것처럼
@mappedBy
(매핑이 되어지도록)되는 녀석이다.**
- 보통, 1:N 관계에서 ‘N’이 주인공이고 ‘1’에 해당하는 것이 거울에 비추는 것처럼
- 이런 컨셉을 가져가는 이유는 만약에 ‘1’ 부분에 주인공을 걸어서 설계하면, ‘1’ 부분이 변경되면 ‘N’쪽에서도
Insert
나Update
쿼리를 날리기 때문이다.
2) JPA 연관관계 설계법**
- 왠만하면 단방향 연관관계로 설계하기
- 필요하다면 추가로 양방향 관계로 사용
- 하지만, 실무에서는 보통 양방향을 많이 쓴다.( JPQL때문에 )**
3) 연관관계 매핑 종류
- 일대다 관계 : ‘1’이 연관관계의 주인이지만, ‘N’에서 외래키를 관리하는 희안한 구조이다.(객체와 RDB의 구조 차이로 인해)
- 외래키가 반대편 테이블에 있어서 불편하며, 그래서 다대일관계를 많이 이용하자!
- 일대일 관계 : 객체적으로는 양방향 연관관계를 해야함. 하지만, 효율이 별로여서 FK있는 쪽이 연관관계의 주인이 되어서 맺는다.
- 주테이블에 외래키를 가지먼 Jpa 개발자 들이 선호하며 주테이블만 조회하면 대상 테이블에 데이터를 가지고 있는지 판단된다.
- 대상 테이블에 외래키를 가지면, DBA들이 선호하며, 프록시의 한계로서 즉시로딩만 가능**
5. 고급 매핑
1) 상속 매핑
@Discriminator
:- @Discriminator로
DType
생성해서 상속해야 할 자식을 타입 별로 나눠서 상속한다. 이것은 JPA 권장사항이다.
- @Discriminator로
- extends가 상속이며 이때는
@Entity
나@Inheritance
속성이 필요!
@MappedSuperclass
속성만 받아서 사용한다.
2) BaseEntity 개념 정리**
- 공통으로 쓰는 엔티티를 설계하려면
BaseEntity
를 생성하여 상속받아서 사용한다.
3) 조인 vs 싱글테이블 전략
- 조인은 우리가 알고 있는 것
- 싱글테이블은 여러개의 상속받는것의 테이블을 하나에 뭉쳐놓음
- 뭐가 맞는지 모르겠다. 실무에서는 해당 전략이 잘 안맞을 수도
6. 프록시 개념**
1) 프록시를 사용하는 이유?
- Member를 조회할 때, Team을 항상 같이 조회해야 하는지?
- Member와 Team을 동시에 조회하는 경우라면 상관 없다.
- 하지만, 어떤 비즈니스 로직에서는 Member만 조회하길 원한다면, Team을 항상 같이 불러내어 조회할 필요는 없다. 낭비가 심하다.
- 따라서, JPA에서는 이러한 문제를
지연로딩
과프록시
라는 개념으로 해결할 수 있어서 매우 중요한 개념이다.**
Team team = new Team();
team.setName("team1");
entityManager.persist(team);
Member member = new Member();
member.setName("member1");
member.setTeam(team);
entityManager.persist(member);
entityManager.flush();
entityManager.clear();
Member findMember = entityManager.find(Member.class, member.getId());
2) 프록시 기본 개념
- JPA에서는
em.find()
뿐만 아니라 참조를 가져오는getReference()
메서드가 있다.
a. em.find()
vs em.getReference()
**
em.find()
:- DB를 통해서 실제 엔티티 객체 조회(일반적인 경우, 쿼리가 DB에 나간다.)
getReference()
:- DB에 쿼리가 안 나가는데 객체가 조회가 되는 것이다.(특이한 경우, DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회)
- 실제 username을 호출하는 과정에서 JPA가 DB에 쿼리를 날린다. 그래서 findMember에 값을 넣고 출력한다. 값을 넣는 과정에서는 더욱 복잡한 과정이 있다.
getReference()
로 만들어진 findMember를 Class 타입으로 호출해보면, Hibernate에서 만들어진 프록시 클래스(가짜 객체)로서 findMember가 호출된다.
- 프록시 객체 개념** :
- 기존의 테이블을 복사해다가 껍데기를 사용하는 경우이며 껍데기는 똑같은데 안에는 텅텅 빈 객체이다. 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉 모양이 같다.
- 내부에는 target 이라는 것이 있는데 진짜 Reference를 가르킨다. 이러한 target은 초기에 null 값으로 초기화되어 아무 것도 없다.
- 이러한 프록시 객체는 Hibernate가 내부적으로 프록시 라이브러리를 통해 만들어준다.
- 실제 엔티티 생성
Member findMember = entityManager.find(Memeber.class, member.getId()); // 실제 엔티티 생성
- 프록시 객체 생성** : 쿼리 안 나감**
- 프록시 객체를 활용하면 실제로 활용되기 전까지 DB 조회를 미룰 수 있다. 따라서, getReference만 호출하여 프록시 객체를 생성하면, DB에 쿼리는 안 나간다.
Member findMember = entityManager.getReference(Member.class, member.getId()); // 프록시 객체 생성
- 프록시 객체 사용 : DB에 쿼리 나감 **
- 프록시 객체를 활용하면 실제로 활용되기 전까지 DB 조회를 미룰 수 있다. 따라서, getReference만 호출하여 프록시 객체를 생성하면, DB에 쿼리는 안 나간다.
- System.out.println에서 사용 시, DB에 쿼리가 나간다.
Member findMember = entityManager.getReference(Member.class, member.getId());
System.out.println("Class : " + findMember.getClass());
System.out.println("id : " + findMember.getId());
System.out.println("name : " + findMember.getName());
3) 프록시 특징*
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 중요** : 프록시 객체를 호출하여 프록시 객체가 가지고 있는 getName() 메서드를 호출한다고 가정한다면, getName() 메서드는 프록시 객체의 내부에 있는 target이 가리키는 실제 엔티티 객체의 target에 있는 getName() 메서드를 대신 호출해준다.
- 하지만, 처음에 프록시 객체에는 target이 없을 것이다. 왜냐하면, getName() 메서드의 결과값은 DB에 있는데 한 번도 조회한 적이 없기 때문이다.
4) 프록시 객체의 초기화 과정**
- a. 프록시 객체 생성(
getReference()
) :- getReference() 메서드를 통해 Member의 프록시 객체를 가지고 온다.
- b. getName() 요청 :
- Member의 getName() 메서드를 호출하게 되는데, 초기에는 getName()를 가지고 있는 프록시 객체는 target이 없다.**
- c. 초기화 요청** :
- JPA가 영속성 컨텍스트에 getName() 메서드를 요청한다.(초가화 요청) JPA가 진짜 Member 객체를 가지고 오라고 요청한다.**
- d. DB 조회 및 실제 Entity 생성 :
- 그렇다면, 영속성 컨텍스트가 DB를 조회하여 실제 Entity 객체를 생성 후 반환한다.
- e. target.getName() 연결 :
- 그리고나서, 프록시 객체의 내부에 있는
Member target
(실제 Member 변수)에 진짜 Entity 객체를 연결시켜 준다.**
- 그리고나서, 프록시 객체의 내부에 있는
- 결론** :
- 프록시 객체의 getName() 메서드를 호출하면, 프록시 객체의 target은 앞에서 연결된 진짜 Entity 객체의 getName() 메서드인
Member에 있는 getName()
메서드가 반환이 된다. - getName()을 이전에 호출했으면, 영속성 컨텍스트에 있기 때문에 더 이상 DB에서 호출할 필요는 없다.
- 프록시 객체의 getName() 메서드를 호출하면, 프록시 객체의 target은 앞에서 연결된 진짜 Entity 객체의 getName() 메서드인
a) 프록시 객체의 초기화 정의** :
- JPA가 영속성 컨텍스트에 요청하여 영속성 컨텍스트가 DB에서 Entity를 가져와서 진짜 Entity 객체를 생성하는 과정!!
5) 프록시 객체 특징 및 주의점**
- a. 프록시 객체는 한 번만 초기화되며 원본 엔티티를 상속받는다!**
- b. 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 실제 엔티티에 접근이 가능하다.**
- c-1. 객체 비교 1 : 기존의 영속성 컨텍스트에 엔티티가 있다면 그것을 반환한다.**
- ex) 먼저 Member 객체의 인스턴스로 member1을 영속성 컨텍스트에 요청했으면, 추후에 getReference로 프록시 객체를 member1 인스턴스를 또 만들어도 이미 영속성 컨텍스트에 member1이 있어서 초기에 만든 실제 Entity의 member1 인스턴스와 getReference로 만들어진 member1 인스턴스의 객체 타입은 같다.
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
- c-2. 객체 비교 2 추가적인 개념** : 영속성 컨텍스트에 프록시가 이미 있는 경우, 무조건 해당 인스턴스에서는 프록시를 반환한다.(잘 안쓰이지만 알아두기!!)
- 우리가 개발할 때, 프록시던 아니던 상관 없도록 개발해야 한다.
Member member = new Member();
member.setName("member");
em.persist(member);
em.flush();
em.clear();
//영속성 컨텍스트에 찾는 엔티티가 있는 경우 엔티티를 반환한다.
Member findMember = em.find(Member.class, member.getId());
System.out.println(findMember.getClass()); //실제 엔티티 반환
Member reference = em.getReference(Member.class, member.getId());
System.out.println(reference.getClass()); //실제 엔티티 반환
em.clear();
//프록시가 이미 있는 경우 프록시를 반환한다.**
Member reference2 = em.getReference(Member.class, member.getId());
System.out.println(reference2.getClass()); //프록시 반환
Member findMember2 = em.find(Member.class, member.getId());
System.out.println(findMember2.getClass()); //프록시 반환
- c-3. 객체 비교 3 (중요**) :
- JPA에서는 같은 인스턴스들의 ‘==’ 비교에 대하여 같은 영속성 컨텍스트 안에서(같은 트랜잭션 레벨에서) 조회하면 항상 같다.
- JPA에서 제공해주는 개념이다.(한 트랜잭션 내에서는 실제 객체던 프록시 객체던 ‘==’ 비교 시, 객체 타입이 항상 같다.)
- d. JPA의 비즈니스 로직에서는 객체 비교 시, 어떤 비즈니스 로직의 메서드에서 인자로서, 객체가 프록시 객체로 넘어올지, 실제 객체로 넘어올지 모르기 때문에 타입 비교를
==
으로 하면 안되고instance of
로 해야 한다.**
private static void isSame(Member m1, Member m2) {
System.out.println("m1 : " + (m1 instanceof Member));
System.out.println("m2 : " + (m2 instanceof Member));
}
- e. 준영속 시, 프록시 조회 불가능하며 에러 발생한다.**
LazyInitialization Exception, No seesion
에러가 발생한다. 실무에서 자주 접하는 에러!- 더이상 영속성 컨텍스트로서 관리를 안하므로 해당 에러가 발생한다.
- 강제로 에러를 발생시키기 위해서는 em.detach(), em.close(), em.clear()시, 해당 에러 발생
- f. 강제 초기화 : JPA 표준은 강제 초기화가 없다. Hibernate에서 제공해주는 것이다.
- 대신, 강제 호출은 있다** :
member.getName()
- 대신, 강제 호출은 있다** :
- g. 프록시 초기화 여부 확인 :
EntityManagerFactor.getPersistenceUnitUtil().isLoaded()
를 이용한다.- ex) EntityManagerFactor.getPersistenceUnitUtil().isLoaded(findMember1);
- 최종 정리** : 즉시 로딩과 지연 로딩이 본 프록시 기능을 활용하여 구현된다.
7. 즉시로딩, 지연로딩
0) 개념 정리*
- Member 테이블을 조회할 때, Member 테이블과 Team 테이블을 함께 조회해야 하는지?
- 즉시 로딩(EAGER)의 경우에는 Member 테이블에 연관된 Team 테이블 개수마다 쿼리가 실행된다. 이것이 Jpql의 N+1 문제점이다.
- 만약 조인된 테이블이 10개라면, 필요없어도 조인된 테이블 10개를 모두 조회한다.
- 그래서, 지연로딩(LAZY)을 이용하여 성능을 증가시킨다.
XToOne
매핑은 기본이 ‘즉시 로딩’이라서 ‘지연 로딩’으로 추가 설정 해주기!- @ManyToOne, @OneToOne
- 결론 : 가급적 지연로딩 이용하기**
- 즉시 로딩은 상상하지 못한 쿼리가 나간다.
- 중요** : JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라!**
1) 지연 로딩
- Member와 Team 테이블에서 원하는 테이블만 따로 조회가 가능하다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
2) 즉시 로딩
- Member와 Team 테이블을 함께 조회!
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
8. CASCADE, 고아객체
1) CASCADE
a. CASCADE 개념
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때, 사용한다. DB의 CASCADE 속성과도 같은 의미
- 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.
@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)
b. CASCADE 주의점**
- 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
c. 종류
-
ALL: 모두 적용
-
PERSIST: 영속
-
REMOVE: 삭제
-
MERGE: 병합
-
REFRESH: REFRESH
-
DETACH: DETACH
2) 고아 객체
a. 고아 객체 개념
- 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
- 참조하는 곳이 하나일 때 사용해야함
- 특정 엔티티가 개인 소유할 때 사용
- @OneToOne, @OneToMany만 가능
- orphanRemoval = true
- 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다
b. 영속성 전이 + 고아 객체, 생명주기
- CascadeType.ALL + orphanRemoval=true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때, 유용
9. 연관관계 관리
1) 글로벌 페치 전략 설정**
- 모든 연관관계를 지연 로딩으로!!
- @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로!!
2) 영속성 전이 설정**
- Order -> Delivery를 영속성 전이 ALL 설정
- 주문을 생성할 때, 배송 정보도 같이 생성해서 라이프사이클을 맞추기 위해서 CascadeType.ALL로 설정
- 즉, 주문을 저장할 때, 배송도 같이 저장된다.(영속성 전이)
- 하지만, 설계상 Delivery의 라이프사이클을 따로 관리하고 싶으면, Order에서 따로 빼서 설계하는 것도 가능하다!**
- Order -> OrderItem을 영속성 전이 ALL 설정
10. 값 타입
1) 기본값 타입
- 생명주기를 엔티티에 의존
a. 엔티티 타입
@Entity
로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능
- 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
b. 값 타입
- int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체
- 식별자가 없고 값만 있으므로 변경시 추적 불가
- 값 타입은 공유하면 안 된다.(불가능)**
- 회원 이름 변경시 다른 회원의 이름도 함께 변경되면 안됨
c. 자바의 기본 타입은 절대 공유 X
- 우리가 아는 기본 자바 개념
- 기본 타입은 항상 값을 복사한다. 따라서, int, double 같은 기본 타입(primitive type)은 절대 공유 X
- Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유가능한 객체이지만 변경X
2. 임베디드 타입**
- 새로운 값 타입을 직접 정의할 수 있음
- 기본 값 타입을 모아서 만들어서
복합 값 타입
이라고도 부르고 JPA는임베디드 타입(embedded type)
이라고 부른다.- int, String과 같은 값 타입
a. 임베디드 타입 예시
- 원래, 회원 엔티티는 이름, 근무 시작일, 근무 종료일, 주소 도시, 주소 번지, 주소 우편번호를 가진다.
- 임베디드 타입을 이용해서 회원 엔티티를 변경하여 이름, 근무 기간, 집 주소를 가지도록 하자!
- 즉,
Period
라는 테이블(workPeriod)과Address
라는 테이블(homeAddress)을 추가로 생성한다.**- Period 테이블에는 근무 시작일(startDate), 근무 종료일(endDate)을 속성으로 넣는다.
- Address 테이블에는 주소 도시(city), 주소 번지(street), 주소 우편번호(zipcode)를 속성으로 넣어 복합 값 타입으로 만든다.
b. 임베디드 타입 사용법**
- 기본 생성자 필수
-
@Embeddable
: 값 타입을 정의하는 곳에 표시 -
@Embedded
: 값 타입을 사용하는 곳에 표시
c. 임베디드 타입의 장점
- 재사용이 가능하다.
- 높은 응집도를 가진다.
Period.isWork()
처럼 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있음
- 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존함
d. 임베디드 타입과 테이블 매핑
- 임베디드 타입은 엔티티의 값일 뿐이다.
- 따라서, 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다.
- 객체와 테이블을 아주 세밀하게(find-grained) 매핑하는 것이 가능!!
e. @AttributeOverride: 속성 재정의
- 한 엔티티에서 같은 값 타입을 사용하면, 컬럼 명이 중복된다.
- 따라서, 이럴 경우에는
@AttributeOverrides
,@AttributeOverride
를 사용해서 컬러 명 속성을 재정의
f. 임베디드 타입과 null
- 임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null
3. 값 타입과 불변 객체
a. 값 타입
- 값 타입은 복잡한 객체 세상을 조금이라도 단순화하려고 만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
a) 값 타입 공유 참조**
- 임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험함
- 예를 들어, 같은 주소를 같이 사용하는 회원1, 회원2가 있는데 주소를 변경하면, 해당 회원들의 주소들이 같이 변경되기 때문에 심각한 문제가 발생한다.
b) 값 타입 복사**
- 값 타입의 실제 인스턴스인 값을 공유하는 것은 위험
- 대신 값(인스턴스)를 복사해서 사용!!
b. 불변 객체**
- 항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다. 하지만, 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다. 즉, 객체의 공유 참조는 피할 수 없다.
- 그래서, 등장한 것이
불변 객체
이다.
a) 불변 객체 개념 및 생성 방법**
- 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단!!
- 값 타입은 불변 객체(immutable object)로 설계해야 한다.
- 불변 객체: 생성 시점 이후 절대 값을 변경할 수 없는 객체
- 생성자로만 값을 설정하고 수정자(Setter)를 만들지 않으면 된다**
- 예를 들어, Integer, String은 자바가 제공하는 대표적인 불변 객체!!
4. 값 타입의 비교
- 값 타입: 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야 한다.
- 동일성(identity) 비교
- 인스턴스의 참조 값을 비교,
==
사용
- 인스턴스의 참조 값을 비교,
- 동등성(equivalence) 비교
- 인스턴스의 값을 비교,
equals()
사용
- 인스턴스의 값을 비교,
5. 값 타입 컬렉션**
- 값 타입을 하나 이상 저장할 때, 사용하고 컬렉션 데이터를 저장할 때, 사용한다. 매우 편리!
@ElementCollection
,@CollectionTable
사용
- 데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
- 원래의 DB의 경우에는 컬렉션을 저장하기 위한 별도의 테이블이 필요함
a) 값 타입 컬렉션 사용 전략
- 값 타입 컬렉션도 지연 로딩 전략 사용**
- 값 타입 컬렉션은 영속성 전에(Cascade) + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다
b) 값 타입 컬렉션의 제약사항**
- 값 타입은 엔티티와 다르게 식별자 개념이 없다.**
- 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.**
- 값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 함: null 입력X, 중복 저장 X**
6. 값 타입 컬렉션 주의 사항!**
-
값 타입은 정말 값 타입이라 판단될 때만 사용
-
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨
-
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티
7. 실전 예제 : 값 타입 매핑
a. 실습 코드
11. 객체지향 쿼리 언어
1) 페치 조인(fetch join)
a. fetch join 개념
- SQL 조인 종류X
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- join fetch 명령어 사용
- 페치 조인 :
:= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
b. fetch join 사용 과정
- 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
- SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
- [JPQL]
- select m from Member m join fetch m.team
- [SQL]
SELECT M.*, T.* FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
2) 페치 조인 사용 예시
a. 페치 조인 사용 예시
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : members) {
//페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩X
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
}
b. 컬렉션 페치 조인 사용 코드
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for(Team team : teams) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println(“-> username = " + member.getUsername()+ ", member = " + member);
}
}
3) 페치 조인 사용하는 이유**
- 실무에서 ‘지연로딩’을 전체적으로 주로 사용하는데 다른 테이블을 함께 조회하고 싶을 때, fetch join을 사용하여 일부분만 함께 테이블을 조회할 수 있도록 하자!