1. 경로 표현식

: 점(.)을 찍어 객체 그래프를 탐색하는 것을 말한다

select m.username -> 상태 필드(state field): 단순히 값을 저장하기 위한 필드, 경로 탐색의 끝으로 더이상의 탐색은 불가능

from Member m

join m.team t -> 단일 값 연관 필드: 연관관계를 위한 필드로, @ManyToOne, @OneToOne, 대상이 엔티티인 필드다. 

시적 내부 조인이 발생하고, 탐색 가능하다 

join m.orders o -> 컬렉션 값 연관 필드: 연관관계를 위한 필드로, @OneToMany, @ManyToMany, 대상이 컬렉션인 필드다

묵시적 내부조인 발생하고, 탐색 불가능하다

from 절에서 명시적 조인을 통해 별칭을 얻으면 별칭을 통해 탐색 가능하다 

where t.name='팀A'

 

-상태 필드 경로 탐색(JPQL,SQL 동일하다)

1) JPQL: select m.username, m.age from Member m

2) SQL: select m.username, m.age from Member m

 

-단일 값 연관 경로 탐색

1)JPQL: select o.member from Order o

2)SQL: select m.* from Orders o inner join Member m on o.member_id=m.id(공부)

 

 

-명시적 조인/ 묵시적 조인 

1) 묵시적 조인: join 키워드를 직접 사용한다.

-> select m from Member m join m.team t

2) 명시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인이 발생한다(내부조인만 가능하다)

-> select m.team from Member m 

 

-경로 탐색을 사용한 묵시적 조인 시 주의사항 

항상 내부 조인을 한다

컬렉션은 탐색의 끝이므로 명시적 조인을 통해 별칭을 얻어야 한다

경로 탐색은 주로 select,where 절에서 사용하지만 묵시적 조인으로 인해 sql의 from (join) 절에 영향을 준다 

 

-묵시적 내부 조인이 발생하지 않게 쿼리를 짜야한다. 중요!!

//회원1, 팀1(SQL 가져옴)

//회원2, 팀1(1차 캐시에서 가져옴)

//회원3, 2(SQL 가져옴)

 

===> 가급적 묵시적 조인 대신에 명시적 조인을 사용한다. 

조인은 SQL 튜닝에 중요 포인트이다

묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어렵다 

 

 

 

 

 

조인 

1. 종류

1) 내부조인: select m from Member m JOIN m.team t (조인 값 중 하나가 Null이면 결과테이블에 포함하지 않는거)

2)외부조인: select m from Member m LEFT JOIN m.team t (조인 값 중 하나가 Null이어도 결과테이블에 포함)

3)세타조인: select count(m) from Member m, Team t where m.username=t.name

 

2. ON 절

1) 조인 대상을 필터링할 수 있다

예시: 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인한다(팀이름이 A인 팀과 멤버를 조인한다)

JPQL: select m,t from Member m LEFT JOIN m.team t on t.name='A'

SQL: select m.*, t.* from Member m LEFT JOIN Team t on m.TEAM_ID=t.id  and t.name='A'

 

2) 연관관계 없는 엔티티를 외부 조인한다(하이버네이트 5.1부터)

예시: 회원의 이름과 팀의 이름이 같은 대상 외부조인한다(회원이름=팀이름)

JPQL: select m,t from Member m LEFT JOIN Team t on m.username=t.name  

SQL: select m.*,t.* from Member m LEFT JOIN Team t on m.username=t.name  

 

3. 서브 쿼리 

예시

나이가 평균보다 많은 회원 

=> select m from Member m where m.age > (select avg(m2.age) from Member m2)

한 건이라도 주문한 고객

select m from Member m where (select count(o) from Order o where m=o.member)>0

 

- 서브쿼리 지원함수 

1) [NOT] EXISTS(subquery): 서브 쿼리에 결과가 존재하면 참을 반환

2) {ALL} (subquery) : 모두 만족 하면 참 

3) {ANY/SOME} (subquery): 같은 의미로, 조건을 하나라도 만족하면 참을 반환한다

4) [NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참을 반환한다

 

-조인예제

1) 팀A 소속인 회원

select m from Member m where exists(select t from m.team t where t.name='팀A')

