This Bugzilla instance is deprecated, and most Eclipse projects now use GitHub or Eclipse GitLab. Please see the deprecation plan for details.
Bug 211301 - Add aggregate-collection mapping support to the EclipseLink-ORM.XML Schema
Summary: Add aggregate-collection mapping support to the EclipseLink-ORM.XML Schema
Status: NEW
Alias: None
Product: z_Archived
Classification: Eclipse Foundation
Component: Eclipselink (show other bugs)
Version: unspecified   Edit
Hardware: All Windows XP
: P5 enhancement (vote)
Target Milestone: ---   Edit
Assignee: Project Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 325781
  Show dependency tree
 
Reported: 2007-11-28 14:24 EST by Guy Pelletier CLA
Modified: 2022-06-09 10:03 EDT (History)
8 users (show)

See Also:
peter.krogh: documentation+


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Guy Pelletier CLA 2007-11-28 14:24:23 EST
 
Comment 1 Doug Clarke CLA 2008-01-30 19:43:32 EST
In the typical 1:M usage you have a 1:1 back-reference mapped as:

<one-to-many name="phoneNumbers" mapped-by="owner"></one-to-many>

or

@OneToMany(mappedBy="owner")

I believe our AggregateCollection mapping can be done by combining the 1:1/M:1 type configuration with the 1:m to get a configuration like:

<one-to-many name="phoneNumbers" mapped-by="owner">
    <join-column name="OWNER_ID" table="PHONE"/>
</one-to-many>

This way it is more about our interpretation of the mapping using the target-foreign-key instead of a completely new mapping. For this to be complete the target class of the relationship should be an embeddable.

Comment 2 Guy Pelletier CLA 2008-03-12 11:47:07 EDT
Just pulling some old info to consider when someone picks up this bug. When we first started the TopLink annotations, this was our original take on this metadata (which we ultimately decided to defer and not implement)

===============================================

@EmbeddedCollection

The @EmbeddedCollection annotation is used to represent an aggregate relationship between a single source object and a collection of target objects. It maps to a TopLink AggregateCollectionMapping. The target objects cannot exist without the existence of the source object (private owned) and there must be a target table mapped from the target objects.

