HHH-9998 - Continue documentation TLC - mapping compositions & collections

This commit is contained in:
Steve Ebersole 2015-08-01 13:49:42 -05:00
parent 9d22ed62ed
commit 7a67f39e38
16 changed files with 669 additions and 7 deletions

View File

@ -44,7 +44,11 @@
<xi:include href="Preface.xml" />
<xi:include href="chapters/services/Services.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
<xi:include href="chapters/services/Services.xml" />
<!--
<xi:include href="chapters/types/Custom_Types.xml" />
-->
</book>

View File

@ -189,4 +189,8 @@
</section>
-->
</section>
JPA portability
* HQL/JPQL differences
* naming strategies
</chapter>

View File

@ -46,12 +46,19 @@
<xi:include href="chapters/categoizations/Data_Categorizations.xml" />
<xi:include href="chapters/basic/Basic_Types.xml" />
<!--
<xi:include href="chapters/component/Component.xml" />
<xi:include href="chapters/composition/Composition.xml" />
<xi:include href="chapters/collection/Collection.xml" />
<!--
<xi:include href="chapters/entity/Entity.xml" />
<xi:include href="chapters/id/Identifiers.xml" />
<xi:include href="chapters/association/Associations.xml" />
<xi:include href="chapters/natural_id/Natural_Id.xml" />
<xi:include href="chapters/generation/Generated_attributes.xml" />
<xi:include href="chapters/access/Attribute_Access.xml" />
<xi:include href="chapters/overrides/Mapping_Overrides.xml" /> AttributeOverrides/AssociationOverrides
<xi:include href="chapters/naming/Naming_Strategies.xml" />
-->
<!-- appendices? -->

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<chapter xml:id="collections"
version="5.0"
xml:lang="en"
xmlns="http://docbook.org/ns/docbook"
xmlns:xi="http://www.w3.org/2001/XInclude"
>
<title>Collections</title>
<section xml:id="collections-synopsis">
<title>Collections as a value type</title>
<para>
discussions of what it means for them to be value types - lifecycle, opt-locking
</para>
<para>
Collections have the usual behavior of value types. They are automatically persisted when referenced by
a persistent object and are automatically deleted when unreferenced. If a collection is passed from one
persistent object to another, its elements might be moved from one table to another. Two entities cannot
share a reference to the same collection instance.
</para>
<para>
Collection attributes do not support null value semantics; Hibernate does not distinguish between a null
collection reference and an empty collection.
</para>
<para>
It is important that collections be defined using the appropriate Java Collections Framework interface
rather than a specific implementation. From a theoretical perspective, this just follows good design
principles. From a practical perspective, Hibernate (really all persistence providers) will use
their own collection implementations which conform to the Java Collections Framework interfaces.
</para>
</section>
<section xml:id="collections-value">
<title>Collections of value types</title>
<para>
collection of values - elements can be of any value type except for collections (in fact even compositions as the element cannot contain collections)
* basics
* compositions
</para>
</section>
<section xml:id="collections-entity">
<title>Collections of entities</title>
<para>
* one-to-many
* many-to-many
</para>
</section>
<section xml:id="collections-list">
<title>List - index</title>
<para>
<!-- todo : discuss mapping list index -->
todo : discuss mapping list index
</para>
</section>
<section xml:id="collections-map">
<title>Map - key</title>
<para>
<!-- todo : discuss mapping map key -->
todo : discuss mapping map key
</para>
</section>
<section xml:id="collections-bag">
<title>Bags</title>
<para>
<!-- todo : discuss mapping bags -->
todo : discuss mapping bags
</para>
</section>
<section xml:id="collections-array">
<title>Arrays</title>
<para>
<!-- todo : discuss mapping arrays -->
todo : discuss mapping arrays
</para>
</section>
<section xml:id="collections-as-basic">
<title>Collections as basic value type</title>
<para>
Notice how all the previous examples explicitly mark the collection attribute as either
ElementCollection, OneToMany or ManyToMany. Collections not marked as such, or collections explicitly
maked with @Basic are treated as JPA basic value. Meaning there value is stored into a single
column in the containing table.
</para>
<para>
This is sometimes beneficial. Consider a use-case such as a VARCHAR column that represents a
delimited list or set of Strings.
</para>
<example>
<title>Delimited set of tags</title>
<programlisting role="JAVA"><xi:include href="extras/DelimitedStringTagsExample.java" parse="text" /></programlisting>
</example>
<para>
See the <citetitle>Hibernate Integrations Guide</citetitle> for more details on developing
custom value type mappings.
</para>
</section>
</chapter>

