From f32f68476b8c043852ba968b28cac4d0ecdddbb0 Mon Sep 17 00:00:00 2001 From: Emmanuel Bernard Date: Mon, 1 Mar 2010 13:26:56 +0000 Subject: [PATCH] HHH-4933 Doc on Map and List support git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18911 1b8cb986-b30d-0410-93ca-fae66ebed9b2 --- .../src/main/docbook/en/modules/entity.xml | 406 +++++++++++++----- .../org/hibernate/annotations/MapKey.java | 6 +- .../annotations/MapKeyManyToMany.java | 6 +- 3 files changed, 298 insertions(+), 120 deletions(-) diff --git a/annotations/src/main/docbook/en/modules/entity.xml b/annotations/src/main/docbook/en/modules/entity.xml index a4225c5eb9..bc000f40ab 100644 --- a/annotations/src/main/docbook/en/modules/entity.xml +++ b/annotations/src/main/docbook/en/modules/entity.xml @@ -24,7 +24,7 @@ - Entity Beans + Mapping Entities
Intro @@ -957,7 +957,7 @@ class UserId implements Serializable { identifier. In the database, it means that the Customer.user and the CustomerId.userId properties share the same - underlying column (user_fk in this case). + underlying column (user_fk in this case). In practice, your code only sets the Customer.user property and the user id value is @@ -966,7 +966,7 @@ class UserId implements Serializable { The id value can be copied as late as flush time, don't rely - on it until after flush time. + on it until after flush time. While not supported in JPA, Hibernate lets you place your @@ -1399,7 +1399,7 @@ public class Plane extends FlyingObject {
- Mapping entity bean associations/relationships + Mapping entity associations/relationships
One-to-one @@ -1493,7 +1493,7 @@ public class Passport implements Serializable { passport and the column id of Passport is id. - The third possibility (using an association table) is very + The third possibility (using an association table) is quite exotic. @@ -1536,8 +1536,7 @@ public class Passport implements Serializable { Many-to-one associations are declared at the property level with the annotation @ManyToOne: - -@Entity() + @Entity() public class Flight implements Serializable { @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) @JoinColumn(name="COMP_ID") @@ -1545,8 +1544,7 @@ public class Flight implements Serializable { return company; } ... -} - +} The @JoinColumn attribute is optional, the default value(s) is like in one to one, the concatenation of the name @@ -1563,8 +1561,7 @@ public class Flight implements Serializable { almost all cases. However this is useful when you want to use interfaces as the return type instead of the regular entity. - -@Entity() + @Entity public class Flight implements Serializable { @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class ) @@ -1577,9 +1574,9 @@ public class Flight implements Serializable { public interface Company { ... - +} - You can alse map a many to one association through an + You can also map a many-to-one association through an association table. This association table described by the @JoinTable annotation will contains a foreign key referencing back the entity table (through @@ -1587,8 +1584,7 @@ public interface Company { referencing the target entity table (through @JoinTable.inverseJoinColumns). - -@Entity() + @Entity public class Flight implements Serializable { @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) @JoinTable(name="Flight_Company", @@ -1599,8 +1595,7 @@ public class Flight implements Serializable { return company; } ... -} - +}
@@ -1611,36 +1606,266 @@ public class Flight implements Serializable { Overview You can map Collection, - List (ie ordered lists, not indexed lists), - Map and Set. The EJB3 - specification describes how to map an ordered list (ie a list - ordered at load time) using - @javax.persistence.OrderBy annotation: this - annotation takes into parameter a list of comma separated (target - entity) properties to order the collection by (eg firstname - asc, age desc), if the string is empty, the collection will - be ordered by id. For true indexed collections, please refer to the - . EJB3 allows you to map Maps using - as a key one of the target entity property using - @MapKey(name="myProperty") (myProperty is a - property name in the target entity). When using - @MapKey (without property name), the target - entity primary key is used. The map key uses the same column as the - property pointed out: there is no additional column defined to hold - the map key, and it does make sense since the map key actually - represent a target property. Be aware that once loaded, the key is - no longer kept in sync with the property, in other words, if you - change the property value, the key will not change automatically in - your Java model (for true map support please refers to ). Many people confuse - <map> capabilities and - @MapKey ones. These are two different features. - @MapKey still has some limitations, please check - the forum or the JIRA tracking system for more informations. + List, Map and + Set pointing to associated entities as + one-to-many or many-to-many associations using the + @OneToMany or + @ManyToMany annotation respectively. If the + collection is of a basic type or of an embeddable type, use + @ElementCollection. We will describe that in + more detail in the following subsections but let's first focus on + some semantic differences between the various collections. - Hibernate has several notions of collections. + Lists can be mapped in two different ways: - + + + as ordered lists, the order is not materialized in the + database + + + + as indexed lists, the order is materialized in the + database + + + + To order lists in memory, add + @javax.persistence.OrderBy to your property. This + annotation takes into parameter a list of comma separated properties + (of the target entity) and order the collection accordingly (eg + firstname asc, age desc), if the string is empty, the + collection will be ordered by the primary key of the target + entity. + + @Entity +public class Customer { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + @OneToMany(mappedBy="customer") + @OrderBy("number") + public List<Order> getOrders() { return orders; } + public void setOrders(List<Order> orders) { this.orders = orders; } + private List<Order> orders; +} + +@Entity +public class Order { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + public String getNumber() { return number; } + public void setNumber(String number) { this.number = number; } + private String number; + + @ManyToOne + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + private Customer number; +} + +-- Table schema +|-------------| |----------| +| Order | | Customer | +|-------------| |----------| +| id | | id | +| number | |----------| +| customer_id | +|-------------| + + To store the index value in a dedicated column, use the + @javax.persistence.OrderColumn annotation on + your property. This annotations describes the column name and + attributes of the column keeping the index value. This column is + hosted on the table containing the association foreign key. If the + column name is not specified, the default is the name of the + referencing property, followed by underscore, followed by + ORDER (in the following example, it would be + orders_ORDER). + + @Entity +public class Customer { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + @OneToMany(mappedBy="customer") + @OrderColumn(name"orders_index") + public List<Order> getOrders() { return orders; } + public void setOrders(List<Order> orders) { this.orders = orders; } + private List<Order> orders; +} + +@Entity +public class Order { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + public String getNumber() { return number; } + public void setNumber(String number) { this.number = number; } + private String number; + + @ManyToOne + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + private Customer number; +} + +-- Table schema +|--------------| |----------| +| Order | | Customer | +|--------------| |----------| +| id | | id | +| number | |----------| +| customer_id | +| orders_index | +|--------------| + + + We recommend you to convert + @org.hibernate.annotations.IndexColumn + usages to @OrderColumn unless you are + making use of the base property. The base + property lets you define the index value of the first element (aka + as base index). The usual value is 0 or + 1. The default is 0 like in Java. + + + Likewise, maps can borrow their keys from one of the + associated entity properties or have dedicated columns to store an + explicit key. + + To use one of the target entity property as a key of the map, + use @MapKey(name="myProperty") + (myProperty is a property name in the target + entity). When using @MapKey (without property + name), the target entity primary key is used. The map key uses the + same column as the property pointed out: there is no additional + column defined to hold the map key, and it does make sense since the + map key actually represent a target property. Be aware that once + loaded, the key is no longer kept in sync with the property, in + other words, if you change the property value, the key will not + change automatically in your Java model. + + @Entity +public class Customer { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + @OneToMany(mappedBy="customer") + @MapKey(name"number") + public Map<String,Order> getOrders() { return orders; } + public void setOrders(Map<String,Order> order) { this.orders = orders; } + private Map<String,Order> orders; +} + +@Entity +public class Order { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + public String getNumber() { return number; } + public void setNumber(String number) { this.number = number; } + private String number; + + @ManyToOne + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + private Customer number; +} + +-- Table schema +|-------------| |----------| +| Order | | Customer | +|-------------| |----------| +| id | | id | +| number | |----------| +| customer_id | +|-------------| + + Otherwise, the map key is mapped to a dedicated column or + columns. To customize things, use one of the following + annotations: + + + + @MapKeyColumn if the map key is a + basic type, if you don't specify the column name, the name of + the property followed by underscore followed by + KEY is used (for example + orders_KEY). + + + + @MapKeyEnumerated / + @MapKeyTemporal if the map key type is + respectively an enum or a Date. + + + + @MapKeyJoinColumn/@MapKeyJoinColumns + if the map key type is another entity. + + + + @AttributeOverride/@AttributeOverrides + when the map key is a embeddable object. Use + key. as a prefix for your embeddable object + property names. + + + + You can also use @MapKeyClass to define + the type of the key if you don't use generics (at this stage, you + should wonder why at this day and age you don't use + generics). + + @Entity +public class Customer { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + @OneToMany @JoinTable(name="Cust_Order") + @MapKeyColumn(name"orders_number") + public Map<String,Order> getOrders() { return orders; } + public void setOrders(Map<String,Order> orders) { this.orders = orders; } + private Map<String,Order> orders; +} + +@Entity +public class Order { + @Id @GeneratedValue public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + private Integer id; + + public String getNumber() { return number; } + public void setNumber(String number) { this.number = number; } + private String number; + + @ManyToOne + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + private Customer number; +} + +-- Table schema +|-------------| |----------| |---------------| +| Order | | Customer | | Cust_Order | +|-------------| |----------| |---------------| +| id | | id | | customer_id | +| number | |----------| | order_id | +| customer_id | | orders_number | +|-------------| |---------------| + + Let's now explore the various collection semantics based on + the mapping you are choosing. Collections semantics @@ -1668,18 +1893,18 @@ public class Flight implements Serializable { java.util.List, java.util.Collection - @org.hibernate.annotations.CollectionOfElements or - @OneToMany or @ManyToMany + @ElementCollection or @OneToMany or + @ManyToMany - Bag semantic with primary key (withtout the + Bag semantic with primary key (without the limitations of Bag semantic) java.util.List, java.util.Collection - (@org.hibernate.annotations.CollectionOfElements or - @OneToMany or @ManyToMany) and @CollectionId + (@ElementCollection or @OneToMany or @ManyToMany) and + @CollectionId @@ -1687,9 +1912,9 @@ public class Flight implements Serializable { java.util.List - (@org.hibernate.annotations.CollectionOfElements or - @OneToMany or @ManyToMany) and - @org.hibernate.annotations.IndexColumn + (@ElementCollection or @OneToMany or @ManyToMany) and + (@OrderColumn or + @org.hibernate.annotations.IndexColumn) @@ -1697,8 +1922,8 @@ public class Flight implements Serializable { java.util.Set - @org.hibernate.annotations.CollectionOfElements or - @OneToMany or @ManyToMany + @ElementCollection or @OneToMany or + @ManyToMany @@ -1706,75 +1931,20 @@ public class Flight implements Serializable { java.util.Map - (@org.hibernate.annotations.CollectionOfElements or - @OneToMany or @ManyToMany) and (nothing or - @org.hibernate.annotations.MapKey/MapKeyManyToMany for true - map support, OR @javax.persistence.MapKey + (@ElementCollection or @OneToMany or @ManyToMany) and + ((nothing or @MapKeyJoinColumn/@MapKeyColumn for true map + support) OR @javax.persistence.MapKey)
- So specifically, java.util.List collections without - @org.hibernate.annotations.IndexColumn are going to be considered as + Specifically, java.util.List collections without + @OrderColumn or @IndexColumn are going to be considered as bags. - Collection of primitive, core type or embedded objects is not - supported by the EJB3 specification. Hibernate Annotations allows - them however (see ). - - @Entity public class City { - @OneToMany(mappedBy="city") - @OrderBy("streetName") - public List<Street> getStreets() { - return streets; - } -... -} - -@Entity public class Street { - public String getStreetName() { - return streetName; - } - - @ManyToOne - public City getCity() { - return city; - } - ... -} - - -@Entity -public class Software { - @OneToMany(mappedBy="software") - @MapKey(name="codeName") - public Map<String, Version> getVersions() { - return versions; - } -... -} - -@Entity -@Table(name="tbl_version") -public class Version { - public String getCodeName() {...} - - @ManyToOne - public Software getSoftware() { ... } -... -} - - So City has a collection of - Streets that are ordered by - streetName (of Street) when - the collection is loaded. Software has a map of - Versions which key is the - Version codeName. - - Unless the collection is a generic, you will have to define - targetEntity. This is a annotation attribute that - take the target entity class as a value. + More support for collections are available via Hibernate + specific extensions (see ).
-
\ No newline at end of file + diff --git a/annotations/src/main/java/org/hibernate/annotations/MapKey.java b/annotations/src/main/java/org/hibernate/annotations/MapKey.java index b142eb01f3..60710c28f3 100644 --- a/annotations/src/main/java/org/hibernate/annotations/MapKey.java +++ b/annotations/src/main/java/org/hibernate/annotations/MapKey.java @@ -32,13 +32,17 @@ import javax.persistence.Column; /** * Define the map key columns as an explicit column holding the map key - * This is completly different from {@link javax.persistence.MapKey} which use an existing column + * This is completely different from {@link javax.persistence.MapKey} which use an existing column * This annotation and {@link javax.persistence.MapKey} are mutually exclusive * + * @deprecated Use {@link javax.persistence.MapKeyColumn} + * This is the default behavior for Map properties marked as @OneToMany, @ManyToMany + * or @ElementCollection * @author Emmanuel Bernard */ @Target({METHOD, FIELD}) @Retention(RUNTIME) +@Deprecated public @interface MapKey { Column[] columns() default {}; /** diff --git a/annotations/src/main/java/org/hibernate/annotations/MapKeyManyToMany.java b/annotations/src/main/java/org/hibernate/annotations/MapKeyManyToMany.java index 1e04b6e46b..9029b6378b 100644 --- a/annotations/src/main/java/org/hibernate/annotations/MapKeyManyToMany.java +++ b/annotations/src/main/java/org/hibernate/annotations/MapKeyManyToMany.java @@ -31,13 +31,17 @@ import javax.persistence.JoinColumn; /** * Define the map key columns as an explicit column holding the map key - * This is completly different from {@link javax.persistence.MapKey} which use an existing column + * This is completely different from {@link javax.persistence.MapKey} which use an existing column * This annotation and {@link javax.persistence.MapKey} are mutually exclusive * + * @deprecated Use {@link javax.persistence.MapKeyJoinColumn} {@link javax.persistence.MapKeyJoinColumns} + * This is the default behavior for Map properties marked as @OneToMany, @ManyToMany + * or @ElementCollection * @author Emmanuel Bernard */ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) +@Deprecated public @interface MapKeyManyToMany { JoinColumn[] joinColumns() default {}; /**