Hibernate는 세 가지 기본적인 상속 매핑 방도들을 지원한다:
table per class hierarchy
table per subclass
table per concrete class
게다가 Hibernate는 네 번째의 약간 다른 종류의 다형성을 지원한다:
implicit polymorphism(함축적인 다형성)
동일한 상속 계층구조의 다른 가지들에 대해 다른 매핑 방도들을 사용하는 것이 가능하고, 그런 다음 전체 계층 구조를 가로질러 다형성을 성취하는데 함축적인 다형성을 사용하라. 하지만 Hibernate는 동일한 루트 <class> 요소 하에서 <subclass> 그리고 <joined-subclass> 그리고 <union-subclass> 매핑들을 혼합하는 것을 지원하지 않는다. 동일한 <class> 요소 하에서 <subclass> 요소와 <join> 요소를 결합시킴으로써 table per hierarchy 방도와 table per subclass 방도를 함께 혼합시키는 것이 가능하다(아래를 보라).
별도의 매핑 문서 내에, hibernate-mapping 바로 밑에 subclass, union-subclass, 그리고 joined-subclass 매핑들을 정의하는 것이 가능하다. 이것은 단지 하나의 새로운 매핑 파일을 추가시켜서 하나의 class 계층구조를 확장하는 것을 당신에게 허용해준다. 당신은 subclass 매핑 내에 앞서 매핑된 슈퍼클래스를 명명하여 extends 속성을 지정해야 한다. 노트 : 명백하게 이 특징은 매핑 문서들의 순서를 중요하게끔 만들었다. Hibernate3 이후로, 매핑 파일들의 순서는 extends 키워드를 사용할 때 상관없다. 하나의 매핑 파일 내의 순서는 여전히 서브클래스들에 앞서 슈퍼클래스들을 정의하는데 여전히 필요하다.
<hibernate-mapping> <subclass name="DomesticCat" extends="Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>
우리가 CreditCardPayment, CashPayment, ChequePayment 구현자들을 가진 하나의 인터페이스 Payment를 갖고 있다고 가정하자. table per hierarchy 매핑은 다음과 같을 것이다:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <property name="creditCardType" column="CCTYPE"/> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
정확히 하나의 테이블이 필요하다. 이 매핑 방도에는 다음의 하나의 큰 제약이 존재한다: CCTYPE과 같이, 서브 클래스들에 의해 선언된 컬럼들은 NOT NULL 컨스트레인트들을 가질 수 없다.
table per subclass 매핑은 다음과 같을 것이다:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ... <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </joined-subclass> <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> </class>
네 개의 테이블들이 필요하다. 세 개의 서브클래스 테이블들은 슈퍼클래스 테이블에 대한 프라이머리 키 연관들을 갖는다 (따라서 그 관계형 모형은 실제로 one-to-one 연관이다).
table-per-subclass에 대한 Hibernate의 구현은 discriminator(판별자) 컬럼을 필요로 하지 않음을 노트하라. 다른 객체/관계형 매핑기들은 슈퍼클래스 테이블 속에 하나의 타입 판별자 컬럼을 필요로 하는 table-per-subclass에 대한 다른 구현을 사용한다. Hibernate에 의해 채택된 접근법은 구현하기가 훨씬 더 어렵지만 관계형 관점에서는 아마 틀림없이 보다 더 정확하다. 만일 당신이 table per subclass 방도에 대해 하나의 판별자 컬럼을 사용하고 싶다면, 당신은 다음과 같이 <subclass>와 <join>의 사용을 결합시킬 수도 있다:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass> </class>
선택적인 fetch="select" 선언은 슈퍼클래스를 질의할 때 outer join을 사용하여 ChequePayment 서브클래스 데이터를 페치시키지 않도록 Hibernate에게 알려준다.
당신은 이 접근법을 사용하여 table per hierarchy 방도와 table per subclass 방도를 혼합시킬 수 있다:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
이들 매핑 방도들 중 어떤 것에 대해, 루트 Payment 클래스에 대한 하나의 다형성 연관은 <many-to-one>을 사용하여 매핑된다.
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
우리가 table per concrete class 방도 매핑에 대해 취할 수 있는 두 가지 방법들이 존재한다. 첫 번째는 <union-subclass>를 사용하는 것이다.
<class name="Payment"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="sequence"/> </id> <property name="amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT"> ... </union-subclass> <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> ... </union-subclass> </class>
세 개의 테이블들이 슈퍼클래스들에 대해 수반된다. 각각의 테이블은 상속된 프로퍼티들을 포함하여, 그 클래스의 모든 프로퍼티들에 대한 컬럼들을 정의한다.
이 접근법의 제약은 만일 하나의 프로퍼티가 슈퍼클래스 상으로 매핑될 경우, 그 컬럼 이름이 모든 서브클래스 테이블들 상에서 같아야 한다는 점이다.(장래의 Hibernate 배포본에서 우리는 이 제약을 풀 수도 있다.) identity 생성기 방도는 union 서브클래스 상속에서 허용되지 않으며, 진정 프라이머리 키 시드는 하나의 계층구조의 모든 unioned 서브클래스들을 가로질러 공유되어야 한다.
만일 당신의 슈퍼클래스가 abstract일 경우에, 그것을 abstract="true"로 매핑하라. 물론 만일 그것이 abstract가 아닐 경우, 추가적인 테이블(위의 예제에서는 디폴트로 PAYMENT)이 슈퍼클래스의 인스턴스들을 소유하는데 필요하다.
대안적인 접근법은 함축적인 다형성을 사용하는 것이다:
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CREDIT_AMOUNT"/> ... </class> <class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class>
어느 곳에서도 우리가 명시적으로 Payment 인터페이스를 언급하지 않음을 주목하라. 또한 Payment의 프로퍼티들이 서브클래스들 각각에서 매핑된다는 점을 주목하라. 만일 당신이 중복을 피하고자 원한다면, XML 엔티티들을 사용하는 것을 고려하라(예를 들어 매핑에서 DOCTYPE 선언과 &allproperties;에서 [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]).
이 접근법의 단점은 다형성 질의들을 수행할 때 Hibernate가 생성된 SQl UNION들을 생성시키는 않는다는 점이다.
이 매핑 방도의 경우, Payment에 대한 하나의 다형성 연관은 대개 <any>를 사용하여 매핑된다.
<any name="payment" meta-type="string" id-type="long"> <meta-value value="CREDIT" class="CreditCardPayment"/> <meta-value value="CASH" class="CashPayment"/> <meta-value value="CHEQUE" class="ChequePayment"/> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any>
이 매핑에 대해 주목할 하나 이상의 것이 존재한다. 서브클래스들이 그것들 자신의<class> 요소 내에 각각 매핑되므로(그리고 Payment가 단지 인터페이스이므로), 서브클래스들 각각은 쉽게 또 다른 상속 계층구조의 부분일 수 있다! (그리고 당신은 Payment 인터페이스에 대해 여전히 다형성 질의들을 사용할 수 있다.)
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="CREDIT_CARD" type="string"/> <property name="amount" column="CREDIT_AMOUNT"/> ... <subclass name="MasterCardPayment" discriminator-value="MDC"/> <subclass name="VisaPayment" discriminator-value="VISA"/> </class> <class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> <id name="id" type="long" column="TXN_ID"> <generator class="native"/> </id> ... <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CASH_AMOUNT"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CHEQUE_AMOUNT"/> ... </joined-subclass> </class>
다시 한번, 우리는 Payment를 명시적으로 언급하지 않는다. 만일 우리가 Payment 인터페이스에 대해 하나의 질의를 실행할 경우-예를 들어, from Payment-, Hibernate는 CreditCardPayment (와 그것의 서브클래스들, 왜냐하면 그것들 또한 Payment를 구현하므로), CashPayment 그리고 ChequePayment 인스턴스들을 자동적으로 반환할 것이지만 NonelectronicTransaction의 인스턴스들을 반환하지 않는다.
table per concrete-class 매핑 방도에 대한 "함축적인 다형성" 접근법에는 어떤 제약들이 존재한다. <union-subclass> 매핑들에 대해서는 다소 덜 제한적인 제약들이 존재한다:
다음 표는 Hibernate에서 table per concrete-class 매핑들에 대한 제약들, 그리고 함축적인 다형성에 대한 제약들을 보여준다.
표 9.1. 상속 매핑들의 특징들
상속 방도 | 다형성 다대일 | 다형성 일대일 | 다형성 일대다 | 다형성 다대다 | 다형성 load()/get() | 다형성 질의들 | 다형성 조인들 | Outer 조인 페칭 |
---|---|---|---|---|---|---|---|---|
table per class-hierarchy | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.get(Payment.class, id) | from Payment p | from Order o join o.payment p | 지원됨 |
table per subclass | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.get(Payment.class, id) | from Payment p | from Order o join o.payment p | 지원됨 |
table per concrete-class (union-subclass) | <many-to-one> | <one-to-one> | <one-to-many> (for inverse="true" only) | <many-to-many> | s.get(Payment.class, id) | from Payment p | from Order o join o.payment p | 지원됨 |
table per concrete class (implicit polymorphism) | <any> | 지원되지 않음 | 지원되지 않음 | <many-to-any> | s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult() | from Payment p | 지원되지 않음 | 지원되지 않음 |