Fetch Join + ON 절
JPA에서 Fetch Join 시 원하는 인덱스를 태우기 위해서 ON절을 추가하고 싶을 때가 있다.
아래의 예를 들어보자.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id @GeneratedValue
private Long id;
...
private int countryCode;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "credit_id")
private CreditCard creditCard;
public Member(... 생략, CreditCard creditCard, int contryCode) {
... 생략
if (creditCard != null) {
changeCreditCard(creditCard);
}
this.countryCode = countryCode;
}
}
여기서 Member Table은 id와 contryCode를 복합 인덱스로 가지고 있다고 가정하자.
다음은 CreditCard 엔터티를 보자.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CreditCard {
@Id
@GeneratedValue
private Long id;
private int countryCode;
public Team(... 생략, int countryCode) {
this.countryCode = countryCode;
}
}
credit_card 테이블도 id와 countryCode를 복합 인덱스로 가지고 있다.
문제는 Member에서 creditCard를 FetchJoin으로 불러오면 id만 가지고 ON절로 묶어서 가져오게 된다. 그래서 추가로 ON절을 명시해서 인덱스를 타게 만들어 주고 싶다!
@JoinColumns
문제를 해결하기 위해 @JsonColums를 사용했다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id @GeneratedValue
private Long id;
...
private int countryCode;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "credit_card_id", referencedColumnName = "id"),
@JoinColumn(name = "country_code", referencedColumnName = "country_code")
})
private CreditCard creditCard;
public Member(... 생략, CreditCard creditCard, int contryCode) {
... 생략
if (creditCard != null) {
changeCreditCard(creditCard);
}
this.countryCode = countryCode;
}
}
해당 방식은 정상적으로 동작하지 않고 아래의 에러가 발생한다.
nested exception is org.hibernate.MappingException: Repeated column in mapping
for entity: com.domain.test.Member column: country_code
(should be mapped with insert="false" update="false")
예외 로그를 보면 엔터티의 country_code 컬럼이 중복되어서 테이블과 매핑할 수 없다는 내용이다. Member가 필드로 country_code 컬럼을 이미 매핑하고 있는데 이를 조인을 사용하는 필드에서 JoinColumn으로 또 사용했기 때문이다.
예외 메세지에서 제안한 방법은 컬럼 매핑 부분에 아래의 옵션을 추가하는 방법이다.
@Column(insertable = false, updateable = false)
private int countryCode;
문제는 위 방식은 해당 엔터티가 DB로 반영(flush)될 때 해당 컬럼은 제외하게 된다. 즉, 해당 엔터티가 읽기 전용(Read only)가 되어버리므로 의도와 다르다.
@JoinColumnsOrFormula
Hibernate의 @JoinColumnOrFormula를 사용하면 해당 문제를 해결할 수 있다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member implements Serializable {
@Id @GeneratedValue
private Long id;
...
private int countryCode;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumnsOrFormulas(value = {
@JoinColumnOrFormula(column = @JoinColumn(name = "credit_card_id", referencedColumnName = "id")),
@JoinColumnOrFormula(formula = @JoinFormula(value = "country_code", referencedColumnName = "country_code"))
})
private CreditCard creditCard;
public Member(... 생략, CreditCard creditCard, int contryCode) {
... 생략
if (creditCard != null) {
changeCreditCard(creditCard);
}
this.countryCode = countryCode;
}
}
Hibernate의 @Fomula는 @Column 대신에 사용하게 되고, 실제로 매핑받는 값이 아니라 SQL의 파생된 값(derived value)을 읽기 전용 상태로 표현한다.
그래서 결과적으로 조인할 때만 내부적으로 SQL의 ON절을 만들어줘서 인덱싱을 위한 컬럼 조건을 추가할 수 있다.
Reference
'Server > Spring' 카테고리의 다른 글
Spring - Exception 처리 전략 적용기 (+ 에러 코드 문서화!) (2) | 2022.09.19 |
---|---|
Spring Security - SecurityFilterChain 사용하기! (+ WebSecurityConfigurerAdapter is Deprecated) (0) | 2022.07.18 |
Spring - REST API에서 직접 정의한 Error code를 사용하는 이유! (0) | 2022.07.03 |
Spring - REST에서 예외를 처리하는 다양한 방법! (0) | 2022.07.03 |
Spring - JpaRepository와 Querydsl 연결하기! (사용자 정의 리포지토리) (2) | 2022.06.18 |