2) 전체 상품 각각의 재고보다 주문량이 많은 주문들

select o from Order o where o.orderAmount  > ALL(select p.stockAmount from Product p)

3)어떤 팀이든 팀에 소속된 회원

select m from Member m where m.team=ANY(select t from Team t)

 

-서브 쿼리의 한계

JPQ는 SELECT(하이버네이트에서 지원),WHERE,HAVING 절에서만 서브 쿼리를 사용 가능하다

FROM 절의 서브 쿼리는 현재 JQPL에서 불가능하다(조인으로 풀 수 있으면 풀어서 해결한다)

 

4. JPQL 타입 표현

-문자: 'HELLO', 'She''s'

-숫자: 10L(Long), 10D(Double), 10F(Float)

-Boolean: True,False

-ENUM: jpabook.MemberType.Admin(패키지명 포함)

-엔티티 타입: TYPE(m) = Member. 이 식은 상속 관계에서 사용

 

5. 기타

1) SQL과 문법이 같은 식들 : EXISTS/IN, AND/OR/NOT, 부등호, BETWEEN,LIKE,IS NULL

2) 조건식: CASE 식 

 

COALESCE: 하나씩 조회해서 null이 아니면 반환한다(m.username,'이름없는 회원' 중에서 null이 아닌 값을 반환한다. username이 null이면 '이름 없는 회원'은 not null 이므로 이름없는 회원이 반환된다)

 

NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값을 반환한다 

 

6. JPQL 기본 함수

concat: 문자 두개 합하는 거 

substring:  문자열 일부 

trim :띄어쓰기 없애는거

lower,upper: 소문자,대문자로 바꾸기

length: 문자열의 길이 

locate(‘a’,’b’): a가 b에서 몇번째에 있는지 위치(Int) 알려줌

abs,sort,mod: 수학 연산

size: 콜렉션의 크기를 알려준다

 

7. 사용자 정의 함수 호출 

하이버네이트는 사용전 방언에 추가해야 한다.

사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다. 다음은 예시다

과정: dialect 디렉토리에 Myh2Dialect 만들고 메소드 만든 다음 

-> persistent.xml Myh2Dialect 변경하고 실행하면 된다

-JPA의 데이터 타입 분류

1. 엔티티 타입

@Entity로 정의하는 객체

데이터가 변해도 식별자로 지속해서 추적이 가능하다

예) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식이 가능하다

 

엔티티 타입 특징

1) 식별자가 있다

2) 생명 주기를 관리한다

3) 공유할 수 있다 

 

2. 값타입(기본값 타입, 임베디드 타입, 컬렉션 값 타입)

int,Integer,String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체를 말한다

식별자가 없고 값만 있으므로 변경시 추적이 불가능하다 

예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체하는 것이다 

 

값 타입의 특징

1) 식별자가 없다

2) 생명주기를 엔티티에 의존한다

3) 공유하지 않는 것이 안전하다(복사해서 사용한다)

 

 

1)기본값 타입

자바 기본 타입(int,double)

래퍼(wrapper인가?) 클래스(Integer,Long)

String

생명주기를 엔티티에 의존한다(회원을 삭제하면 이름,나이 필드도 함께 삭제된다)

값 타입은 공우하면 안된다(회원 이름 변경시 다른 회원이 이름도 함게 변경되면 안되므로)

 

-자바의 기본 타입(int,double같은 primitive type)은 절대 공유하면 안된다!!

이유: 기본 타입은 항상 값을 복사한다. 그래서 복사된 값 중 하나가 변경되면 그것을 참조하는 다른 값들도 다 변경된다

Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 공유가능한 객체이지만 변경하지 않는다 

 

2)임베디드타입(embedded type, 복합 값 타입)

 

새로운 값 타입을 직접 정의할 수 있다. 

JPA에서는 embedded type일고 한다. 

주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다

int,String(기본 값 타입)과 같은 값 타입이다 

 

-회원엔티티가 모든 정보를 가지고 있어도 되지만, 

startDate/endDate 필드는 Period 객체에

city/street/zipcode 필드는 Addres 객체에 넣어서 Member에서 연관관계를 형성할 수도 있다 

 

 