View File

@ -0,0 +1,59 @@
@Entity
public static class Post {
@Id
public Integer id;
@Basic
@Type( type = "delimited_strings" )
Set<String> tags;
}
public static class DelimitedStringsType extends AbstractSingleColumnStandardBasicType<Set> {
public DelimitedStringsType() {
super(
VarcharTypeDescriptor.INSTANCE,
new DelimitedStringsJavaTypeDescriptor()
);
}
@Override
public String getName() {
return "delimited_strings";
}
}
public static class DelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor<Set> {
public DelimitedStringsJavaTypeDescriptor() {
super(
Set.class,
new MutableMutabilityPlan<Set>() {
@Override
protected Set deepCopyNotNull(Set value) {
Set<String> copy = new HashSet<String>();
copy.addAll( value );
return copy;
}
}
);
}
@Override
public String toString(Set value) {
return null;
}
@Override
public Set fromString(String string) {
return null;
}
@Override
public <X> X unwrap(Set value, Class<X> type, WrapperOptions options) {
return null;
}
@Override
public <X> Set wrap(X value, WrapperOptions options) {
return null;
}
}

View File

@ -0,0 +1,210 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<chapter xml:id="composition"
version="5.0"
xml:lang="en"
xmlns="http://docbook.org/ns/docbook"
xmlns:xi="http://www.w3.org/2001/XInclude"
>
<title>Compositions</title>
<sidebar>
<title>Related topics</title>
<itemizedlist>
<listitem>
<para><xref linkend="access"/></para>
</listitem>
<listitem>
<para><xref linkend="overrides"/></para>
</listitem>
<listitem>
<para><xref linkend="naming"/></para>
</listitem>
</itemizedlist>
</sidebar>
<para>
Historically Hibernate called these components. JPA calls them embeddables. Either way the concept is the
same: a composition of values. For example we might have a Name class that is a composition of
first-name and last-name, or an Address class that is a composition of street, city, postal code, etc.
</para>
<example xml:id="composition-ex-embeddable">
<title>Simple composition example</title>
<programlisting role="JAVA"><xi:include href="extras/Name.java" parse="text" /></programlisting>
<programlisting role="JAVA"><xi:include href="extras/Address.java" parse="text" /></programlisting>
</example>
<para>
A composition is another form of value type. The lifecycle of a composition is defined by the thing that
contains it.
</para>
<para>
A composition inherits the attribute access of its parent. For details on attribute access, see
<xref linkend="access"/>.
</para>
<para>
Compositions can be made up of basic values as well as associations, with the caveat that compositions which
are used as collection elements cannot themselves define collections.
</para>
<section>
<title>Component / Embedded</title>
<para>
This is the form of composition you will see most often. Here an entity or another composition
is the container.
</para>
<example xml:id="composition-ex-embedded1">
<title>Simple Embedded</title>
<programlisting role="JAVA"><xi:include href="extras/Person.java" parse="text" /></programlisting>
</example>
<note>
<para>
Notice that JPA defines 2 terms for composition: Embeddable and Embedded. Embeddable is used to
describe the composition class itself (Name). Embedded is used to describe a usage of that
composition (Person.name).
</para>
</note>
<para>
The composition here is the Name type related to Person.name.
</para>
<example xml:id="composition-ex-embedded1-sql">
<title>Person table</title>
<programlisting role="SQL"><xi:include href="extras/Person1.sql" parse="text" /></programlisting>
</example>
<para>
The composed values are mapped to the same table as the parent table. Composition is part of good
OO data modeling (idiomatic java). In fact that table could also be mapped by the following entity instead.
</para>
<example xml:id="composition-ex-no-composition">
<title>Alternative to composition</title>
<programlisting role="JAVA"><xi:include href="extras/Person_alt.java" parse="text" /></programlisting>
</example>
<para>
The composition form is certainly more OO. And that becomes more evident as we work with multiple
compositions.
</para>
</section>
<section xml:id="composition-multiple">
<title>Multiple compositions</title>
<example xml:id="composition-ex-multiple-compositions">
<title>Multiple compositions</title>
<programlisting role="JAVA"><xi:include href="extras/Contact.java" parse="text" /></programlisting>
</example>
<para>
It is certainly more convenient to work with the compositions. However, an interesting thing happens
in this particular example. By default, this mapping actually will not work as-is.
The problem is in how JPA defines implicit naming rules for columns that are part of a composition, which
say that all of the Address compositions would map to the same implicit column names.
</para>
<para>
This occurs any time we have multiple compositions based on the same embeddable in a given parent.
We have a few options to handle this issue.
</para>
<section xml:id="composition-multiple-jpa">
<title>JPA's AttributeOverride</title>
<para>
The JPA-defined way to handle this situation is through the use of its AttributeOverride annotation.
</para>
<example>
<title>JPA's AttributeOverride</title>
<programlisting role="JAVA"><xi:include href="extras/Contact-AttributeOverride.java" parse="text"/></programlisting>
</example>
<para>
Now, essentially there are no implicit column names in the Address compositions. We have explicitly
named them.
</para>
</section>
<section xml:id="composition-multiple-namingstrategy">
<title>ImplicitNamingStrategy</title>
<note>
<para>
This is a Hibernate specific feature. Users concerned with JPA provider portability should instead
prefer explicit column naming with AttributeOverride as per <xref linkend="composition-multiple-jpa"/>
</para>
</note>
<para>
Hibernate naming strategies are covered in detail in <xref linkend="naming"/>. However, for the purposes
of this discussion, Hibernate has the capability to interpret implicit column names in a way that is
safe for use with multiple compositions.
</para>
<example>
<title>Enabling composition-safe implicit naming</title>
<programlisting role="JAVA"><xi:include href="extras/component-safe-implicit-naming.java" parse="text"/></programlisting>
</example>
<para>
Now the "path" to attributes are used in the implicit column naming.
</para>
<example>
<title>Enabling composition-safe implicit naming</title>
<programlisting role="SQL"><xi:include href="extras/Contact-ImplicitNamingStrategy.sql" parse="text"/></programlisting>
</example>
<para>
You could even develop your own to do special implicit naming.
</para>
</section>
</section>
<section xml:id="composition-collections">
<title>Collections of compositions</title>
<para>
Collections of compositions are specifically value collections (as compositions are a value type). Value
collections are covered in detail in <xref linkend="collections-value"/>.
</para>
<para>
The one thing to add to the discussion of value collections in regards to compositions is that
the composition cannot, in turn, define collections.
</para>
</section>
<section xml:id="composition-mapkey">
<title>Compositions as Map key</title>
<para>
Compositions can also be used as the key values for Maps. Mapping Maps and their keys is convered in
detail in <xref linkend="collections-map-key"/>.
</para>
<para>
Again, compositions used as a Map key cannot, in turn, define collections.
</para>
</section>
<section xml:id="composition-identifier">
<title>Compositions as identifiers</title>
<para>
Compositions can also be used as entity identifiers. This usage is covered in detail in
<xref linkend="identifier-composite"/>
</para>
<para>
Again, compositions used as an entity identifier cannot, in turn, define collections.
</para>
</section>
</chapter>

