JPA 스터디 - 5

한상우·2022년 12월 18일
0

JPA

목록 보기
5/7

JPA 9장

JPA 데이터 타입은 크게 엔티티 타입과 값 타입이 있다.

이번 장에서는 값 타입에 대해 공부

엔티티 타입


  • 식별자가 있음
  • 생명주기가 있음
  • 공유할 수 있음

값 타입에는 3가지가 존재

기본값 타입


말 그대로 기본 값이다. 자바가 제공하는 기본 데이터 타입이다.

String, int …

임베디드 타입


새로운 값 타입을 직접 정의해서 사용

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

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

@Entity
public class Member {
	
	@Id @GeneratedValue
	private Long id;
	private String name;

	@Embedded Address homeAddress;
}

@Embeddable
public class Address {
	@Column(name="city")
	private String city;

	private String street;
}

Member 엔티티가 더욱 응집력 있게 바뀌도록

임베디드 타입은 엔티티의 값일 뿐이기에, 테이블에 매핑은 똑같이 들어간다.

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

지루한 반복 작업은 JPA에 맡기고 더 세밀한 객체지향 모델을 설계하는데 집중하자

  • @AttributeOveeride 라고 속성 재정의 하는게 있는데, 복잡해지니 쓰지 말자
  • 임베디드 타입이 null이면 매핑한 컬럼은 모두 null로 된다

값 타입과 불변 객체


값 타입을 여러 엔티티에서 공유하면 위험하다.

(왜 인지는 알지??)

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

자바에서 객체는 대입을 할 때 참조를 넘겨주기에 객체의 공유 참조를 피할 수 없다.

때문에 외부에서 원본 객체의 참조값을 받은 다른 객체가 원본 객체의 값을 수정할 수도 있는 것이다. 이를 방지하기 위해 수정자 메소드를 모두 제거하는게 안전하다.

→ 객체를 불변 객체로 만들자!!

값 타입 비교


동일성 비교 : == 연산자 , 인스턴스 참조값 비교

동등성 비교 : equals() 사용, 인스턴스 값 비교

동등성 비교를 위해서는 equals() 메소드를 재정의 해야 하고, hsahCode 도 재정의 하는 것이 안전하다

(hashCode 란 객체를 식별하는 정수값)

값 타입 컬렉션


안볼랭

JPA 10장

객체지향 쿼리 소개

JPQL

Criteria

QueryDSL

네이티브 SQL

객체지향 쿼리 심화

객체지향 쿼리


em.find() 메소드를 통해 엔티티 하나를 조회할 수 있지만,

복잡한 검색일 경우 SQL로 필요한 내용을 최대한 걸러서 조회해야 한다.

ORM은 DB 테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 엔티티 객체를 대상으로 하는 방법이 필요

이를 위해 만들어진게 JPQL

JPQL


특징

  • 객체지향 쿼리언어
  • SQL을 추상화해서 특정 DB SQL 에 의존하지 않는다.
  • JPA에 의해 결국 SQL로 변환된다.

JPQL의 기본 문법과 쿼리 진행

(INSERT는 없음, em.persist() 메소드가 있기 때문에)

update, delete는 뒤에서 진행

SELECT

SELECT m FROM Member AS m where m.name = "Sangwoo"
  • 엔티티와 속성은 대소문자 구분하지만, JPQL 키워드(SELECT, FROM)는 구분하지 않음
  • Member는 클래스 명이 아니라 엔티티 명이다.
  • Member AS m 처럼 별칭은 필수이다. (AS 생략 가능)

TypeQuery, Query

JPQL을 실행하려면 쿼리 객체를 만들어야 함.

반환할 타입이 명확하다면 TypeQuery

명확하지 않다면 Query

TypedQuery<Member> query = 
em.createQuery("SELECT m FROM Member m", Member.class);
// 두번째 파라미터에 반환할 타입을 지정

Query query = 
em.createQuery("SELECT m FROM Member m");
// 지정하지 않으면 Query 반환