-임베디드 타입 사용법 

@Embeddable: 값 타입을 정의하는 곳에 표시

@Embedded: 값 타입을 사용하는 곳에 표시

기본 생성자는 필수다 

 

-임베디드 타입의 장점

재사용이 가능하다

높은 응집도를 가진다

Period.isWork()처럼 해당 값 타입만 사용하는 의미있는 메소드를 만들 수 있다

임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존한다 

 

-임베디드 타입과 테이블 매핑 

임베디드 타입은 엔티티의 값일 뿐이다.

임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같다

객체와 테이블을 아주 세밀하게(fine-grained) 매핑하는 것이 가능하다

잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다 

 

 

-임베디드 타입과 연관관계

 

 

-@AttributeOverride: 속성 재정의

한 엔티티에서 같은 값 타입을 사용하면? 컬럼명이 중복된다

@AttributeOverride, @AttributeOverrides를 사용해서 컬럼명 속성을 재정의한다 

 

 

-임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null이다 

 

 

값 타입

값 타입은 객체 세상을 조금이라도 단순화하기 위해 만든 개념이므로 단순하고 안전하게 다룰 수 있어야 한다 

-값 타입 공유 참조

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.

부작용이 발생한다 

 

-값 타입 복사

값 타입의 실제 인스턴스인 값을 공유하는 것은 위험하다

대신 값(인스턴스)를 복사해서 사용한다 

 

 

-객체 타입의 한계

항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용을 피할 수 있다

문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다 

자바 기본 타입에 값을 대입하면 값을 복사하지만

객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없다

객체의 공유 참조는 피할 수 없다 

 

-불변 객체: 생성시점 이후 절대 값을 변경할 수 없는 객체 

객체 타입을 수정할 수 없게 만들면 부작용을 원천 차단할 수 있다

갑 타입은 불변객체(immutable object)로 설계해야한다

생성자로만 값을 설정하고 수정자(setter)를 만들지 않으면 된다

참고: Integer,String은 자바가 제공하는 대표적인 불변 객체이다 

 

===> 불변이라는 작은 제약으로 부작용이라는 큰 재앙을 막을 수 있다

 

-값 타입 비교: 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야한다 

1)동일성(identity) 비교:  인스턴스의 참조 값을 비교하고 == 사용한다

2)동등성(equivalence) 비교: 인스턴스의 값을 비교, equals()를 사용한다 

 

*값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야한다*

값타입의 equals() 메소드를 적절하게 재정의한다(주로 모든 필드에서 사용한다?)

 

 

3)컬렉션 값 타입(collection value type)

 

-값 타입 컬렉션이란 

값 타입을 하나 이상 저장할 때 사용한다

@ElementCollection, @Collection Table 어노테이션을 사용한다

데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없으므로 컬렉션을 저장하기 위한 별도의 테이블이 필요하다 

 

참고: 값 타입 컬렉션은 영속성(cascade) 전에 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다 

 

 

-실습 코드

 

 

-값 타입 컬렉션의 제약사항

값 타입은 엔티티와 다르게 식별자 개념이 없다

값은 변경하면 추적이 어렵다

값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 

값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다(값타입 컬렉션 변경 발생 -> 값 타입 컬렉션 모두 삭제 -> 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장)

값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야한다. null 입력x, 중복저장 x

 

-값 타입 컬렉션 대안-> 여기 실습 코드 보면서 공부 해야할 것 같음 

실무에서는 상황에 따라 값 타입 컬렉션 대신에 일대다 관계를 고려한다

일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용한다

영속성 전이(cascade)+고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용한다

 

예시) Address Entity

 

=====> 값 타입은 정말 값 타입이라 판단될 때만 사용한다

엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨

식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티다 

1. 지연로딩, 즉시로딩 

 

-연관관계 조회시 지연로딩 LAZY를 사용해서 프록시로 조회한다 

 

Member에있는 team 객체는 TEAM_ID로 외래키로 조회한다. 

만약 이 객체를 사용하지 않는다면 프록시에 team을 담아 놓고, team을 사용하는 그때 db에서 해당 정보를 조회한다(사용하는 시점에 초기화)

m.getTeam().getName(): 이때 초기화가 발생한다

 

