하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이
하이버 3.0부터는 lazy=true"가 기본값이라고 나와있습니다. (여기에.. Association fetching strategies 참조)
하지만 이 말은 XML 설정에 국한 된 말입니다. 애노테이션 설정에서는 그렇치 않습니다.
여기에 2.2.5.5. Association fetching 이 부분을 참조하면 다음과 같이 나와있습니다.
@OneToOne과 @ManyToOne의 FetchMode는 Eager
따라서, XML 설정으로 lazy="true"를 생각하고 코딩한 것을 애노테이션으로 옮길 때는 모두 명시적으로 fetch=FetchMode.LAZY로 설정해주어야 예상치 못한 쿼리가 발생하는 것을 방지할 수 있습니다.
예상치 못한 쿼리는 다음과 같이 발생합니다.
이런 상황(상속 구조는 Table Per Subclass로 맵핑)에서.. User가 가지고 있는 DefaultBillingDetails를 가져온다고 가정합니다. User와 DefaultBillingDetails는 @OneToObe 이나 @ManyToOne으로 맵핑할 수 있습니다. 그러면 default가 EAGER죠.
assertNotNull(loadedUser);
BillingDetails billingDetails = loadedUser.getDefaultBillingDetails();
System.out.println("읽어올 때 타입을 알 수 있나? " + new Boolean(billingDetails instanceof CreditCard).toString());
- default 상황에서 발생하는 쿼리
user0_.id as id3_2_,
user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_2_,
user0_.name as name3_2_,
billingdet1_.BILLING_DETAILS_ID as BILLING1_0_0_,
billingdet1_.USER_ID as USER2_0_0_,
billingdet1_1_.NUMBER as NUMBER1_0_,
billingdet1_2_.ACCOUNT as ACCOUNT2_0_,
case
when billingdet1_1_.BILLING_DETAILS_ID is not null then 1
when billingdet1_2_.BILLING_DETAILS_ID is not null then 2
when billingdet1_.BILLING_DETAILS_ID is not null then 0
end as clazz_0_,
user2_.id as id3_1_,
user2_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_1_,
user2_.name as name3_1_
from
User user0_
left outer join
BILLING_DETAILS billingdet1_
on user0_.DEFAULT_BILLING_DETAILS_ID=billingdet1_.BILLING_DETAILS_ID
left outer join
CREDIT_CARD billingdet1_1_
on billingdet1_.BILLING_DETAILS_ID=billingdet1_1_.BILLING_DETAILS_ID
left outer join
BANK_ACCOUNT billingdet1_2_
on billingdet1_.BILLING_DETAILS_ID=billingdet1_2_.BILLING_DETAILS_ID
left outer join
User user2_
on billingdet1_.USER_ID=user2_.id
where
user0_.id=?
User와 User가 가지고 있는 BillingDetails를 left outter join으로 가져옵니다.
BillingDetails를 Table Per Subclass로 맵핑했기 떄문에 CREDIT_CARD와 BANK_ACCOUNT에 left ountter join을 사용하여 모든 레코드를 가져옵니다.
마지막으로 BillingDetails가 ManyToOne으로 참조하는 User를 가져옵니다.(이건 User의 DefaultBillingDetail과 연관을 맺고 있는 User가 아닙니다. 그런 속성은 없습니다. 이 속성은 User가 가지고 있는 BillingDetail 콜렉션과 양방향으로 연관을 맺고 있는 속성입니다.)
굉장한 쿼리가 만들어졌습니다. User가 defaultBillingDetail을 가져와서 아직 사용하지도 않았는데 말이죠.
- @OneToOne 또는 @ManyToOne에서 fetch=FetchMode.LAZY 로 설정한 경우
user0_.id as id3_0_,
user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
user0_.name as name3_0_
from
User user0_
where
user0_.id=?
딱 User의 정보만 가져왔습니다. 분명 가져오라고 했는데... 쿼리를 날리지 않았습니다. 그러면 이 상태에서 BillingDetail에 있는 pay()라는 메소드를 실행하면 NullPointerException이 떨어질까요?
assertNotNull(loadedUser);
BillingDetails billingDetails = loadedUser.getDefaultBillingDetails();
System.out.println("읽어올 때 타입을 알 수 있나? " + new Boolean(billingDetails instanceof CreditCard).toString());
billingDetails.pay();
System.out.println("이제는 읽어올 때 타입을 알 수 있을까? " + new Boolean(billingDetails instanceof CreditCard).toString());
이렇게 해봤습니다.
select
user0_.id as id3_0_,
user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
user0_.name as name3_0_
from
User user0_
where
user0_.id=?
읽어올 때 타입을 알 수 있나? false
Hibernate:
select
billingdet0_.BILLING_DETAILS_ID as BILLING1_0_1_,
billingdet0_.USER_ID as USER2_0_1_,
billingdet0_1_.NUMBER as NUMBER1_1_,
billingdet0_2_.ACCOUNT as ACCOUNT2_1_,
case
when billingdet0_1_.BILLING_DETAILS_ID is not null then 1
when billingdet0_2_.BILLING_DETAILS_ID is not null then 2
when billingdet0_.BILLING_DETAILS_ID is not null then 0
end as clazz_1_,
user1_.id as id3_0_,
user1_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
user1_.name as name3_0_
from
BILLING_DETAILS billingdet0_
left outer join
CREDIT_CARD billingdet0_1_
on billingdet0_.BILLING_DETAILS_ID=billingdet0_1_.BILLING_DETAILS_ID
left outer join
BANK_ACCOUNT billingdet0_2_
on billingdet0_.BILLING_DETAILS_ID=billingdet0_2_.BILLING_DETAILS_ID
left outer join
User user1_
on billingdet0_.USER_ID=user1_.id
where
billingdet0_.BILLING_DETAILS_ID=?
pay() 메소드를 호출하는 순간, BillingDetails 하나를 가져오고 해당 메소드를 호출해 줍니다.