// 결과 조회
List result = query.getResultList();
// 결과가 정확히 하나라면 
query.getSingleResult() 사용
  • Query객체는 조회 대상이 둘 이상이면 Object[] 반환 하나면 Object 반환 - SELECT m.username, m.age FROM Member m

파라미터 바인딩

// 이름 기준
em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);

// 위치 기준 (? 다음에 위치값 주면 됨 (1부터 시작))
em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
			.setParameter(1, usernameParam);
  • 이름 기준 파라미터 방식 사용하는게 더 명확
  • 파라미터 바인딩 사용하지 않고 문자를 더해 만들면 SQL 인젝션 공격을 당할 수 있고, 성능 이슈(JPA나 DB에서 같은 쿼리는 재사용)도 있다 → 파라미터 바인딩 필수로 사용하자

프로젝션

SELECT 절에 조회 대상을 지정하는 것

  1. 엔티티 프로젝션
  • SELECT m FROM Member m
  • 원하는 객체를 바로 조회 (컬럼을 하나하나 나열하는 SQL과 차이가 있음)
  • 조회한 엔티티는 영속성 컨텍스트에서 관리
  1. 임베디드 타입 프로젝션
  • 임베디드 타입은 조회의 시작점이 될 수 없음
  • SELECT a FROM Address a (X)
  • SELECT o.address FROM Order o
  • 임베디드 타입은 값 타입이여서 영속성 컨텍스트에서 관리되지 않는다.
  1. 스칼라 타입 프로젝션
  • 기본 데이터 타입들이 스칼라 타입임
em.createQuery(”SELECT m.username, m.age FROM Member m”) 
을 해주면 두 필드를 프로젝션해 Object를 받을 텐데, 
이때 NEW 명령어를 통해 반환받을 클래스를 지정할 수 있다.

em.createQuery(”SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m”
, UserDTO.class)

페이징 API

DB마다 페이징을 처리하는 SQL 문법이 다르다.

JPA는 이를 추상화하였음

  • setFirstResult: 조회 시작 위치
  • setMaxResults: 조회할 데이터 수

집합과 정렬

집합 함수

  • COUNT: 결과 수 반환, 반환타입 Long

  • MAX, MIN

  • AVG: 평균 값, 반환타입 Double

  • SUM

  • GROUP BY, HAVING : 특정 그룹끼리 묶어줌

  • ORDER BY: 정렬할 때 사용

    • ASC : 오름차순, DESC : 내림차순

JPQL 조인

내부조인: INNER JOIN (INNER 생략 가능)

SELECT m FROM Member m INNER JOIN m.team t
where t.name = :teamName
  • SQL문과 다르게 연관 필드를 사용

외부 조인: LEFT JOIN

SELECT m FROM Member m LEFT JOIN m.team t

컬렉션 조인

SELECT t, m FROM Team t LEFT JOIN t.members m
  • 컬렉션을 사용하는 곳에 조인

세타 조인 where 절을 통해 할 수 있단다

내부 조인만 지원

(세타 조인은 내부조인과 비슷하지만, where를 통해서 간단하게 할 수 있는거)

select m from Member m, Team t
where m.username = t.name

JOIN ON 절 외부조인에서만 사용한단다.

조인 대상 필터링 하고 조인할 수 있다.

select m, t from Member m left join m.team t on t.name = 'A'

페치 조인

성능 최적화를 위해 제공하는 기능

연관된 엔티티나 컬렉션을 한 번에 조회하는 기능

엔티티 페치 조인

select m from Member m join fetch m.team
  • 회원엔티티를 조회하면서 연관된 팀 엔티티 함께 조회
  • 회원과 팀을 같이 조회해서 지연 로딩 발생 안함
  • 회원과 팀이 지연로딩으로 설정했다고 해도, 회원을 조회할 때 페치 조인을 사용해서 팀도 같이 조회했으므로 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티다.

페치 조인과 일반 조인의 차이

select t from Team t join t.members m

where t.name =TeamA

JPQL문을 SQL로 변환할 때 조인한 회원을 조회하지 않고, 오로지 팀만 조회했다.

