component의 개념은 Hibernate에서 다른 용도로 몇몇 다른 컨텍스트들 내에서 재사용된다.
하나의 컴포넌트는 엔티티 참조가 아닌, value 타입으로서 영속화 되는 하나의 포함된 객체이다. "컴포넌트" 용어는 (아키텍처 수준의 컴포넌트들이 아닌) composition(구성,합성)에 대한 객체-지향적인 개념을 언급한다. 예를 들어 당신은 다음과 같이 개인을 모형화 시킬 수도 있다:
public class Person { private java.util.Date birthday; private Name name; private String key; public String getKey() { return key; } private void setKey(String key) { this.key=key; } public java.util.Date getBirthday() { return birthday; } public void setBirthday(java.util.Date birthday) { this.birthday = birthday; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } ...... ...... }
public class Name { char initial; String first; String last; public String getFirst() { return first; } void setFirst(String first) { this.first = first; } public String getLast() { return last; } void setLast(String last) { this.last = last; } public char getInitial() { return initial; } void setInitial(char initial) { this.initial = initial; } }
이제 Name은 Person의 컴포넌트로서 영속화 될 수도 있다. Name이 그것의 영속 프로퍼티들에 대한 getter 메소드와 setter 메소드를 정의하지만, 어떤 인터페이스들이나 식별자 프로퍼티들을 선언하는 것을 필요로 하지 않음을 주목하라.
우리의 Hibernate 매핑은 다음과 같을 것이다:
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <!-- class attribute optional --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
person 테이블은 pid, birthday, initial, first, last 컬럼들을 가질 것이다.
모든 값(value) 타입들처럼, 컴포넌트들은 공유된 참조들을 지원하지 않는다. 달리 말해, 두 명의 개인들은 동일한 이름을 가질 수 있지만, 두 개의 person 객체들은 오직 값 만이 "동일한" 두 개의 독립적인 name 객체들을 포함할 것이다. 컴포넌트의 null 값 의미는 특별한 용도를 위한 것이다. 포함된 객체를 다시 로드시킬 때, Hibernate는 모든 컴포넌트 컬럼들이 null일 경우에 전체 컴포넌트가 null이라고 가정할 것이다. 이것은 대부분의 용도에 맞을 것이다.
컴포넌트의 프로퍼티들은 임의의 Hibernate 타입일 수 있다(콜렉션들, many-to-one 연관들, 다른 컴포넌트들, 기타). 내포된 컴포넌트들은 신종의 사용례로 간주되지 않을 것이다. Hibernate는 매우 잘 정제된 객체 모형을 지원하도록 고안되어있다.
<component> 요소는 컴포넌트 클래스의 프로퍼티를 포함되는 엔티티에 대한 역 참조로서 매핑시키는 <parent> 서브요소를 허용한다.
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name" unique="true"> <parent name="namedPerson"/> <!-- reference back to the Person --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
컴포넌트들을 가진 콜렉션들이 지원된다(예를 들면 Name 타입을 가진 배열). <element> 태그를 <composite-element> 태그로 대체시켜서 당신의 컴포넌트 콜렉션을 선언하라.
<set name="someNames" table="some_names" lazy="true"> <key column="id"/> <composite-element class="eg.Name"> <!-- class attribute required --> <property name="initial"/> <property name="first"/> <property name="last"/> </composite-element> </set>
노트: 만일 당신이 composite 요소를 가진 하나의 Set를 정의할 경우, equals()와 hashCode()를 정확하게 구현하는 것이 매우 중요하다.
Composite 요소들은 컴포넌트들을 포함하지만 콜렉션들을 포함하지 않는다. 만일 당신의 composite 요소 자체가 컴포넌트들을 포함할 경우, <nested-composite-element> 태그를 사용하라. 이것은 꽤 신종의 경우-그것들 자체가 컴포넌트들을 갖고 있는 컴포넌트들의 콜렉션-이다. 이 단계에서 당신은 one-to-many 연관이 더 적절한지를 당신 스스로에게 질문하게 될 것이다. 하나의 엔티티로서 composite 요소를 다시 모델링하려고 시도하라 - 그러나 자바 모형들이 동일할지라도, 관계형 모형과 영속화 의미들은 여전히 약간 다르다.
당신이 하나의 <set>을 사용 중이라면, 하나의 composite 요소 매핑은 null 가능한 프로퍼티들을 지원하지 않음을 노트하길 바란다. Hibernate는 객체들을 삭제할 때 하나의 레코드를 식별하는데 각각의 컬럼들 값을 사용해야 하며 (composite 요소 테이블 내에 별도의 프라이머리 키 컬럼이 존재하지 않는다), 그것은 null 값들에 대해서는 불가능하다. 당신은 하나의 composite-요소 내에 not-null 프로퍼티들 만을 사용해야 하거나 하나의 <list>, <map>, <bag> 또는 <idbag>을 선택해야 한다.
composite 요소에 대한 하나의 특별한 경우는 내포된 <many-to-one> 요소를 가진 composite 요소이다. 이같은 매핑은 many-to-many 연관 테이블의 특별한 컬럼들을 composite 요소 클래스로 매핑시키는 것을 당신에게 허용해준다. 다음은 Order로부터 Item으로의 many-to-many 연관이다. 여기서 purchaseDate, price, 그리고 quantity는 연관의 프로퍼티들이다:
<class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.Purchase"> <property name="purchaseDate"/> <property name="price"/> <property name="quantity"/> <many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional --> </composite-element> </set> </class>
물론, 양방향 연관 네비게이션의 경우, 다른 측 상에 있는 purchase에 대한 참조가 존재할 수 없다. 컴포넌트들이 값(value) 타입들이고 공유된 참조들을 허용하지 않음을 기억하라. 하나의 Purchase는Order를 가진 set 내에 있을 수 있지만, 그것은 동시에 Item에 의해 참조될 수 없다.
심지어 세겹의(또는 네 겹의, 기타) 연관들이 가능하다:
<class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.OrderLine"> <many-to-one name="purchaseDetails class="eg.Purchase"/> <many-to-one name="item" class="eg.Item"/> </composite-element> </set> </class>
composite 요소들은 다른 엔티티들에 대한 연관들과 동일한 구문을 사용하여 질의들 내에 나타날 수도 있다.
<composite-map-key> 요소는 당신에게 하나의 컴포넌트 클래스를 하나의 Map의 키로서 매핑시키도록 한다. 당신은 컴포넌트 클래스 상에서 hashCode()와 equals()를 정확하게 오버라이드 시키도록 하라.
당신은 하나의 컴포넌트를 하나의 엔티티 클래스에 대한 하나의 식별자로서 사용할 수도 있다. 당신의 컴포넌트 클래스는 어떤 사양들을 충족시켜야 한다:
그것은 java.io.Serializable을 구현해야 한다.
그것은 composite 키 등가(equality)에 대한 데이터베이스 개념과 일치되게, equals()와 hashCode()를 다시 구현해야 한다.
노트: Hibernate3에서, 두 번째 사양은 Hibernate의 절대적으로 엄격한 사양이 아니다. 그러나 아무튼 그것을 행하라.
당신은 compsite 키들을 생성시키는데 IdentifierGenerator를 사용할 수 없다. 대신에 어플리케이션은 그것 자신의 식별자들을 할당해야 한다.
통상의 <id> 선언 위치에 (내포된 <key-property> 요소들을 가진) <composite-id> 태그를 사용하라. 예를 들어, OrderLine 클래스는 Order의 (composite) 프라이머리 키에 의존하는 프라이머리 키를 갖는다.
<class name="OrderLine"> <composite-id name="id" class="OrderLineId"> <key-property name="lineId"/> <key-property name="orderId"/> <key-property name="customerId"/> </composite-id> <property name="name"/> <many-to-one name="order" class="Order" insert="false" update="false"> <column name="orderId"/> <column name="customerId"/> </many-to-one> .... </class>
이제 OrderLine 테이블을 참조하는 임의의 foreign 키들이 또한 compsite이다. 당신은 다른 클래스들에 대한 당신의 매핑들 속에 이것을 선언해야 한다. OrderLine에 대한 하나의 연관은 다음과 같이 매핑될 것이다:
<many-to-one name="orderLine" class="OrderLine"> <!-- the "class" attribute is optional, as usual --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </many-to-one>
(<column> 태그가 모든 곳에서 column 속성에 대한 대안임을 노트하라.)
OrderLine에 대한 many-to-many 연관은 또한 composite foreign 키를 사용한다:
<set name="undeliveredOrderLines"> <key column name="warehouseId"/> <many-to-many class="OrderLine"> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </many-to-many> </set>
Order에서 OrderLine들의 콜렉션이 사용될 것이다:
<set name="orderLines" inverse="true"> <key> <column name="orderId"/> <column name="customerId"/> </key> <one-to-many class="OrderLine"/> </set>
(통상적으로 <one-to-many> 요소는 컬럼들을 선언하지 않는다.)
만일 OrderLine 자체가 하나의 콜렉션을 소유할 경우, 그것은 또한 하나의 composite foreign 키를 갖는다.
<class name="OrderLine"> .... .... <list name="deliveryAttempts"> <key> <!-- a collection inherits the composite key type --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </key> <list-index column="attemptId" base="1"/> <composite-element class="DeliveryAttempt"> ... </composite-element> </set> </class>
당신은 Map 타입의 프로퍼티를 매핑시킬 수도 있다:
<dynamic-component name="userAttributes"> <property name="foo" column="FOO" type="string"/> <property name="bar" column="BAR" type="integer"/> <many-to-one name="baz" class="Baz" column="BAZ_ID"/> </dynamic-component>
<dynamic-component> 매핑의 의미는 <component>와 동일하다. 이런 종류의 매핑의 장점은 배치 시에 단지 매핑 문서를 편집함으로써 그 bean의 실제 프로퍼티들을 결정하는 가용성이다. 매핑 문서에 대한 런타임 처리는 또한 DOM 파서를 사용하여 가능하다. 더 좋게는 당신이 Configuration 객체를 통해 Hibernate의 구성-시 메타모형에 접근할 수 있다(그리고 변경시킬 수 있다)