View File

@ -0,0 +1,15 @@
@Embeddable
public class Address {
private String line1;
private String line2;
@Embedded
private ZipCode zipCode;
...
@Embeddable
public static class Zip {
private String postalCode;
private String plus4;
...
}
}

View File

@ -0,0 +1,68 @@
@Entity
public class Contact {
@Id
private Integer id;
@Embedded
private Name name;
@Embedded
@AttributeOverrides(
@AttributeOverride(
name="line1",
column = @Column(name = "home_address_line1"),
),
@AttributeOverride(
name="line2",
column = @Column(name = "home_address_line2")
),
@AttributeOverride(
name="zipCode.postalCode",
column = @Column(name = "home_address_postal_cd")
),
@AttributeOverride(
name="zipCode.plus4",
column = @Column(name = "home_address_postal_plus4")
)
)
private Address homeAddress;
@Embedded
@AttributeOverrides(
@AttributeOverride(
name="line1",
column = @Column(name = "mailing_address_line1"),
),
@AttributeOverride(
name="line2",
column = @Column(name = "mailing_address_line2")
),
@AttributeOverride(
name="zipCode.postalCode",
column = @Column(name = "mailing_address_postal_cd")
),
@AttributeOverride(
name="zipCode.plus4",
column = @Column(name = "mailing_address_postal_plus4")
)
)
private Address mailingAddress;
@Embedded
@AttributeOverrides(
@AttributeOverride(
name="line1",
column = @Column(name = "work_address_line1"),
),
@AttributeOverride(
name="line2",
column = @Column(name = "work_address_line2")
),
@AttributeOverride(
name="zipCode.postalCode",
column = @Column(name = "work_address_postal_cd")
),
@AttributeOverride(
name="zipCode.plus4",
column = @Column(name = "work_address_postal_plus4")
)
)
private Address workAddress;
...
}

