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 MINNER 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을 사용하여 일부분만 함께 테이블을 조회할 수 있도록 하자!