hibernate-orm/reference/en/modules/component_mapping.xml

403 lines
15 KiB
XML
Raw Normal View History

<chapter id="components">
<title>Component Mapping</title>
<para>
The notion of a <emphasis>component</emphasis> is re-used in several different contexts,
for different purposes, throughout Hibernate.
</para>
<sect1 id="components-dependentobjects">
<title>Dependent objects</title>
<para>
A component is a contained object that is persisted as a value type, not an entity
reference. The term "component" refers to the object-oriented notion of composition
(not to architecture-level components). For example, you might model a person like this:
</para>
<programlisting><![CDATA[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;
}
......
......
}]]></programlisting>
<programlisting><![CDATA[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;
}
}]]></programlisting>
<para>
Now <literal>Name</literal> may be persisted as a component of
<literal>Person</literal>. Notice that <literal>Name</literal> defines getter
and setter methods for its persistent properties, but doesn't need to declare
any interfaces or identifier properties.
</para>
<para>
Our Hibernate mapping would look like:
</para>
<programlisting><![CDATA[<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid.hex"/>
</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>]]></programlisting>
<para>
The person table would have the columns <literal>pid</literal>,
<literal>birthday</literal>,
<literal>initial</literal>,
<literal>first</literal> and
<literal>last</literal>.
</para>
<para>
Like all value types, components do not support shared references. In other words, two
persons could have the same name, but the two person objects would contain two independent
name ojects, only "the same" by value. The null value semantics of a component are
<emphasis>ad hoc</emphasis>. When reloading the containing object, Hibernate will assume
that if all component columns are null, then the entire component is null. This should
be okay for most purposes.
</para>
<para>
The properties of a component may be of any Hibernate type (collections, many-to-one
associations, other components, etc). Nested components should <emphasis>not</emphasis>
be considered an exotic usage. Hibernate is intended to support a very fine-grained
object model.
</para>
<para>
The <literal>&lt;component&gt;</literal> element allows a <literal>&lt;parent&gt;</literal>
subelement that maps a property of the component class as a reference back to the
containing entity.
</para>
<programlisting><![CDATA[<class name="eg.Person" table="person">
<id name="Key" column="pid" type="string">
<generator class="uuid.hex"/>
</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>]]></programlisting>
</sect1>
<sect1 id="components-incollections" revision="1">
<title>Collections of dependent objects</title>
<para>
Collections of components are supported (eg. an array of type
<literal>Name</literal>). Declare your component collection by
replacing the <literal>&lt;element&gt;</literal> tag with a
<literal>&lt;composite-element&gt;</literal> tag.
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
Note: if you define a <literal>Set</literal> of composite elements, it is
very important to implement <literal>equals()</literal> and
<literal>hashCode()</literal> correctly.
</para>
<para>
Composite elements may contain components but not collections. If your
composite element itself contains
components, use the <literal>&lt;nested-composite-element&gt;</literal>
tag. This is a pretty exotic case - a collection of components which
themselves have components. By this stage you should be asking yourself
if a one-to-many association is more appropriate. Try remodelling the
composite element as an entity - but note that even though the Java model
is the same, the relational model and persistence semantics are still
slightly different.
</para>
<para>
Please note that a composite element mapping doesn't support null-able properties
if you're using a <literal>&lt;set&gt;</literal>. Hibernate
has to use each columns value to identify a record when deleting objects
(there is no separate primary key column in the composite element table),
which is not possible with null values. You have to either use only
not-null properties in a composite-element or choose a
<literal>&lt;list&gt;</literal>, <literal>&lt;map&gt;</literal>,
<literal>&lt;bag&gt;</literal> or <literal>&lt;idbag&gt;</literal>.
</para>
<para>
A special case of a composite element is a composite element with a nested
<literal>&lt;many-to-one&gt;</literal> element. A mapping like this allows
you to map extra columns of a many-to-many association table to the
composite element class. The following is a many-to-many association
from <literal>Order</literal> to <literal>Item</literal> where
<literal>purchaseDate</literal>, <literal>price</literal> and
<literal>quantity</literal> are properties of the association:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
Of course, there can't be a reference to the purchae on the other side, for
bidirectional association navigation. Remember that components are value types and
don't allow shared references. A single <literal>Purchase</literal> can be in the
set of an <literal>Order</literal>, but it can't be referenced by the <literal>Item</literal>
at the same time.
</para>
<para>Even ternary (or quaternary, etc) associations are possible:</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
Composite elements may appear in queries using the same syntax as
associations to other entities.
</para>
</sect1>
<sect1 id="components-asmapindex">
<title>Components as Map indices</title>
<para>
The <literal>&lt;composite-map-key&gt;</literal> element lets you map a
component class as the key of a <literal>Map</literal>. Make sure you override
<literal>hashCode()</literal> and <literal>equals()</literal> correctly on
the component class.
</para>
</sect1>
<sect1 id="components-compositeid" revision="1">
<title>Components as composite identifiers</title>
<para>
You may use a component as an identifier of an entity class. Your component
class must satisfy certain requirements:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
It must implement <literal>java.io.Serializable</literal>.
</para>
</listitem>
<listitem>
<para>
It must re-implement <literal>equals()</literal> and
<literal>hashCode()</literal>, consistently with the database's
notion of composite key equality.
</para>
</listitem>
</itemizedlist>
<para>
<emphasis>Note: in Hibernate3, the second requirement is not an absolutely hard
requirement of Hibernate. But do it anyway.</emphasis>
</para>
<para>
You can't use an <literal>IdentifierGenerator</literal> to generate composite keys.
Instead the application must assign its own identifiers.
</para>
<para>
Use the <literal>&lt;composite-id&gt;</literal> tag (with nested
<literal>&lt;key-property&gt;</literal> elements) in place of the usual
<literal>&lt;id&gt;</literal> declaration. For example, the
<literal>OrderLine</literal> class has a primary key that depends upon
the (composite) primary key of <literal>Order</literal>.
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
Now, any foreign keys referencing the <literal>OrderLine</literal> table are also
composite. You must declare this in your mappings for other classes. An association
to <literal>OrderLine</literal> would be mapped like this:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
(Note that the <literal>&lt;column&gt;</literal> tag is an alternative to the
<literal>column</literal> attribute everywhere.)
</para>
<para>
A <literal>many-to-many</literal> association to <literal>OrderLine</literal> also
uses the composite foreign key:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
The collection of <literal>OrderLine</literal>s in <literal>Order</literal> would
use:
</para>
<programlisting><![CDATA[<set name="orderLines" inverse="true">
<key>
<column name="orderId"/>
<column name="customerId"/>
</key>
<one-to-many class="OrderLine"/>
</set>]]></programlisting>
<para>
(The <literal>&lt;one-to-many&gt;</literal> element, as usual, declares no columns.)
</para>
<para>
If <literal>OrderLine</literal> itself owns a collection, it also has a composite
foreign key.
</para>
<programlisting><![CDATA[<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>]]></programlisting>
</sect1>
<sect1 id="components-dynamic" revision="1">
<title>Dynamic components</title>
<para>
You may even map a property of type <literal>Map</literal>:
</para>
<programlisting><![CDATA[<dynamic-component name="userAttributes">
<property name="foo" column="FOO"/>
<property name="bar" column="BAR"/>
<many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>]]></programlisting>
<para>
The semantics of a <literal>&lt;dynamic-component&gt;</literal> mapping are identical
to <literal>&lt;component&gt;</literal>. The advantage of this kind of mapping is
the ability to determine the actual properties of the bean at deployment time, just
by editing the mapping document. Runtime manipulation of the mapping document is
also possible, using a DOM parser. Even better, you can access (and change) Hibernate's
configuration-time metamodel via the <literal>Configuration</literal> object.
</para>
</sect1>
</chapter>