revised inheritance and components, minor fixes to example

git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@5573 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Gavin King 2005-02-06 00:27:53 +00:00
parent 0b8ee71d7f
commit 35cf78f98a
3 changed files with 278 additions and 146 deletions

View File

@ -236,7 +236,7 @@
<title>Components as Map indices</title> <title>Components as Map indices</title>
<para> <para>
The <literal>&lt;composite-index&gt;</literal> element lets you map a 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 component class as the key of a <literal>Map</literal>. Make sure you override
<literal>hashCode()</literal> and <literal>equals()</literal> correctly on <literal>hashCode()</literal> and <literal>equals()</literal> correctly on
the component class. the component class.
@ -266,92 +266,110 @@
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para>
<emphasis>Note: in Hibernate3, the second requirement is not an absolutely hard
requirement of Hibernate. But do it anyway.</emphasis>
</para>
<para> <para>
You can't use an <literal>IdentifierGenerator</literal> to generate composite keys. You can't use an <literal>IdentifierGenerator</literal> to generate composite keys.
Instead the application must assign its own identifiers. Instead the application must assign its own identifiers.
</para> </para>
<para>
Since a composite identifier must be assigned to the object before saving it,
we can't use <literal>unsaved-value</literal> of the identifier to distinguish
between newly instantiated transient instances and detached instances from a
previous session.
</para>
<para>
TODO: document new auto-detect features for assigned IDs in H3
</para>
<para>
So, if you wish to use transitive reattachment (you don't have to), you must
either implement <literal>Interceptor.isUnsaved()</literal> or define the
<literal>unsaved-value</literal> of a <literal>&lt;version&gt;</literal> or
<literal>&lt;timestamp&gt;</literal> element.
</para>
<para> <para>
Use the <literal>&lt;composite-id&gt;</literal> tag (with nested Use the <literal>&lt;composite-id&gt;</literal> tag (with nested
<literal>&lt;key-property&gt;</literal> elements) in place of the <literal>&lt;key-property&gt;</literal> elements) in place of the usual
usual <literal>&lt;id&gt;</literal> declaration: <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> </para>
<programlisting><![CDATA[<class name="eg.Foo" table"FOOS"> <programlisting><![CDATA[<class name="OrderLine">
<composite-id name="compId" class="eg.FooCompositeID">
<key-property name="string"/> <composite-id name="id" class="OrderLineId">
<key-property name="short"/> <key-property name="lineId"/>
<key-property name="date" column="date_" type="date"/> <key-property name="orderId"/>
<key-property name="customerId"/>
</composite-id> </composite-id>
<property name="name"/> <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> </class>]]></programlisting>
<para> <para>
Now, any foreign keys into the table <literal>FOOS</literal> are also composite. Now, any foreign keys referencing the <literal>OrderLine</literal> table are also
You must declare this in your mappings for other classes. An association to composite. You must declare this in your mappings for other classes. An association
<literal>Foo</literal> would be declared like this: to <literal>OrderLine</literal> would be mapped like this:
</para> </para>
<programlisting><![CDATA[<many-to-one name="foo" class="eg.Foo"> <programlisting><![CDATA[<many-to-one name="orderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual --> <!-- the "class" attribute is optional, as usual -->
<column name="foo_string"/> <column name="lineId"/>
<column name="foo_short"/> <column name="orderId"/>
<column name="foo_date"/> <column name="customerId"/>
</many-to-one>]]></programlisting> </many-to-one>]]></programlisting>
<para> <para>
This new <literal>&lt;column&gt;</literal> tag is also used by multi-column custom types. (Note that the <literal>&lt;column&gt;</literal> tag is an alternative to the
Actually it is an alternative to the <literal>column</literal> attribute everywhere. A <literal>column</literal> attribute everywhere.)
collection with elements of type <literal>Foo</literal> would use: </para>
</para>
<programlisting><![CDATA[<set name="foos"> <para>
<key column="owner_id"/> A <literal>many-to-many</literal> association to <literal>OrderLine</literal> also
<many-to-many class="eg.Foo"> uses the composite foreign key:
<column name="foo_string"/> </para>
<column name="foo_short"/>
<column name="foo_date"/> <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> </many-to-many>
</set>]]></programlisting> </set>]]></programlisting>
<para> <para>
On the other hand, <literal>&lt;one-to-many&gt;</literal>, as usual, declares no columns. 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>
<para> <para>
If <literal>Foo</literal> itself contains collections, they will also need a If <literal>OrderLine</literal> itself owns a collection, it also has a composite
composite foreign key. foreign key.
</para> </para>
<programlisting><![CDATA[<class name="eg.Foo"> <programlisting><![CDATA[<class name="OrderLine">
.... ....
.... ....
<set name="dates" lazy="true"> <list name="deliveryAttempts">
<key> <!-- a collection inherits the composite key type --> <key> <!-- a collection inherits the composite key type -->
<column name="foo_string"/> <column name="lineId"/>
<column name="foo_short"/> <column name="orderId"/>
<column name="foo_date"/> <column name="customerId"/>
</key> </key>
<element column="foo_date" type="date"/> <list-index column="attemptId" base="1"/>
<composite-element class="DeliveryAttempt">
...
</composite-element>
</set> </set>
</class>]]></programlisting> </class>]]></programlisting>
@ -367,7 +385,7 @@
<programlisting><![CDATA[<dynamic-component name="userAttributes"> <programlisting><![CDATA[<dynamic-component name="userAttributes">
<property name="foo" column="FOO"/> <property name="foo" column="FOO"/>
<property name="bar" column="BAR"/> <property name="bar" column="BAR"/>
<many-to-one name="baz" class="eg.Baz" column="BAZ"/> <many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component>]]></programlisting> </dynamic-component>]]></programlisting>
<para> <para>

View File

@ -154,12 +154,8 @@ create sequence employer_id_seq]]></programlisting>
<property name="title"/> <property name="title"/>
<set name="authors" table="author_work"> <set name="authors" table="author_work">
<key> <key column name="work_id"/>
<column name="work_id" not-null="true"/> <many-to-many class="Author" column name="author_id"/>
</key>
<many-to-many class="Author">
<column name="author_id" not-null="true"/>
</many-to-many>
</set> </set>
<subclass name="Book" discriminator-value="B"> <subclass name="Book" discriminator-value="B">
@ -292,7 +288,7 @@ alter table author_work
<many-to-one name="customer" column="customer_id"/> <many-to-one name="customer" column="customer_id"/>
<list name="lineItems" table="line_items"> <list name="lineItems" table="line_items">
<key column="order_id"/> <key column="order_id"/>
<index column="line_number"/> <list-index column="line_number"/>
<composite-element class="LineItem"> <composite-element class="LineItem">
<property name="quantity"/> <property name="quantity"/>
<many-to-one name="product" column="product_id"/> <many-to-one name="product" column="product_id"/>

