Jump to: navigation, search

EclipseLink/Development/JPA 2.0/new collection mappings

< EclipseLink‎ | Development‎ | JPA 2.0
Revision as of 07:25, 18 April 2009 by Douglas.clarke.oracle.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Element Collections

JPA 2.0 Root | bug 248293 | bug 265359

Summary

JPA 2.0 introduces support for collections of Embeddables and primitives through the @ElementCollection annotation. Support for ElementCollection of primitives will be easily supported through our BasicCollection and BasicMap support and will only require annotation processing updates. Support for lists of Embeddables should be supportable through the AggregateCollectionMapping. Support for Maps of Embeddables will be more difficult and will require completion of the Map Collections support feature.

@OrderBy must also be supported on these collection types.

Functional Requirements

See the following sections from the JPA 2.0 spec for more details.

  • 2.6 - Collections of Embeddable Classes and Basic Types
  • 2.7 - Map Collections
  • 10.1.2 - AssociationOverride Annotation
  • 10.1.3 - AssociationOverrides Annotation
  • 10.1.4 - AttributeOverride Annotation
  • 10.1.5 - AttributeOverrides Annotation
  • 10.1.7 - CollectionTable Annotation
  • 10.1.11 - ElementCollection Annotation
  • 10.1.26 - MapKey Annotation
  • 10.1.27 - MapKeyClass Annotation
  • 10.1.28 - MapKeyColumn Annotation
  • 10.1.29 - MapKeyJoinColumn Annotation
  • 10.1.30 - MapKeyJoinColumns Annotation

Design

Core changes

In JPA 2.0 Embeddable may play two distinct roles: it could be either @Embedded or be a target of @ElementCollection. Moreover, the same embeddable class may play these two roles simultaneously in the same persistence unit several times: it may be embedded in one or more master classes and be a target of ElementCollection one or more times (with different CollectionTables).

The core equivalent of the first role is AggregateObjectMapping, which requires the target descriptor to have AggregateDescriptor type; the second is AggregateCollectionMapping which requires the target descriptor to have AggregateCollection type. AggregateObject descriptor is cloned by AggregateObjectMapping targeting it, therefore it could be used by several different aggregating descriptors. AggregateCollectionDescriptor is not cloned by AggregateCollectionMapping and therefore may be a target of only one AggregateCollectionMapping.