View File

@ -0,0 +1,19 @@
create table Contact(
id integer not null,
name_firstName VARCHAR,
name_middleName VARCHAR,
name_lastName VARCHAR,
homeAddress_line1 VARCHAR,
homeAddress_line2 VARCHAR,
homeAddress_zipCode_postalCode VARCHAR,
homeAddress_zipCode_plus4 VARCHAR,
mailingAddress_line1 VARCHAR,
mailingAddress_line2 VARCHAR,
mailingAddress_zipCode_postalCode VARCHAR,
mailingAddress_zipCode_plus4 VARCHAR,
workAddress_line1 VARCHAR,
workAddress_line2 VARCHAR,
workAddress_zipCode_postalCode VARCHAR,
workAddress_zipCode_plus4 VARCHAR,
...
)

View File

@ -0,0 +1,14 @@
@Entity
public class Contact {
@Id
private Integer id;
@Embedded
private Name name;
@Embedded
private Address homeAddress;
@Embedded
private Address mailingAddress;
@Embedded
private Address workAddress;
...
}

View File

@ -0,0 +1,7 @@
@Embeddable
public class Name {
private String firstName;
private String middleName;
private String lastName;
...
}

View File

@ -0,0 +1,8 @@
@Entity
public class Person {
@Id
private Integer id;
@Embedded
private Name name;
...
}

View File

@ -0,0 +1,7 @@
create table Person (
id integer not null,
firstName VARCHAR,
middleName VARCHAR,
lastName VARCHAR,
...
)

View File

@ -0,0 +1,9 @@
@Entity
public class Person {
@Id
private Integer id;
private String firstName;
private String middleName;
private String lastName;
...
}

View File

@ -0,0 +1,9 @@
MetadataSources sources = ...;
sources.addAnnotatedClass( Address.class );
sources.addAnnotatedClass( Name.class );
sources.addAnnotatedClass( Contact.class );
Metadata metadata = sources.getMetadataBuilder()
.applyImplicitNamingStrategy( ImplicitNamingStrategyComponentPathImpl.INSTANCE )
...
.build();

View File

@ -0,0 +1,111 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.annotations.basic;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Type;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
/**
* @author Steve Ebersole
*/
public class CollectionAsBasicTest extends BaseUnitTestCase {
@Test
public void testCollectionAsBasic() {
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build();
try {
Metadata metadata = new MetadataSources().addAnnotatedClass( Post.class )
.getMetadataBuilder().applyBasicType( new DelimitedStringsType() )
.build();
PersistentClass postBinding = metadata.getEntityBinding( Post.class.getName() );
Property tagsAttribute = postBinding.getProperty( "tags" );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity
@Table( name = "post")
public static class Post {
@Id
public Integer id;
@Basic
@Type( type = "delimited_strings" )
Set<String> tags;
}
public static class DelimitedStringsType extends AbstractSingleColumnStandardBasicType<Set> {
public DelimitedStringsType() {
super(
VarcharTypeDescriptor.INSTANCE,
new DelimitedStringsJavaTypeDescriptor()
);
}
@Override
public String getName() {
return "delimited_strings";
}
}
public static class DelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor<Set> {
public DelimitedStringsJavaTypeDescriptor() {
super(
Set.class,
new MutableMutabilityPlan<Set>() {
@Override
protected Set deepCopyNotNull(Set value) {
Set<String> copy = new HashSet<String>();
copy.addAll( value );
return copy;
}
}
);
}
@Override
public String toString(Set value) {
return null;
}
@Override
public Set fromString(String string) {
return null;
}
@Override
public <X> X unwrap(Set value, Class<X> type, WrapperOptions options) {
return null;
}
@Override
public <X> Set wrap(X value, WrapperOptions options) {
return null;
}
}
}