View File

@ -5,12 +5,7 @@
<title>The Three Strategies</title> <title>The Three Strategies</title>
<para> <para>
TODO: While this is all still supported, many new features would require Hibernate supports the three basic inheritance mapping strategies:
a rewrite of this whole chapter
</para>
<para>
Hibernate supports the three basic inheritance mapping strategies.
</para> </para>
<itemizedlist> <itemizedlist>
@ -26,25 +21,45 @@
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
table per concrete class (some limitations) table per concrete class
</para> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para> <para>
It is even possible to use different mapping strategies for different In addition, Hibernate supports a fourth, slightly different kind of
branches of the same inheritance hierarchy, but the same limitations polymorphism:
apply as apply to table-per-concrete class mappings. Hibernate does
not support mixing <literal>&lt;subclass&gt;</literal> mappings and
<literal>&lt;joined-subclass&gt;</literal> mappings inside the same
<literal>&lt;class&gt;</literal> element. However, it is possible to
use a <literal>&lt;join&gt;</literal> element to map this.
</para> </para>
<itemizedlist>
<listitem>
<para>
implicit polymorphism
</para>
</listitem>
</itemizedlist>
<para>
It is possible to use different mapping strategies for different
branches of the same inheritance hierarchy, and then make use of implicit
polymorphism to achieve polymorphism across the whole hierarchy. However,
Hibernate does not support mixing <literal>&lt;subclass&gt;</literal>,
and <literal>&lt;joined-subclass&gt;</literal> and
<literal>&lt;union-subclass&gt;</literal> mappings under the same root
<literal>&lt;class&gt;</literal> element. It is possible to mix together
the table per hierarchy and table per subclass strategies, under the
the same <literal>&lt;class&gt;</literal> element, by combining the
<literal>&lt;subclass&gt;</literal> and <literal>&lt;join&gt;</literal>
elements (see below).
</para>
<sect2>
<title>Table per class hierarchy</title>
<para> <para>
Suppose we have an interface <literal>Payment</literal>, with implementors Suppose we have an interface <literal>Payment</literal>, with implementors
<literal>CreditCardPayment</literal>, <literal>CashPayment</literal>, <literal>CreditCardPayment</literal>, <literal>CashPayment</literal>,
<literal>ChequePayment</literal>. The table-per-hierarchy mapping would <literal>ChequePayment</literal>. The table per hierarchy mapping would
look like: look like:
</para> </para>
@ -56,6 +71,7 @@
<property name="amount" column="AMOUNT"/> <property name="amount" column="AMOUNT"/>
... ...
<subclass name="CreditCardPayment" discriminator-value="CREDIT"> <subclass name="CreditCardPayment" discriminator-value="CREDIT">
<property name="creditCardType" column="CCTYPE"/>
... ...
</subclass> </subclass>
<subclass name="CashPayment" discriminator-value="CASH"> <subclass name="CashPayment" discriminator-value="CASH">
@ -67,13 +83,18 @@
</class>]]></programlisting> </class>]]></programlisting>
<para> <para>
Exactly one table is required. There is one big limitation of this Exactly one table is required. There is one big limitation of this mapping
mapping strategy: columns declared by the subclasses may not have strategy: columns declared by the subclasses, such as <literal>CCTYPE</literal>,
<literal>NOT NULL</literal> constraints. may not have <literal>NOT NULL</literal> constraints.
</para> </para>
</sect2>
<sect2>
<title>Table per class subclass</title>
<para> <para>
A table-per-subclass mapping would look like: A table per subclass mapping would look like:
</para> </para>
<programlisting><![CDATA[<class name="Payment" table="PAYMENT"> <programlisting><![CDATA[<class name="Payment" table="PAYMENT">
@ -84,6 +105,7 @@
... ...
<joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
<key column="PAYMENT_ID"/> <key column="PAYMENT_ID"/>
<property name="creditCardType" column="CCTYPE"/>
... ...
</joined-subclass> </joined-subclass>
<joined-subclass name="CashPayment" table="CASH_PAYMENT"> <joined-subclass name="CashPayment" table="CASH_PAYMENT">
@ -102,34 +124,140 @@
is actually a one-to-one association). is actually a one-to-one association).
</para> </para>
</sect2>
<sect2>
<title>Table per subclass, using a discriminator</title>
<para> <para>
Note that Hibernate's implementation of table-per-subclass requires Note that Hibernate's implementation of table per subclass requires
no discriminator column. Other object/relational mappers use a no discriminator column. Other object/relational mappers use a
different implementation of table-per-subclass which requires a type different implementation of table per subclass which requires a type
discriminator column in the superclass table. The approach taken by discriminator column in the superclass table. The approach taken by
Hibernate is much more difficult to implement but arguably more Hibernate is much more difficult to implement but arguably more
correct from a relational point of view. correct from a relational point of view. If you would like to use
a discriminator column with the table per subclass strategy, you
may combine the use of <literal>&lt;subclass&gt;</literal> and
<literal>&lt;join&gt;</literal>, as follow:
</para> </para>
<para> <programlisting><![CDATA[<class name="Payment" table="PAYMENT">
TODO: document usage of join for discriminators in table-per-subclass <id name="id" type="long" column="PAYMENT_ID">
</para> <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">
<join table="CASH_PAYMENT">
...
</join>
</subclass>
<subclass name="ChequePayment" discriminator-value="CHEQUE">
<join table="CHEQUE_PAYMENT" fetch="select">
...
</join>
</subclass>
</class>]]></programlisting>
<para> <para>
TODO: document usage of join for mixing inheritance mapping strategies The optional <literal>fetch="select"</literal> declaration tells Hibernate
not to fetch the <literal>ChequePayment</literal> subclass data using an
outer join when querying the superclass.
</para> </para>
</sect2>
<sect2>
<title>Mixing table per class hierarchy with table per subclass</title>
<para> <para>
For either of these two mapping strategies, a polymorphic You may even mix the table per hierarchy and table per subclass strategies
association to <literal>Payment</literal> is mapped using using this approach:
</para>
<programlisting><![CDATA[<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>]]></programlisting>
<para>
For any of these mapping strategies, a polymorphic association to the root
<literal>Payment</literal> class is mapped using
<literal>&lt;many-to-one&gt;</literal>. <literal>&lt;many-to-one&gt;</literal>.
</para> </para>
<programlisting><![CDATA[<many-to-one name="payment" <programlisting><![CDATA[<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>]]></programlisting>
column="PAYMENT"
class="Payment"/>]]></programlisting>
<para>The table-per-concrete-class strategy is very different.</para> </sect2>
<sect2>
<title>Table per concrete class</title>
<para>
There are two ways we could go about mapping the table per concrete class
strategy. The first is to use <literal>&lt;union-subclass&gt;</literal>.
</para>
<programlisting><![CDATA[<class name="Payment">
<id name="id" type="long" column="PAYMENT_ID">
<generator class="native"/>
</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>]]></programlisting>
<para>
Three tables are involved. Each table defines columns for all properties
of the class, including inherited properties.
</para>
<para>
The limitation of this approach is that if a property is mapped on the
superclass, the column name must be the same on all subclass tables.
(We might relax this in a future release of Hibernate.)
</para>
</sect2>
<sect2>
<title>Table per concrete class, using implicit polymorphism</title>
<para>
An alternative approach is to make use of implicit polymorphism:
</para>
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
<id name="id" type="long" column="CREDIT_PAYMENT_ID"> <id name="id" type="long" column="CREDIT_PAYMENT_ID">
@ -156,48 +284,40 @@
</class>]]></programlisting> </class>]]></programlisting>
<para> <para>
Three tables were required. Notice that nowhere do we Notice that nowhere do we mention the <literal>Payment</literal> interface
mention the <literal>Payment</literal> interface explicitly. explicitly. Also notice that properties of <literal>Payment</literal> are
Instead, we make use of Hibernate's <emphasis>implicit mapped in each of the subclasses.
polymorphism</emphasis>. Also notice that properties of
<literal>Payment</literal> are mapped in each of the
subclasses.
</para> </para>
<para> <para>
In this case, a polymorphic association to <literal>Payment</literal> The disadvantage of this approach is that Hibernate does not generate SQL
is mapped using <literal>&lt;any&gt;</literal>. <literal>UNION</literal>s when performing polymorphic queries.
</para> </para>
<programlisting><![CDATA[<any name="payment" <para>
meta-type="class" For this mapping strategy, a polymorphic association to <literal>Payment</literal>
id-type="long"> is usually mapped using <literal>&lt;any&gt;</literal>.
</para>
<programlisting><![CDATA[<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_CLASS"/>
<column name="PAYMENT_ID"/> <column name="PAYMENT_ID"/>
</any>]]></programlisting> </any>]]></programlisting>
<para> </sect2>
It would be better if we defined a <literal>UserType</literal>
as the <literal>meta-type</literal>, to handle the mapping from
type discriminator strings to <literal>Payment</literal> subclass.
</para>
<programlisting><![CDATA[<any name="payment" <sect2>
meta-type="PaymentMetaType" <title>Mixing implicit polymorphism with other inheritance mappings</title>
id-type="long">
<column name="PAYMENT_TYPE"/> <!-- CREDIT, CASH or CHEQUE -->
<column name="PAYMENT_ID"/>
</any>]]></programlisting>
<para> <para>
There is one further thing to notice about this mapping. There is one further thing to notice about this mapping. Since the subclasses
Since the subclasses are each mapped in their own are each mapped in their own <literal>&lt;class&gt;</literal> element (and since
<literal>&lt;class&gt;</literal> element (and since <literal>Payment</literal> is just an interface), each of the subclasses could
<literal>Payment</literal> is just an interface), each of easily be part of another inheritance hierarchy! (And you can still use polymorphic
the subclasses could easily be part of another table-per-class queries against the <literal>Payment</literal> interface.)
or table-per-subclass inheritance hierarchy! (And you can
still use polymorphic queries against the
<literal>Payment</literal> interface.)
</para> </para>
<programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <programlisting><![CDATA[<class name="CreditCardPayment" table="CREDIT_PAYMENT">
@ -238,9 +358,7 @@
not instances of <literal>NonelectronicTransaction</literal>. not instances of <literal>NonelectronicTransaction</literal>.
</para> </para>
<para> </sect2>
TODO: Document union-subclass for polymorphic-table-per-concrete-class mappings
</para>
</sect1> </sect1>
@ -249,13 +367,13 @@
<para> <para>
There are certain limitations to the "implicit polymorphism" approach to There are certain limitations to the "implicit polymorphism" approach to
the table-per-concrete-class mapping strategy. There are somewhat less the table per concrete-class mapping strategy. There are somewhat less
restrictive limitations to <literal>&lt;union-subclass&gt;</literal> restrictive limitations to <literal>&lt;union-subclass&gt;</literal>
mappings. (TODO) mappings.
</para> </para>
<para> <para>
The following table shows the limitations of table-per-concrete-class The following table shows the limitations of table per concrete-class
mappings, and of implicit polymorphism, in Hibernate. mappings, and of implicit polymorphism, in Hibernate.
</para> </para>
@ -285,7 +403,7 @@
</thead> </thead>
<tbody> <tbody>
<row> <row>
<entry>table-per-class-hierarchy</entry> <entry>table per class-hierarchy</entry>
<entry><literal>&lt;many-to-one&gt;</literal></entry> <entry><literal>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry> <entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal></entry> <entry><literal>&lt;one-to-many&gt;</literal></entry>
@ -296,7 +414,7 @@
<entry><emphasis>supported</emphasis></entry> <entry><emphasis>supported</emphasis></entry>
</row> </row>
<row> <row>
<entry>table-per-subclass</entry> <entry>table per subclass</entry>
<entry><literal>&lt;many-to-one&gt;</literal></entry> <entry><literal>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry> <entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal></entry> <entry><literal>&lt;one-to-many&gt;</literal></entry>
@ -307,18 +425,7 @@
<entry><emphasis>supported</emphasis></entry> <entry><emphasis>supported</emphasis></entry>
</row> </row>
<row> <row>
<entry>table-per-concrete-class (implicit polymorphism)</entry> <entry>table per concrete-class (union-subclass)</entry>
<entry><literal>&lt;any&gt;</literal></entry>
<entry><emphasis>not supported</emphasis></entry>
<entry><emphasis>not supported</emphasis></entry>
<entry><literal>&lt;many-to-any&gt;</literal></entry>
<entry><emphasis>use a query</emphasis></entry>
<entry><literal>from Payment p</literal></entry>
<entry><emphasis>not supported</emphasis></entry>
<entry><emphasis>not supported</emphasis></entry>
</row>
<row>
<entry>table-per-concrete-class (union-subclass)</entry>
<entry><literal>&lt;many-to-one&gt;</literal></entry> <entry><literal>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry> <entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal> (for <literal>inverse="true"</literal> only)</entry> <entry><literal>&lt;one-to-many&gt;</literal> (for <literal>inverse="true"</literal> only)</entry>
@ -328,6 +435,17 @@
<entry><literal>from Order o join o.payment p</literal></entry> <entry><literal>from Order o join o.payment p</literal></entry>
<entry><emphasis>supported</emphasis></entry> <entry><emphasis>supported</emphasis></entry>
</row> </row>
<row>
<entry>table per concrete class (implicit polymorphism)</entry>
<entry><literal>&lt;any&gt;</literal></entry>
<entry><emphasis>not supported</emphasis></entry>
<entry><emphasis>not supported</emphasis></entry>
<entry><literal>&lt;many-to-any&gt;</literal></entry>
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
<entry><literal>from Payment p</literal></entry>
<entry><emphasis>not supported</emphasis></entry>
<entry><emphasis>not supported</emphasis></entry>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>