An @EmbeddedCollection will work similarly to the existing @Embedded annotation. In that, the access type for an embedded collection will be discovered in the same manner that it is currnetly discovered for an embedded object (That is, according to Sahoo's fix of Glassfish issue 831). Also, the @EmbeddedCollection is defaulted if the source entity holds a collection of objects that are decorated with the @Embeddable annotation.

The @EmbeddedCollection annotation is used in conjunction with @PrimaryKeyJoinColumn(s). If the source entity uses a composite primary key, a primary key join column must be specified for each field of the composite primary key. In a single primary key case, a primary key join column may optionally be specified. 

Defaulting will apply otherwise as follows:

name, the concatenation of the following: Embeddable.Table.name; "_";
the primary key column of the source entity. 
referencedColumnName, the primary key column of the source entity. 

@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface EmbeddedCollection {
    /**
     * (Optional) The target collection type.
     */
    Class target() default java.util.Vector.class;

    /**
     * (Optional) The target embeddable class. It should be specified if
     * generics are not used.
     */
    Class targetEmbeddable() default void.class;

    /**
     * (Optional) Defines whether the value of the field or property should
     * be lazily loaded or must be eagerly fetched. The EAGER
     * strategy is a requirement on the persistence provider runtime
     * that the value must be eagerly fetched. TheLAZY
     * strategy is a hint to the persistence provider runtime.
     * If not specified, defaults toLAZY.
     */
    FetchType fetch() default LAZY;
}

==== Example

@EmbeddedCollection
@PrimaryKeyJoinColumn(name="CUSTOMER_ID", referenceColumnName="CUSTOMER_ID")
public CollectiongetDependants() {
    return dependants;
}

@Embeddable
@Table(name="DEPEND")
public Dependant {
    ...
}
Comment 3 Michael Keith CLA 2008-03-14 14:31:18 EDT
At first blush I thought our aggregate collection mapping was really just a collection of embeddable objects so my original thought was to just implement the aggregate collection mapping using the JPA 2.0 @ElementCollection annotation. Upon further inspection and discussion it became clearer that they are not similar enough to warrant this. The problem is that neither is it a uni-driectional 1-m, so we really need to just create an @AggregateCollection annotation to model what we already have.

The JPA 2.0 @CollectionTable annotation can still be used, however, to model the table.
Comment 4 Andrei Ilitchev CLA 2008-03-14 16:53:22 EDT
AggregateCollection target can't be @Entity, so it should be @Embeddable.

Note that because AggregateCollection target is not a true Embeddable there will be both limitation and additional functionality.
The limitation:
Embeddable used as an AgregateCollectionTarget can neither be used as a target for another AggregateCollection nor as a "simple" Embeddable(AggregateObject).
The additional functionality:
Embeddable used as an AggregateCollectionTarget may also have @Ids, @GeneartedValues, etc.

Example from toplink tests converted to use the suggested annotations:
Agent has an AggregateCollection of Customers; Customer has AggregateCollection of Dependents.
AGENT table has pk AGENT_ID;
CUSTOMER table has pk CUSTOMER_ID and fk OWNER_AGENT_ID;
DEPENDENT table has 2 dim. pk OWNER_CUSTOMER_ID (also fk) and NAME.
Both Agent and Customer use sequencing.

@Entity 
public class Agent {
  @Id
  @GeneartedValue(strategy=SEQUENCE, generator="AGENT_GEN")
  @SequenceGenerator(name="AGENT_GEN", sequenceName="AGENT_SEQ")
  @Column(name="AGENT_ID");
  int id;
  String name;
  // target class is defaulted to the type parametrizing collection.
  @AggregateCollection
  @CollectionTable(name="CUSTOMER", joinColumns={@JoinColumn(name="OWNER_AGENT_ID", referencedColumnName="AGENT_ID")}, UniqueContraints{@UniqueConstraint(columnNames={"CUSTOMER_ID"})})
  @AttributesOverrides{
    @AttributeOverride(name="id", column=@Column(name="CUSTOMER_ID"),
    @AttributeOverride(name="name", column=@Column(name="NAME")
  }
  Collection<Customer> customers;
  ...
}

@Embeddable 
public class Customer {
  @Id
  @GeneartedValue(strategy=SEQUENCE, generator="CUSTOMER_GEN")
  @SequenceGenerator(name="CUSTOMER_GEN", sequenceName="CUSTOMER_SEQ")
  int id;
  String name;
  // target class is defaulted to the type parametrizing collection.
  @AggregateCollection
  @CollectionTable(name="DEPENDENT", joinColumns={@JoinColumn(name="OWNER_CUSTOMER_ID", referencedColumnName="CUSTOMER_ID")}, UniqueContraints{@UniqueConstraint(columnNames={"OWNER_CUSTOMER_ID", "NAME"})})
  @AttributesOverrides{
    @AttributeOverride(name="name", column=@Column(name="NAME")
    @AttributeOverride(name="age", column=@Column(name="AGE")
  }
  Collection<Dependent> dependents;
...
}

@Embeddable 
public class Dependent {
  String name;
  int age;
  ...
}
Comment 5 Michael Keith CLA 2008-03-14 17:17:16 EDT
Fixed up example:

@Entity 
public class Agent {
  @Id
  @GeneratedValue(strategy=SEQUENCE, generator="AGENT_GEN")
  @SequenceGenerator(name="AGENT_GEN", sequenceName="AGENT_SEQ")
  @Column(name="AGENT_ID");
  int id;
  String name;
  @AggregateCollection
  @CollectionTable(name="CUSTOMER",
    joinColumns=@JoinColumn(name="OWNER_AGENT_ID"),
    uniqueConstraints=@UniqueConstraint(columnNames="CUSTOMER_ID"))
  Collection<Customer> customers;
  ...
}

@Embeddable 
public class Customer {
  @Id
  @GeneratedValue(strategy=SEQUENCE, generator="CUSTOMER_GEN")
  @SequenceGenerator(name="CUSTOMER_GEN", sequenceName="CUSTOMER_SEQ")
  @Column(name="CUSTOMER_ID")
  int id;
  String name;
  @AggregateCollection
  @CollectionTable(name="DEPENDENT",
    joinColumns=@JoinColumn(name="OWNER_CUSTOMER_ID"),
    uniqueConstraints=@UniqueConstraint(
      columnNames={"OWNER_CUSTOMER_ID","NAME"}))
  Collection<Dependent> dependents;
...
}

@Embeddable 
public class Dependent {
  String name;
  int age;
  ...
}

Of course this introduces the problem of allowing Embeddables to have identifiers...
Comment 6 Doug Clarke CLA 2008-03-19 14:02:52 EDT
With the differences between our Aggregate Collection mapping and what is coming in EmbeddableCollection combined with the very rare usage of this mapping I would like to propose that we do not create an @AggregateCollection mapping. We will add support for this mapping in XML and API only.

Please add an example of what the XML will look like for review.
Comment 7 Andrei Ilitchev CLA 2008-03-19 16:24:40 EDT
Here's xml example.

<entity name="XMLAgent" class="org.eclipse.persistence.testing.models.jpa.aggregate.Agent" access="PROPERTY">
    <table name="CMP3_XML_AGENT"/>
    <sequence-generator name="CMP3_XML_AGENT_SEQUENCE_GENERATOR" sequenceName="CMP3_XML_AGENT_SEQ"/>
    <attributes>
        <id name="id">
	    <column name="AGENT_ID"/>
    	    <generated-value strategy="SEQUENCE" generator="CMP3_XML_AGENT_SEQUENCE_GENERATOR"/>
        </id>
        <basic name="firstName">
	    <column name="F_NAME"/>
        </basic>
        <basic name="lastName">
	    <column name="L_NAME"/>
        </basic>
        <aggregate-collection name="customers">
	    <collection-table name="CMP3_XML_CUSTOMER">
	        <primary-key-join-column name="OWNER_AGENT_ID" referenced-column-name="AGENT_ID"/>
	        <unique-constraint>
		    <column-name>
		        "CUSTOMER_ID"
		    <column-name/>
	        <unique-constraint/>
	    <collection-table/>
	    <attribute-override name="id">
	        <column name="CUSTOMER_ID"/>
	    </attribute-override>
        </aggregate-collection>
    </attributes>
</entity>

<embeddable class="org.eclipse.persistence.testing.models.jpa.aggregate.Customer" access="PROPERTY">
    <sequence-generator name="CMP3_XML_CUSTOMER_SEQUENCE_GENERATOR" sequenceName="CMP3_XML_CUSTOMER_SEQ"/>
    <attributes>
        <id name="id">
	    <column name="CUSTOMER_ID"/>
	    <generated-value strategy="SEQUENCE" generator="CMP3_XML_CUSTOMER_SEQUENCE_GENERATOR"/>
        </id>
        <aggregate-collection name="dependents">
	    <collection-table name="CMP3_XML_DEPENDENT">
	        <primary-key-join-column name="OWNER_CUSTOMER_ID" referenced-column-name="CUSTOMER_ID"/>
	        <unique-constraint>
		    <column-name>
		        "OWNER_CUSTOMER_ID"
		    <column-name/>
		    <column-name>
		        "NAME"
		    <column-name/>
	        <unique-constraint/>
	    <collection-table/>
        </aggregate-collection>
    </attributes>
</embeddable>

<embeddable class="org.eclipse.persistence.testing.models.jpa.aggregate.Dependent" access="PROPERTY"/>

Note that <collection-table> has been already implemented using <primary-key-join-column> in main (instead of <join-column> as in jpa 2.0).
Comment 8 Andrei Ilitchev CLA 2008-04-04 14:16:59 EDT
The jpa 2.0-style approach that was chosen can't cover the entire AggregateCollections functionality currently available in TopLink (and tested by TopLink tests).

Jpa 2.0 - style approach means specifying most of AggregateCollection information on the source: <collection-table> element specifies the table to be used by the target; <attribute-override> maps fields of this table to the attributes of the target class.

However the existing aggragate collection functionality seem to be too rich to be expressed in this way without excessive complexity.
Currently AggregateCollection target in TopLink:
1. May have a pk(s) and may use Sequencing;
2. May have to-one mappings;
3. May use inheritance (multitable included).

I tried to express this functionality using jpa 2.0 - style approach
adding under <aggregate-collection>:
1. <id> element (that possibly includes <generated-value>) - to override (implicit or explicit) <basic> on the target;
2. <association-override> element to support to-one mappings;

3.1 Inheritance requires <descriminator-column> - can't specify it on the target (because it doesn't have table) - so need to specify it on the source (under <aggregate-collection>)
3.2 Multitable inheritance is the real problem: how to specify the second table? How to map it ot descendent class? how to override attributes / associations for the attributes added in the descendent?

Below is my latest take on the aggragate test model (only the part that tests aggregate collections) using jpa 2.0-style approach, with comments in places where I could not find a good solution.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xsi:schemaLocation="xsd/eclipselink_orm_1_0.xsd" version="1.0" xmlns="http://www.eclipse.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <description>Aggregate Test Model Instance Document</description>
    <package>org.eclipse.persistence.testing.models.jpa.xml.aggregate</package>
    <entity name="XMLAgent" class="Agent" access="PROPERTY">
        <table name="CMP3_XML_AGENT"/>
        <optimistic-locking type="VERSION_COLUMN" cascade="true"/>
        <table-generator name="XML_AGENT_TABLE_GENERATOR" table="CMP3_XML_AGGREGATE_SEQ" pk-column-name="SEQ_NAME" value-column-name="SEQ_COUNT" pk-column-value="XML_AGENT_SEQ"/>
        <table-generator name="XML_CUSTOMER_TABLE_GENERATOR" table="CMP3_XML_AGGREGATE_SEQ" pk-column-name="SEQ_NAME" value-column-name="SEQ_COUNT" pk-column-value="XML_CUSTOMER_SEQ"/>
        <attributes>
            <basic name="firstName">
                <column name="FNAME"/>
            </basic>
            <basic name="lastName">
                <column name="LNAME"/>
            </basic>
            <id name="id">
                <column name="AGENT_ID"/>
                <generated-value strategy="TABLE" generator="XML_AGENT_TABLE_GENERATOR"/>
            </id>
            <aggregate-collection name="houses">
                <collection-table name="CMP3_XML_HOUSE">
                    <primary-key-join-column name="OWNER_AGENT_ID" referenced-column-name="AGENT_ID"/>
                    <unique-constraint>
                        <column-name>
                            "OWNER_AGENT_ID"
                        </column-name>
                        <column-name>
                            "LOCATION"
                        </column-name>
                    </unique-constraint>
                </collection-table>
                <!-- How to define TYPE - inheritance indicator field? specify it here along with inheritance strategy -->
		        <inheritance strategy="JOINED"/>
		        <discriminator-column name="TYPE"/>
                <!-- How to define specify the second table - for SingleHouse ???? -->
                <!-- How to override attibutes added in SingleHouse ??? -->
                <aggregate-collection name="sellingPoints" target-class="SellingPoint">
                    <collection-table name="CMP3_XML_SELLING_POINT">
                        <primary-key-join-column name="OWNER_OWNER_AGENT_ID" referenced-column-name="OWNER_AGENT_ID"/>
                        <primary-key-join-column name="OWNER_LOCATION" referenced-column-name="LOCATION"/>
                        <unique-constraint>
                            <column-name>
                                "OWNER_OWNER_AGENT_ID"
                            </column-name>
                            <column-name>
                                "OWNER_LOCATION"
                            </column-name>
                            <column-name>
                                "AREA"
                            </column-name>
                        </unique-constraint>
                    </collection-table>
                </aggregate-collection>
                <attribute-override name="insuranceId.id">
                    <column name="INS_ID"/>
                </attribute-override>
            </aggregate-collection>
            <aggregate-collection name="customers">
                <collection-table name="CMP3_XML_CUSTOMER">
                    <primary-key-join-column name="OWNER_AGENT_ID" referenced-column-name="AGENT_ID"/>
                    <unique-constraint>
                        <column-name>
                            "CUSTOMER_ID"
                        </column-name>
                    </unique-constraint>
                </collection-table>
	            <id name="id">
	                <column name="CUSTOMER_ID"/>
	                <generated-value strategy="TABLE" generator="XML_CUSTOMER_TABLE_GENERATOR"/>
	            </id>
                <attribute-override name="name">
                    <column name="LAST_NAME"/>
                </attribute-override>
	            <aggregate-collection name="dependants" target-class="Dependant">
	                <collection-table name="CMP3_XML_DEPENDANT">
	                    <primary-key-join-column name="OWNER_CUSTOMER_ID" referenced-column-name="CUSTOMER_ID"/>
	                    <unique-constraint>
	                        <column-name>
	                            "OWNER_CUSTOMER_ID"
	                        </column-name>
	                        <column-name>
	                            "FIRST_NAME"
	                        </column-name>
	                    </unique-constraint>
	                </collection-table>
	                <attribute-override name="firstName">
	                    <column name="FIRST_NAME"/>
	                </attribute-override>
	            </aggregate-collection>
                <association-override name="company">
                    <join-column name="COMPANY_ID"/>
                </association-override>
            </aggregate-collection>
            <version name="version">
                <column name="VERSION"/>
            </version>
        </attributes>
    </entity>
    <embeddable class="Customer" access="PROPERTY"/>
    <embeddable class="Dependant" access="PROPERTY"/>
    <embeddable class="House" access="PROPERTY"/>
        <discriminator-value>H</discriminator-value>
    </embeddable>
    <embeddable class="TownHouse" access="PROPERTY"/>
        <discriminator-value>TH</discriminator-value>
    </embeddable>
    <embeddable class="SingleHouse" access="PROPERTY"/>
        <discriminator-value>SH</discriminator-value>
        <!-- How to specify the additional table used by SingleHouse? -->
    </embeddable>
    <embeddable class="SellingPoint" access="PROPERTY"/>
        <inheritance strategy="SINGLE"/>
        <discriminator-column name="TYPE"/>
        <discriminator-value>SP</discriminator-value>
    </embeddable>
    <embeddable class="RoomSellingPoing" access="PROPERTY"/>
        <discriminator-value>RSP</discriminator-value>
    </embeddable>
    <embeddable class="Oid" access="PROPERTY"/>
    <entity name="XMLCompany" class="Company" access="PROPERTY">
        <table name="CMP3_XML_COMPANY_AGG"/>
        <table-generator name="XML_COMPANY_TABLE_GENERATOR" table="CMP3_XML_AGGREGATE_SEQ" pk-column-name="SEQ_NAME" value-column-name="SEQ_COUNT" pk-column-value="XML_COMPANY_SEQ"/>
        <attributes>
            <id name="companyId">
                <column name="COMPANY_ID"/>
                <generated-value strategy="TABLE" generator="XML_COMPANY_TABLE_GENERATOR"/>
            </id>
        </attributes>
    </entity>
</entity-mappings>



I suggest to abandon jpa 2.0 - style approach in favour of toplink-style approach: define <aggregate-collection-target> element that would be very close (if not identical) to <entity> (allowing tables, all kinds of mappings, id, inheritance etc.) to be used as <aggregate-collection> target.

In the future it may be possible to extend AggregateCollection functionality allowing several sources to use the same target class (overriding the target's table(s) and all the fields - just like AggregateObject mapping does).
Comment 9 Peter Krogh CLA 2009-04-03 16:02:23 EDT
These JPA enhancements were accidentally assigned to 1.1.1.
Comment 10 Doug Clarke CLA 2009-11-27 11:00:10 EST
Moving to 2.1 where our goal is to have the eclipselink-orm.xsd fully support all native features currently available in the deployment XML XSD
Comment 11 Guy Pelletier CLA 2009-12-08 09:29:54 EST
Updating priority due to revised bug categorization process.  See the following
page for details:

http://wiki.eclipse.org/EclipseLink/Development/Bugs/Guidelines#Priority_and_Target_Milestone 

If you feel the updated priority is incorrect, please send an email to
eclipselink-users@eclipse.org.
Comment 12 Eclipse Webmaster CLA 2022-06-09 10:03:43 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink