@ManyToOne의 속성들

dev_314·2023년 3월 7일
0

JPA - Trial and Error

목록 보기
8/16

@ManyToOne

targetEntity

association의 대상이 되는 Entity(class)를 명시적으로 지정할 수 있다.

따로 설정하지 않으면, 기본적으로 필드의 타입값으로 설정된다.

@ManyToOne(targetEntity = Car.class)
@JoinColumn(name = "user_id", nullable = false)
private User user;

필드의 타입은 User인데, targetEntity로 지정한 타입은 Car이므로, 컴파일 타임에 어떤 Table(Class, Entity)를 reference하는지 알 수 없어서 예외가 발생한다.

org.hibernate.sql.ast.tree.from.UnknownTableReferenceException: Unable to determine TableReference (`car`) for `autobid.autobid.bid.domain.Bid.user.auctions.{fk-target}
@ManyToOne(targetEntity = User.class)
@JoinColumn(name = "user_id", nullable = false)
private User user;

같은 경우에는 문제 없이 작동한다. 생략해도 무관하다.

cascade

부모가 어떻게 변할 때, 자식이 부모의 상태를 따라갈 것인가를 지정할 수 있다.
기본적으로는 자식은 부모의 어떤 작업에도 상태가 변하지 않는다.

public enum CascadeType { 
    ALL, 
    PERSIST, 
    MERGE, 
    REMOVE,
    REFRESH,
	DETACH
}

CascadeType.REMOVE와 orphanRemoval = true의 차이

class Parent {
	@OneToMany(mappedBy = "parent", cascade = {CascadeType.REMOVE})
    private List<Child> children = new ArrayList<>();
}

class Child {
	@ManyToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;
}

위와 같은 상황에서 아래의 코드를 수행한다면,

Parent p = repo.find(...); // p에 자식이 들어있는 상황
repo.remove(p); 

p가 remove되었으므로, 자식도 따라서 remove 된다. -> 자식에 대한 DELETE 쿼리가 발생한다.

그러나 아래의 상황에서는 자식에 대한 DELETE 쿼리가 발생하지 않는다.

p.children.remove(0); // 0번 자식을 제거

부모가 remove된 것이 아니므로, 자식도 remove되지 않는 것이다.


orphanRemoval@OneToMany의 속성이다.

@OneToMany(mappedBy = ..., orphanRemoval = true) // 기본값은 false

orphanRemoval을 사용할 경우, 두 상황 모두에서 자식이 제거된다.

// case 1. 부모 객체 제거
repo.remove(p); -> 자식도 따라서 DELETE 쿼리 발생
// case 2. 자식 객체만 제거
p.child.remove(0) -> 연관 관계가 끊어졌으므로 자식에 대해서만 DELETE 쿼리 발생

fetch

특정 필드를 실제로 언제 불러올 것인지 지정할 수 있다.
@OneToMany의 경우 기본적으로 FetchType.LAZY로 설정되어 있다.

Lazy Loading을 사용할 경우, 해당 필드에는 Proxy 객체가 할당된다.
해당 필드를 실제로 사용할 때 조회 쿼리가 발생한다.

// `@ManyToOne`는 `FetchType.EAGER`가 기본값.
@ManyToOne(fetch = FetchType.EAGER)
// `@OneToMany`는 `FetchType.LAZY`가 기본값.
@OneToMany(fetch = FetchType.LAZY) 

optional

FK에 not null 제약조건 여부를 지정할 수 있다.

// Car Entity
@ManyToOne(optional = false) // true가 기본값
private User user;

// User Entity
@OneToMany(mappedBy = "user")
private List<Car> cars;

optional = false로 설정되어 있으면, FK에 not null 제약조건이 걸린 DDL이 발생한다.

기본값인 optional = true을 사용하면 FK가 nullable하므로, User가 없는 Car (record)가 존재할 수 있다.

이런 상황에서 Car를 조회하면, Join이 포함된 쿼리의 결과에 차량 데이터가 포함되지 않을 수 있다.

SELECT *
// car의 user_id = null인 레코드는 제외된다.
FROM Car JOIN User ON Car.user_id = User.id 
WHERE Car.id = ?;

이를 막기 위해, optional = true인 경우 JPA는 OUTER JOIN을 수행한다.

select b1_0.id, a1_0.id 
from b b1_0 left join a a1_0 on a1_0.id=b1_0.fk 
where b1_0.id=?
# 'Outer' 생략 가능
# OUTER Join에는 LEFT, RIGHT, FULL JOIN이 있다
# MySQL은 OUTER JOIN을 지원하지 않는다 -> 대신 Left + Right로 대체 가능
// em : EntityManager
B b = em.find(B.class, 2l);
b // not null
b.getA() // null

그런데 optional = false로 설정하면, FK가 not null하므로, FK가 없는 레코드가 생성될 수 없다. 그러므로 JPA는 INNER JOIN을 수행한다.

select b1_0.id, a1_0.id 
from b b1_0 join a a1_0 on a1_0.id=b1_0.fk 
where b1_0.id=?
// em : EntityManager
B b = em.find(B.class, 2l);
b // not null: Table B 레코드의 FK가 not null이다는 가정하에
b.getA() // not null

만약 어떤 이유에서든지 Table B 레코드의 FK가 Null이면, optional = false로 인해 INNER JOIN이 수행되므로, b는 null이 된다.

profile
블로그 이전했습니다 https://dev314.tistory.com/

0개의 댓글