diff --git a/documentation/src/main/docbook/integration/en-US/Hibernate_Integrations.xml b/documentation/src/main/docbook/integration/en-US/Hibernate_Integrations.xml index c240514fa3..4c5a484af3 100644 --- a/documentation/src/main/docbook/integration/en-US/Hibernate_Integrations.xml +++ b/documentation/src/main/docbook/integration/en-US/Hibernate_Integrations.xml @@ -44,7 +44,11 @@ - + + + diff --git a/documentation/src/main/docbook/manual/en-US/chapters/portability/Portability.xml b/documentation/src/main/docbook/manual/en-US/chapters/portability/Portability.xml index cf58b1abbc..38c8e458be 100644 --- a/documentation/src/main/docbook/manual/en-US/chapters/portability/Portability.xml +++ b/documentation/src/main/docbook/manual/en-US/chapters/portability/Portability.xml @@ -189,4 +189,8 @@ --> + + JPA portability + * HQL/JPQL differences + * naming strategies diff --git a/documentation/src/main/docbook/mapping/en-US/Hibernate_Mapping.xml b/documentation/src/main/docbook/mapping/en-US/Hibernate_Mapping.xml index 7babd3a1b1..242ce43ed2 100644 --- a/documentation/src/main/docbook/mapping/en-US/Hibernate_Mapping.xml +++ b/documentation/src/main/docbook/mapping/en-US/Hibernate_Mapping.xml @@ -46,13 +46,20 @@ - + + diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/collection/Collection.xml b/documentation/src/main/docbook/mapping/en-US/chapters/collection/Collection.xml new file mode 100644 index 0000000000..44d1e466d1 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/collection/Collection.xml @@ -0,0 +1,111 @@ + + + + Collections + +
+ Collections as a value type + + discussions of what it means for them to be value types - lifecycle, opt-locking + + + 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. + + + Collection attributes do not support null value semantics; Hibernate does not distinguish between a null + collection reference and an empty collection. + + + 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. + +
+ +
+ Collections of value types + + 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 + +
+ +
+ Collections of entities + + * one-to-many + * many-to-many + +
+ +
+ List - index + + + todo : discuss mapping list index + +
+ +
+ Map - key + + + todo : discuss mapping map key + +
+ +
+ Bags + + + todo : discuss mapping bags + +
+ +
+ Arrays + + + todo : discuss mapping arrays + +
+ +
+ Collections as basic value type + + 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. + + + This is sometimes beneficial. Consider a use-case such as a VARCHAR column that represents a + delimited list or set of Strings. + + + Delimited set of tags + + + + + See the Hibernate Integrations Guide for more details on developing + custom value type mappings. + +
+ +
\ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/collection/extras/DelimitedStringTagsExample.java b/documentation/src/main/docbook/mapping/en-US/chapters/collection/extras/DelimitedStringTagsExample.java new file mode 100644 index 0000000000..3571beeb01 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/collection/extras/DelimitedStringTagsExample.java @@ -0,0 +1,59 @@ +@Entity +public static class Post { + @Id + public Integer id; + @Basic + @Type( type = "delimited_strings" ) + Set tags; +} + + +public static class DelimitedStringsType extends AbstractSingleColumnStandardBasicType { + public DelimitedStringsType() { + super( + VarcharTypeDescriptor.INSTANCE, + new DelimitedStringsJavaTypeDescriptor() + ); + } + + @Override + public String getName() { + return "delimited_strings"; + } +} + +public static class DelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor { + public DelimitedStringsJavaTypeDescriptor() { + super( + Set.class, + new MutableMutabilityPlan() { + @Override + protected Set deepCopyNotNull(Set value) { + Set copy = new HashSet(); + copy.addAll( value ); + return copy; + } + } + ); + } + + @Override + public String toString(Set value) { + return null; + } + + @Override + public Set fromString(String string) { + return null; + } + + @Override + public X unwrap(Set value, Class type, WrapperOptions options) { + return null; + } + + @Override + public Set wrap(X value, WrapperOptions options) { + return null; + } +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/Composition.xml b/documentation/src/main/docbook/mapping/en-US/chapters/composition/Composition.xml new file mode 100644 index 0000000000..acbf66d60a --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/Composition.xml @@ -0,0 +1,210 @@ + + + + Compositions + + + Related topics + + + + + + + + + + + + + + + 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. + + + + Simple composition example + + + + + + A composition is another form of value type. The lifecycle of a composition is defined by the thing that + contains it. + + + + A composition inherits the attribute access of its parent. For details on attribute access, see + . + + + + 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. + + +
+ Component / Embedded + + This is the form of composition you will see most often. Here an entity or another composition + is the container. + + + + Simple Embedded + + + + + + 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). + + + + + The composition here is the Name type related to Person.name. + + + + Person table + + + + + 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. + + + + Alternative to composition + + + + + The composition form is certainly more OO. And that becomes more evident as we work with multiple + compositions. + +
+ +
+ Multiple compositions + + + Multiple compositions + + + + + 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. + + + + 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. + + +
+ JPA's AttributeOverride + + + The JPA-defined way to handle this situation is through the use of its AttributeOverride annotation. + + + + JPA's AttributeOverride + + + + + Now, essentially there are no implicit column names in the Address compositions. We have explicitly + named them. + +
+ +
+ ImplicitNamingStrategy + + + + This is a Hibernate specific feature. Users concerned with JPA provider portability should instead + prefer explicit column naming with AttributeOverride as per + + + + + Hibernate naming strategies are covered in detail in . 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. + + + + Enabling composition-safe implicit naming + + + + + Now the "path" to attributes are used in the implicit column naming. + + + + Enabling composition-safe implicit naming + + + + + You could even develop your own to do special implicit naming. + +
+
+ +
+ Collections of compositions + + Collections of compositions are specifically value collections (as compositions are a value type). Value + collections are covered in detail in . + + + The one thing to add to the discussion of value collections in regards to compositions is that + the composition cannot, in turn, define collections. + +
+ +
+ Compositions as Map key + + Compositions can also be used as the key values for Maps. Mapping Maps and their keys is convered in + detail in . + + + Again, compositions used as a Map key cannot, in turn, define collections. + +
+ +
+ Compositions as identifiers + + Compositions can also be used as entity identifiers. This usage is covered in detail in + + + + Again, compositions used as an entity identifier cannot, in turn, define collections. + +
+
\ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Address.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Address.java new file mode 100644 index 0000000000..fc13041d01 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Address.java @@ -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; + ... + } +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact-AttributeOverride.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact-AttributeOverride.java new file mode 100644 index 0000000000..f8c23dda1d --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact-AttributeOverride.java @@ -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; + ... +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact-ImplicitNamingStrategy.sql b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact-ImplicitNamingStrategy.sql new file mode 100644 index 0000000000..09f59a25c7 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact-ImplicitNamingStrategy.sql @@ -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, + ... +) \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact.java new file mode 100644 index 0000000000..32cdea07b8 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Contact.java @@ -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; + ... +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Name.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Name.java new file mode 100644 index 0000000000..69835c2999 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Name.java @@ -0,0 +1,7 @@ +@Embeddable +public class Name { + private String firstName; + private String middleName; + private String lastName; + ... +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person.java new file mode 100644 index 0000000000..37db28cf3a --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person.java @@ -0,0 +1,8 @@ +@Entity +public class Person { + @Id + private Integer id; + @Embedded + private Name name; + ... +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person1.sql b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person1.sql new file mode 100644 index 0000000000..7dbc0647c4 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person1.sql @@ -0,0 +1,7 @@ +create table Person ( + id integer not null, + firstName VARCHAR, + middleName VARCHAR, + lastName VARCHAR, + ... +) \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person_alt.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person_alt.java new file mode 100644 index 0000000000..7f296932c3 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/Person_alt.java @@ -0,0 +1,9 @@ +@Entity +public class Person { + @Id + private Integer id; + private String firstName; + private String middleName; + private String lastName; + ... +} \ No newline at end of file diff --git a/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/component-safe-implicit-naming.java b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/component-safe-implicit-naming.java new file mode 100644 index 0000000000..8fa6857948 --- /dev/null +++ b/documentation/src/main/docbook/mapping/en-US/chapters/composition/extras/component-safe-implicit-naming.java @@ -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(); \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/basic/CollectionAsBasicTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/basic/CollectionAsBasicTest.java new file mode 100644 index 0000000000..af4d5b786b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/basic/CollectionAsBasicTest.java @@ -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 . + */ +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 tags; + } + + public static class DelimitedStringsType extends AbstractSingleColumnStandardBasicType { + + public DelimitedStringsType() { + super( + VarcharTypeDescriptor.INSTANCE, + new DelimitedStringsJavaTypeDescriptor() + ); + } + + @Override + public String getName() { + return "delimited_strings"; + } + } + + public static class DelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor { + public DelimitedStringsJavaTypeDescriptor() { + super( + Set.class, + new MutableMutabilityPlan() { + @Override + protected Set deepCopyNotNull(Set value) { + Set copy = new HashSet(); + copy.addAll( value ); + return copy; + } + } + ); + } + + @Override + public String toString(Set value) { + return null; + } + + @Override + public Set fromString(String string) { + return null; + } + + @Override + public X unwrap(Set value, Class type, WrapperOptions options) { + return null; + } + + @Override + public Set wrap(X value, WrapperOptions options) { + return null; + } + } +}