-지연로딩이 아닌 즉시로딩(EAGER)을 사용한 경우

Member 조회시 항상 Team도 조회된다. member 조회 쿼리가 나가면 반드시 team 조회 쿼리도 나감 

 

-프록시와 즉시로딩에 대해 주의할 점 

 

가급적 실무에서는 지연 로딩만 사용한다!

즉시 로딩을 적용하면 예상하지 못한 SQL이 발생하고

즉시로딩은 JPQL에서 N+1 문제를 일으킨다(team 하나(1)를 조회하면 그거에 해당하는 멤버 N을 조회하는 경우-> N+1)

@ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 fetch=FetchType.LAZY로 설정하기

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

 

-지연로딩을 사용하는 경우를 구분하면

 

다대일 관계에서 참조하는 객체를 가끔 사용한다면 지연로딩을, 

자주 사용한다면 즉시로딩을 하면된다. 

참조하는 객체를 항상 사용하지 않는 경우 -> 지연로딩

지연로딩을 하면 연관된 프록시로 가져온다 

 

위 엔티티 관계에서

Member/Order 가끔 사용 -> 지연로딩

Order/Product 자주 사용 -> 즉시로딩

Member/Team 자주 사용 -> 즉시로딩 

 

-실무에서 지연로딩 활용

 

모든 연관관계에서 지연 로딩을 사용해라!

실무에서 즉시 로딩을 사용하지마라! 즉시 로딩은 상상하지 못한 쿼리가 나간다

JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라

타입 컬렉션들은 기본으로 지연로딩

 

궁금한점,알아봐야할 것: 

 

처음에 엔티티로 조회했으면 이후에 프록시로 해도 디비에서 가져온다 (엔티티를 가져오는 건가?)

처음에 reference로 조회했으면 이후에 엔티티 조회해도 프록시를 가져온다?

 

2.영속성 전이: CASCADE

 

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용한다 

예시: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장(1:n)

-Cascade를 설정하지 않는다면, 부모/자식 객체 모두 일일이 persist,remove 해줘야한다 

 

-또한 부모/자식이 양방향 연관관계인 경우 하나의 값이 변경되면 연관된 객체에도 값 변경을 해줘야한다. 

(다음 사진은 연관관계 메소드를 작성해서 두 곳에 변경된 값을 입력하는 경우이다)

 

-주의할 점!

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

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

단일 엔티티에 종속될 때만 cascade 사용할 수 있다. 엔티티가 다른 엔티티와 연관되어 있다면 cascade 사용하면 안됨

 

-cascade를 사용하는 경우

1) parent,child의 라이프 사이클이 유사할 때

2) 단일 소유자일  

 

-종류: ALL(모두 적용),PERSIST(영속),REMOVE(삭제)를 주로 사용한다 

 

3. 고아객체

고아 객체 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 의미한다

고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 제거한다 

orphanRemoval=true

 

-주의

참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아객체로 보고 삭제하는 기능이므로 참조하는 곳이 하나일 때 사용해야한다!

특정 엔티티가 개인 소유할 때 사용 가능하다

@OneToOne, @OneToMany만 가능하다 

 

-참고

개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면(orphanRemoval=true), 부모를 제거할 때 자식도 제거된다. 이것은 CascadeType.Remove처럼 동작한다 

 

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

CascadeType.ALL+orphanRemoval=True

orphanRemoval=True: 부모 객체를 삭제하면 자식 객체가 고아가되는데, 그러한 고아객체 삭제를 활성화한다(CascadeType.Remove처럼 행동한다)

여기서 CacadeType.ALL을 추가하면 부모 엔티티를 통해서 자식의 생명주기도 관리할 수 있다. 

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

 

궁금한점: orphanRemoval=True가 CascadeType.Remove처럼 행동하는데 CascadeType.ALL을 추가하는 이유는 

Persist같은 연산도 영속하기 위해서인건가?

 

갑자기 궁금해져서 찾아본거 

클래스,객체,인스턴스의 차이

클래스는 어떠한 변수와 메소드를 가지는 명세서,설계도

객체는 우리가 클래스로 구현할 어떤 것 

인스턴스는 객체를 실체화 시킨 것 

+ Recent posts