woowacourse-study / 2022-jpa-study

🔥 우아한테크코스 4기 JPA 스터디 (22.06.13~22.07.02) 🔥
5 stars 1 forks source link

[섹션 9, 10, 11] 토르 제출합니다 #25

Closed injoon2019 closed 2 years ago

injoon2019 commented 2 years ago

섹션 9

값 타입

JPA에서 데이터 타입은 크게 두 분류가 있다. 하나는 엔티티 타입이고 다른 하나는 값 타입이다. 엔티티 타입은 식별자가 있으므로 데이터가 변해도 추적이 가능하다. 값 타입은 식별자가 없으므로 값이 변하면 추적이 불가능하다.

값 타입 종류

기본값 타입 (자바 원시 타입, 래퍼 클래스, String)

임베디드 타입

주로 기본 값 타입을 모아서 만드는 복합값 타입이다. 재사용이 가능하며, 높은 응집도를 보인다. 또한 해당 값 타입만 사용하는 의미 있는 메소드를 만들 수 있다.

Address

@Embedabble
public class Address {
    private String city;
    private String street;
    private String zipcode;

Member

@Entity
public class Member {
    ...
    @Embedded
    private Period workPeriod;

임베디드 값타입은 사용하기 전과 후에 매핑하는 테이블은 같다. 만약 한 객체에서 두 개의 같은 임베디드 타입을 가지면 중복으로 에러가 나는데, 이때는 @AttributeOverrides를 사용하면 된다.

Member

public class Member {
    ...
        @Embedded
    private Address homeAddress;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name="city", column=@Column(name = "WORK_CITY")),
        @AttributeOverride(name="street", column=@Column(name = "WORK_STREET")),
        @AttributeOverride(name="zipcode", column=@Column(name = "WORK_ZIPCODE"))
    })
}

값 타입은 공유되면 위험하다. 하지만 객체의 공유 참조는 막을 길이 없으니 불변 객체로 만들어서 사용해야 한다. 또 값타입인만큼 eq/hc도 재정의해줘야 한다.

컬렉션 값 타입

DB에는 컬렉션을 그대로 저장할 수 없으므로 일대다 테이블로 저장해야 한다.

Member

@Entity
public class Member {
    ...
    @ElementCollection
    @CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID")
    private List<Address> addressHisotry = new ArrayList<>();
}

@ElementCollection을 붙여서 컬렉션임을 표시하고, @JoinColumn은 FK를 지정해서 join할때 사용한다.

값 타입은 변경되면 추적이 어렵다. 그래서 변경되면 일일이 찾아서 지우기 어렵기 떄문에 전부 삭제하고 새로 저장한다. 성능이 안좋기 떄문에 안쓰는게 좋다!

그래서 대안은 일대다 엔티티 관계를 만들고 여기에서 값 타입을 사용하는 것이다.

AddressEntity

@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
    @Id @GeneratedValue
    private Long id;
    private Address address;

    ...

Member

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();

섹션 10

지금까지는 데이터를 가져올 때 전부 다 가져왔다. 하지만 당연히 실제로 사용하려면 조건을 걸어서 필요한 데이터만 조회해야 한다.

QueryDSL

그래서 QueryDSL을 사용하면 동적 쿼리를 쉽게 작성할 수 있다. 객체지향 쿼리 언어이며 테이블을 대상이 아닌 엔티티 객체를 대상으로 쿼리한다.

알리아스는 필수로 붙여줘야 한다.

TypeQuery

TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);

반환 타입이 명확할 때 쓰면 된다.

프로젝션

SELECT m.username, m.age FROM Member m 가져오는게 여러 값이다. 이럴 떄는 가져오려는 값들을 담은 Dto를 생성하고 그걸 이용하면 된다.

List<MemberDTO> result = em.createQuery("select new jpql.Member(m.username, m.age) from Member m", MemberDTO.class).getResultList();

네이티브 쿼리

JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능은 네이티브 쿼리를 통해 해결할 수 있다. 네이티브 쿼리를 쓸 때는 영속성 컨텍스트 관리에 더 신경써줘야 한다.

페이징

JPA는 페이징을 추상화했다. setFirstResult, setMaxResults로 쉽게 이용할 수 있다.

조인

내부 조인

SELECT m FROM Member m JOIN m.team t

외부 조인

SELECT m from Member m LEFT JOIN m.team t

세타 조

SELECT count(m) FROM Member m, Team t where m.username = t.name

서브 쿼리

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

JPA는 FROM절의 서브쿼리가 불가능하다. 그냥 다 가져와서 애플리케이션에서 나누거나 쿼리를 두 번날려야 한다.

섹션 11

경로 표현식

객체에서 점을 찍어 객체 그래프를 탐색하는 것이다.

상태 필드

단순히 값을 저장하기 위한 필드이므로 경로 탐색의 끝이다.

연관 필드

묵시적 조인을 실무에서 쓰면 잠재적 에러가 생길 수 있기 떄문에 쓰지마라.

페치 조인

연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능이다. JPQL에서 성능 최적화를 위해 제공하는 기능이다.

SELECT m from Member m join fetch m.team

회원을 조회하면서 연관된 팀도 함께 조회한다.

String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();

Member에서 lazy로 설정을 해도 쿼리를 fetch로 하면 fetch가 우선이다.

컬렉션 페치 조인

일대다 관계 (팀 입장)에서 멤버를 가져오는 것. 일대다 조인은 데이터가 늘어나는 것을 조심해야 한다.

페치 조인과 DISTINCT

일대다의 경우 데이터가 늘어날 수 있는데 이 때는 distinct를 이용해서 데이터가 중복되지 않게 해줘야 한다.

String query = "select distinct t Fro Team t join fetch t.members";

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

일반 조인시 실행된 엔티티를 함꼐 조회하지 않는다.fetch는 한번에 다 데이터를 불러온다. 이걸로 N+1 문제를 해결할 수도 있다.

페치 조인 한계

둘 이상의 컬렉션은 페치 조인할 수 없다. 또 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.

둘 이상의 컬렉션을 페치 조인할 수 없는 이유는 일대다가 데이터를 늘릴 수 있는데, 이건 일대 다대다이기 때문이다. 이 문제를 해결하기 위해 뒤집어서 다대일로 만들어서 쓸 수도 있고 @BatchSize 애노테이션을 줘도 된다.

엔티티 직접 사용

엔티티를 직접 사용하면 SQL에서 자체적으로 해당 엔티티의 기본 키 값을 사용한다.

Named 쿼리

쿼리에 이름을 부여하고 나중에 불러와서 사용하는 것이다. 동적 쿼리가 안되는 단점이 있지만, 애플리케이션 로딩 시점에 쿼리를 검증하는 것이 막강한 장점이다.

Spring Data Jpa에서는 추상화해서 사용한다.

벌크 연산

쿼리 한 번으로 여러 테이블 로우를 변경한다. 벌크 연산은 영속성 컨텍스트를 무시하고 직접 DB에 쿼리를 날리기 때문에 정합성이 꼬일 수 있다. 그래서 왠만하면 벌크 연산 수행 후 영속성 컨텍스트 초기화를 해주는게 좋다.

injoon2019 commented 2 years ago

왜 서브쿼리에서 알리아스를 쓰는게 더 낫나

image correlted subquery

injoon2019 commented 2 years ago

페치 조인, Eager 차이 https://www.inflearn.com/questions/39516