To accomodate JPA 2.0 requirements for Embeddable the following changes in the core were made:

  1. The target of AggregateCollectionMapping may be an AggregateDescriptor;
  2. Each AggregateCollectionMapping clones the target descriptor (just like AggregateObjectMapping does) and customizes it:
    1. if the original descriptor type was AggregateObjectDescriptor then clone descriptor type changed AggregateCollectionDescriptor;
    2. optionally maps to each field of the target table(s) a new field (that allows each AggregateCollectionMapping to use it's own target table(s)).

These changes allow to always map JPA 2.0 Embeddable to core AggregateObjectDescriptor.

Note that all original core functionality is still available.

Metadata processing changes

A new metadata processing class, ElementCollectionAccessor will be built. Since we have already provided a solution for supporting direct collections and direct maps (through EclipseLink's @BasicMap and @BasicCollection) with the BasicCollectionAccesor and BasicMapAccessor, will create a new common level class DirectCollectionAccessor which will house the common processing code that will used by all three accessors.

With this re-use we quickly get support for ElementCollection's to basic collections and basic maps. ElementCollectionAccessor will further need to include metadata processing to support the usage of an Embeddable class within the collection. In this scenario, the metadata processing code will build an AggregateCollectionMapping.

The usage of an embeddable also allows for attribute and association overrides to be applied to the element collection (in the same way that they can be specified for an @Embedded mapping). Note: Since embeddable objects can now include nested embeddable objects, attribute and association overrides must support the dot notation name, e.g. 'location.venue'

The dot notation names can also be specified at the entity level to apply to mappings from a mapped superclass. An override from an entity, say 'records.location.venue' will override an attribute override specified in the 'records' attribute with the name 'location.venue'.

An element collection mapping can not be defaulted, it must be explicitly defined otherwise the attribute will default into a serialized basic mapping.

More documentation to come:

  • MapKey
  • MapKeyClass
  • MapKeyJoinColumn
  • OrderColumn
  • Nested ElementCollection support? (Not required by the spec but internal code changes can allow them)
  • key. and value. override name support.
  • Converter support (ConvertKey)
  • Association-override & join-table (requires support of JoinTable on M-1 and 1-1 mappings first)

Examples

Example 1 - An element collection representing a basic collection mapping.

Annotations

@ElementCollection
@Column(name="DESIGNATION")
// CollectionTable will default in this case, Entity name + "_" + attribute name
// JoinColumns will default in this case which are different from BasicCollection collection table default
private Collection<String> designations;

XML

<element-collection name="designations">
   <column name="DESIGNATION"/>
</element-collection>

Example 2 - An element collection representing an basic map mapping.

Annotations

@ElementCollection
@MapKeyColumn(name="Q_DATE")
@Column(name="QUOTE")
@CollectionTable(name="EXPERT_QUOTES", joinColumns=@JoinColumn(name="EBC_ID"))
public Map<Date, String> getQuotes() {
    return quotes;
}

XML

<element-collection name="quotes">
  <column name="QUOTE"/>
  <map-key-column name="Q_DATE"/>
  <collection-table name="EBC_QUOTES">
    <join-column name="EBC_ID"/>
  </collection-table>
</element-collection>

Example 3 - An element collection representing an embeddable collection mapping.

Annotations

// An element collection representing an aggregate collection mapping.
@ElementCollection
// CollectionTable will default
// Column is not applicable here.
// JoinColumns will default.
private Collection<Record> records;
 
...
 
@Embeddable
public class Record {

XML

<element-collection name="records"/>

Example 4 - An element collection with dot notation attribute and association overrides.

Annotations

@MappedSuperclass
@Access(FIELD)
public abstract class RatedBeerConsumer<X, Y, Z> extends BeerConsumer {
  ...
 
  @ElementCollection
  private Collection<Record> records;
 
  ...
}
 
@Embeddable
public class Record {
 
  ...
 
  @Embedded
  @AttributeOverrides({
    @AttributeOverride(name="name", column=@Column(name="VENUE_NAME")),
    @AttributeOverride(name="attendance", column=@Column(name="VENUE_ATTENDANCE"))
  })
  // Novice beer consumer is going to override VENUE_NAME to VENUE and use VENUE_ATTENDANCE as is.
  // Expert beer consumer is going to override VENUE_ATTENDANCE to WITNESSES and user VENUE_NAME as is.
  public Venue getVenue() {
      return venue;
  }
 
  ...
 
  @ManyToOne(cascade=PERSIST)
  @JoinColumn(name="L_ID")
  public Location getLocation() {
      return location;
  }
 
  ...
 
}
 
@Embeddable
public class Venue {
 
  ...
 
  @Embedded
  // There are no attribute overrides at this level, however, novice beer consumer is going to provide the following overrides:
  // - yearBuilt -> VENUE_YEAR_BUILT
  // - builder -> VENUE_BUILDER
  // And expert beer consumer is going to use the defaults (columns) of
  // - YEAR_BUILT
  // - BUILDER
  public VenueHistory getHistory() {
    return history;
  }
 
  ...
 
}

Now for the override specifications:

@Entity(name="NOVICE_CONSUMER")
@DiscriminatorValue(value="NBC")
@AttributeOverrides({
    @AttributeOverride(name="records.date", column=@Column(name="REC_DATE")),
    @AttributeOverride(name="records.description", column=@Column(name="DESCRIP")),
    @AttributeOverride(name="records.venue.name", column=@Column(name="VENUE")),
    @AttributeOverride(name="records.venue.history.yearBuilt", column=@Column(name="VENUE_YEAR_BUILT")),
    @AttributeOverride(name="records.venue.history.builder", column=@Column(name="VENUE_BUILDER"))
})
@AssociationOverride(name="records.location", joinColumns=@JoinColumn(name="LOC_ID", referencedColumnName="ID"))
public class NoviceBeerConsumer extends RatedBeerConsumer<Integer, Integer, Integer> {
 
 ...
 
}
 
@Entity(name="EXPERT_CONSUMER")
@DiscriminatorValue(value="EBC")
@AttributeOverrides({
    @AttributeOverride(name="records.date", column=@Column(name="RECORD_DATE")),
    @AttributeOverride(name="records.description", column=@Column(name="DESCRIPTION")),
    @AttributeOverride(name="records.venue.attendance", column=@Column(name="WITNESSES"))
})
@AssociationOverride(name="records.location", joinColumns=@JoinColumn(name="LOCATION_ID", referencedColumnName="ID"))
public class ExpertBeerConsumer extends RatedBeerConsumer<String, String, String> {
 
  ...
 
}

XML

 

Testing

Work Required

  1. Develop model for testing
    approx 10 days
  2. Update Annotation Processing
    approx 5 days - processing @ElementCollection annotation / and XML for primitive
    approx 10 days - processing @ElementCollection annotation / and XML for Embeddables
    approx 5 days - integrating Map collections support for Embeddables

Open Issues

  1. bug 272793 How does the new JPA @CollectionTable relate to the existing EclipseLink @CollectionTable and what does it mean for users upgrading from one to the other.