JPQL은 결과를 반환할 때 연관관계를 고려하지 않고, 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다.

페치 조인을 사용하면 연관된 엔티티도 함께 조회한다!!

페치 조인의 특징

엔티티에 직접 적용하는 로딩전략 (글로벌 로딩 전략) 보다 페치 조인을 우선한다.

@OneToMany(fetch = FetchType.LAZY) 보다 우선함

글로벌 로딩 전략은 지연로딩으로 하되, 최적화가 필요하면 페치 조인 적용하는게 효과적

한계

  • 패치 조인 대상에는 별칭을 줄 수 없음
  • 컬렉션을 페치조인 하면 페이징 API를 사용할 수 없다.

경로 표현식

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

알아야 할 용어

상태 필드: 값을 저장하기 위한 필드

연관 필드: 연관관계를 위한 필드

  • 단일 값 연관 필드 (대상이 엔티티) @ManyToOne , @OneToOne
  • 컬렉션 값 연관 필드 (대상이 컬렉션) @OneToMany, @ManyToMany

JPQL에서 경로 표현식을 사용해 경로 탐색을 하려면 3가지 경로에 따라 어떤 특징이 있는지 이해해야 함

  1. 상태 필드 경로 : 경로 탐색의 끝

    select m.name from Member m
  2. 단일 값 연관 경로 : 묵시적으로 내부조인이 일어남, 계속 탐색 가능

    select m.team from Member m
    • 단일 값 연관 필드로 경로 탐색을 하면 SQL에서 내부 조인이 일어남 (이를 묵시적 JOIN)
    • 명시적 조인은 직접 JOIN을 적어주는 것
  3. 컬렉션 값 연관 경로 : 묵시적으로 내부조인이 일어남, 계속 탐색 불가능

    select t.members from Team t
    select t.members.name from Team t // 경로 탐색 시작 허락 안함, 불가능

서브 쿼리

  • EXISTS : 서브쿼리에 결과가 존재하면 참
  • ALL : 모두 만족하면 참
  • ANY, SOME : 조건을 하나라도 만족하면 참
  • IN : 결과중 하나라도 같은 것이 있으면 참

다형성 쿼리

JPQL로 부모 엔티티를 조회하면 자식 엔티티도 함께 조회

Item이라는 클래스의 자식으로 Book Album Movie가 있다 가정

조인 전략을 사용하면 모든 자식을 조인을 통해 같이 조회하지만

TYPE 을 통해 조회 대상을 특정 자식 타입으로 한정할 수 있다.

동적 쿼리, 정적 쿼리

동적 쿼리: JPQL을 문자로 완성해서 직접 넘기는 것

  • em.createQuery(”select … “)

정적 쿼리(Named 쿼리): 미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용

  • 에플리케이션 로딩 시점에 JPQL문법을 체크해 오류를 빨리 확인할 수 있고, 파싱된 결과를 재사용해 성능상 이점도 있음
@Entity
@NamedQuery(
		name = "Member.findByUsername",
		query = "select m from Member m where m.username = :username")
public class Member {
}
em.createNamedQuery("Member.findByUsername", Member.class)
	.setParameter("username", "sangwoo")
	.getResultList();

Criteria


JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 api다

코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있다

단점

  • 코드가 복잡하고 장황하다
  • 직관적으로 이해하기 힘들다

JPA Criteria는 너무 장황하고 복잡하다. 반면에 QueryDSL은 같은 코드 기반이지만,

결과 코드가 JPQL과 비슷하고 직관적이다.

QueryDSL 써라

QueryDSL


복잡하고 어려운 Criteria의 단점을 극복하기 위해 쉽고 간결한 오픈소스 프로젝트

처음에 몰랐던게 QMember?? 이런게 어디서 나온거지??
-> 알고보니까 QueryDSL 설정을 해주면 @Entity 를 보고 컴파일 할 때 Q 클래스들을 생성해준대!!

profile
안녕하세요 ^^

0개의 댓글