Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

EclipseLink/Development/JPA 2.0/mappedbyid

< EclipseLink‎ | Development‎ | JPA 2.0
Revision as of 09:39, 27 April 2009 by Guy.pelletier.oracle.com (Talk | contribs) (Internal processing)

MappedById Support

JPA 2.0 Root | Enhancement Request

Issue Summary

In JPA 1.0 primary key columns could only be mapped to @Basic attributes. Primary Keys that were derived from Foreign Keys had to be mapped twice, once as the relationship mapping and again as an @Basic. JPA 2.0 adds the functionality to designate a Relationship as providing the primary key for an Entity, see | DerivedId . When using an embeddedid, a @MappedById annotation can be used on a relationship mapping to indicate that it uses the same Id field that is also defined by the target ID field in the Embeddedid.

See JPA 2.0 section 2.4.1 for details.

General Solution

The EmbeddedID will be difficult to support as values in the EmbeddedID will need to be populated by EclipseLink on persist(), merge() calls even though they are not the sources of Identity for the Entity. Even more complicated if the derived ID is an IdClass then an instance of that class must be stored in the EmbeddedId which will be new functionality in EclipseLink. Transformation Mapping may be leveraged in combination with the CMP3Policy to support this functionality.

Metadata processing

The metadata processing will need a number of changes to support the notion of id classes and embedded id classes within their own embedded id's. The spec provided 6 examples, so we'll work through each and discuss the metadata processing behevior and any changes needed to it.

The first required change will be that we will create a new DerivedIdClassAccessor mapping. This accessor will be built for those cases where and EmbeddedId class maps to a either a derived IdClass or EmbeddedId class of the parent entity. Previously only, BasicAccessors were allowed on an embedded id class. We will now support both BasicAccessors and DerivedIdClassAccessors. The new DerivedIdClassAccessor will extend the existing EmbeddedAccessor, essentially creating one level of nested embedded mappings for the primary key class of the dependent entity.

All the mappings from the DerivedIdClassAccessor will be added to the owning descriptor's primary key list (and will also be used as the foreign key association from the dependant entity to the parent entity).

Example 1

The parent entity has a simple primary key and the dependent entity uses EmbeddedId to represent a composite key.

Model

@Entity
@Table(name="JPA_SARGEANT")
public class Sargeant {
    @Id
    @Column(name="ID")
    @GeneratedValue(strategy=TABLE, generator="SARGEANT_TABLE_GENERATOR")
    @TableGenerator(
        name="SARGEANT_TABLE_GENERATOR", 
        table="JPA_SARGEANT_SEQ", 
        pkColumnName="SEQ_NAME", 
        valueColumnName="SEQ_COUNT",
        pkColumnValue="SARGEANT_SEQ",
        initialValue=50
    )
    long sargeantId;

    @Basic
    @Column(name="NAME")
    String name;
    
    public String getName() {
        return name;
    }
    
    public long getSargeantId() {
        return sargeantId;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void setSargeantId(long sargeantId) {
        this.sargeantId = sargeantId;
    }
}

@Entity
@Table(name="JPA_MASTER_CORPORAL")
public class MasterCorporal {
    @EmbeddedId 
    MasterCorporalId id;
    
    @ManyToOne 
    @MappedById("sargeantPK")
    Sargeant sargeant;
    
    public MasterCorporalId getId() {
        return id;
    }
    
    public Sargeant getSargeant() {
        return sargeant;
    }
    
    public void setId(MasterCorporalId id) {
        this.id = id;
    }

    public void setSargeant(Sargeant sargeant) {
        this.sargeant = sargeant;
        id.setSargeantPK(sargeant.getSargeantId());
    }
}

@Embeddable
public class MasterCorporalId {
    @Column(name="NAME")
    String name;
    
    @Column(name="SARGEANTPK")
    long sargeantPK;
    
    public String getName() {
        return name;
    }
    
    public long getSargeantPK() {
        return sargeantPK;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setSargeantPK(long sargeantPK) {
        this.sargeantPK = sargeantPK;
    }
}

Test

EntityManager em = createEntityManager();
beginTransaction(em);
        
Sargeant sargeant = new Sargeant();
MasterCorporal masterCorporal = new MasterCorporal();
MasterCorporalId masterCorporalId = new MasterCorporalId();
sargeant.setName("Sarge");
em.persist(sargeant);
            
masterCorporalId.setName("Corpie");
masterCorporal.setId(masterCorporalId);
masterCorporal.setSargeant(sargeant);
em.persist(masterCorporal);
            
em.commit();
em.close();

Internal processing

When processing the @ManyToOne mapping, the mapping for sargeantPK will be retrieved from the embeddable descriptor for MasterCorporalId and the following foreign key association:

DatabaseField dependentField = sargeantPKMapping.getField(); // The mapping from the embeddable class
DatabaseField parentField = getReferenceDescriptor().getPrimaryKeyField(); // The primary key mapping from the Sargeant entity
manyToOneMapping.addForeignKeyField(dependentField, parentField);

// The example above will yield.
// manyToOneMapping.addForeignKeyField("JPA_MASTER_CORPORAL.SARGEANTPK", "JPA_SARGEANT.ID");

// And to ensure the primary key class is properly initialized from the CMP3Policy
manyToOneMapping.setIsDerivedIdMapping(true);
manyToOneMapping.setMappedByIdValue("sargeantPK");

Example 2

The parent entity uses IdClass and the dependent entity uses EmbeddedId. The type of the pk attribute is that same as that of the primary key of the parent entity.

Model

@Entity
@Table(name="JPA_MAJOR")
@IdClass(MajorId.class)
public class Major {
    @Id
    @Column(name="F_NAME")
    protected String firstName;

    @Id
    @Column(name="L_NAME")
    protected String lastName;
    
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public MajorId getPK() {
        return new MajorId(firstName, lastName);
    }
}

public class MajorId {
    public String firstName;
    public String lastName;
    
    public MajorId() {}
    
    public MajorId(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public boolean equals(Object other) {
        if (other instanceof MajorId) {
            final MajorId otherMajorId = (MajorId) other;
            return (otherMajorId.firstName.equals(firstName) && otherMajorId.lastName.equals(lastName));
        }
        
        return false;
    }
}

@Entity
@Table(name="JPA_CAPTAIN")
public class Captain {
    @EmbeddedId 
    CaptainId id;
    
    @ManyToOne 
    @MappedById("majorPK")
    Major major;
    
    public CaptainId getId() {
        return id;
    }
    
    public Major getMajor() {
        return major;
    }
    
    public void setId(CaptainId id) {
        this.id = id;
    }

    public void setMajor(Major major) {
        this.major = major;
        id.setMajorPK(major.getPK());
    }
}

@Embeddable
public class CaptainId {
    @Column(name="NAME")
    String name;
    MajorId majorPK;
    
    public String getName() {
        return name;
    }
    
    public MajorId getMajorPK() {
        return majorPK;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setMajorPK(MajorId majorPK) {
        this.majorPK = majorPK;
    }
}

Test

Major major = new Major();
MajorId majorId;
Captain captain = new Captain();
CaptainId captainId = new CaptainId();
        
major.setFirstName("Mr.");
major.setLastName("Major");
majorId = major.getPK();
em.persist(major);
            
captainId.setName("Captain Sparrow");
captain.setId(captainId);
captain.setMajor(major);
em.persist(captain);
em.commit()
em.close();            

Internal processing

The IdClass reference within the Embeddable class will be mapped to a DerivedIdClassAccessor which is an extension of an EmbeddedAccessor. Simply put, internally this is a nested embedded mapping.

From that mapping, we will iterate over its references mapping accessors (MajorId in this class) and add a foreign key association to each part of that id class.

for (MappingAccessor basicAccessor : getReferenceAccessors()) {
  DatabaseField dependentField = ((BasicAccessor) basicAccessor).getField();
  DatabaseField parentField = referenceDescriptor.getAccessorFor(basicAccessor.getAttributeName()).getMapping().getField();
               
  mapping.addForeignKeyField(dependentField, parentField);
}

// The example above will yield.
// manyToOneMapping.addForeignKeyField("JPA_CAPTAIN.FIRSTNAME", "JPA_MAJOR.F_NAME");
// manyToOneMapping.addForeignKeyField("JPA_CAPTAIN.LASTNAME", "JPA_MAJOR.L_NAME");

// And to ensure the primary key class is properly initialized from the CMP3Policy
manyToOneMapping.setIsDerivedIdMapping(true);
manyToOneMapping.setMappedByIdValue("sargeantPK");

Example 3

The parent entity used EmbeddedId and the dependent entity uses EmbeddedId.

Model

@Entity
@Table(name="JPA_CORPORAL")
public class Corporal {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name="firstName", column=@Column(name="F_NAME")),
        @AttributeOverride(name="lastName", column=@Column(name="L_NAME"))
    })
    CorporalId corporalId;
    
    public CorporalId getCorporalId() {
        return corporalId;
    }
    
    public void setCorporalId(CorporalId corporalId) {
        this.corporalId = corporalId;
    }
}

@Embeddable
public class CorporalId {
    @Column(name="FIRSTNAME")
    String firstName;
    
    @Column(name="LASTNAME")
    String lastName;
    
    public String getFirstName() {
        return firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }    
}

@Entity
@Table(name="JPA_PRIVATE")
public class Private {
    @EmbeddedId 
    PrivateId id;
    
    @ManyToOne 
    @MappedById("corporalPK")
    Corporal corporal;
    
    public PrivateId getId() {
        return id;
    }
    
    public Corporal getCorporal() {
        return corporal;
    }
    
    public void setId(PrivateId id) {
        this.id = id;
    }

    public void setCorporal(Corporal corporal) {
        this.corporal = corporal;
        id.setCorporalPK(corporal.getCorporalId());
    }
}

@Embeddable
public class PrivateId {
    @Column(name="NAME")
    String name;
    CorporalId corporalPK;
    
    public String getName() {
        return name;
    }
    
    public CorporalId getCorporalPK() {
        return corporalPK;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setCorporalPK(CorporalId corporalPK) {
        this.corporalPK = corporalPK;
    }
}

Test

Corporal corporal = new Corporal();
CorporalId corporalId = new CorporalId();
Private aPrivate = new Private();
PrivateId privateId = new PrivateId();
        
corporalId.setFirstName("Corporal");
corporalId.setLastName("Kenny");
corporal.setCorporalId(corporalId);
em.persist(corporal);
            
privateId.setName("Private Ryan");
aPrivate.setId(privateId);
aPrivate.setCorporal(corporal);
em.persist(aPrivate);
em.commit()
em.close();

Internal processing

The EmbeddedId reference within the Embeddable class will be mapped to a DerivedIdClassAccessor which is an extension of an EmbeddedAccessor. Simply put, internally this is a nested embedded mapping.

From that mapping, we will iterate over its references mapping accessors (CorporalId in this class) and add a foreign key association to each part of that embedded id class.

for (MappingAccessor basicAccessor : getReferenceAccessors()) {
  String defaultFieldName = basicAccessor.getMapping().getField().getName();
  // getField will look for an attribute override field.
  DatabaseField dependentField = getField(defaultFieldName);

  EmbeddedIdAccessor embeddedIdAccessor = referenceDescriptor.getEmbeddedIdAccessor();
  // getField will look for an attribute override field.
  DatabaseField parentField = embeddedIdAccessor.getField(defaultFieldName);

  manyToOneMapping.addForeignKeyField(dependentField, parentField);
}

// The example above will yield.
// manyToOneMapping.addForeignKeyField("JPA_PRIVATE.FIRSTNAME", "JPA_CORPORAL.F_NAME");
// manyToOneMapping.addForeignKeyField("JPA_PRIVATE.LASTNAME", "JPA_CORPORAL.L_NAME");

// And to ensure the primary key class is properly initialized from the CMP3Policy
manyToOneMapping.setIsDerivedIdMapping(true);
manyToOneMapping.setMappedByIdValue("sargeantPK");

Example 4

The parent entity has a simple primary key and the dependent's primary key consists of a single attribute corresponding to the simple primary key of the parent entity. The dependent entity has a primary key attribute in addition to the relationship attribute corresponding to the primary key. This attribute is mapped to the primary key by the mappedById annotation applied to the relationship.

Model

@Entity
@Table(name="JPA_GENERAL")
public class General { 
    @Id
    @Column(name="GENERAL_ID")
    @GeneratedValue(strategy=TABLE, generator="GENERAL_TABLE_GENERATOR")
    @TableGenerator(
        name="GENERAL_TABLE_GENERATOR", 
        table="JPA_GENERAL_SEQ", 
        pkColumnName="SEQ_NAME", 
        valueColumnName="SEQ_COUNT",
        pkColumnValue="GENERAL_SEQ",
        initialValue=50
    )
    Integer generalId;

    public Integer getGeneralId() {
        return generalId;
    }

    public void setGeneralId(Integer generalId) {
        this.generalId = generalId;
    } 
}

@Entity
@Table(name="JPA_LIEUTENANT_GENERAL")
public class LieutenantGeneral {
    @Id
    Integer id;

    @OneToOne
    @MappedById
    General general;
    
    public General getGeneral() {
        return general;
    }
    
    public Integer getId() {
        return id;
    }

    public void setGeneral(General general) {
        this.general = general;
        this.id = general.getGeneralId();
    }
    
    public void setId(Integer id) {
        this.id = id;
    }    
}

Test

General general = new General();
LieutenantGeneral lieutenantGeneral = new LieutenantGeneral();
em.persist(general);
            
lieutenantGeneral.setGeneral(general);
em.persist(lieutenantGeneral); 

em.commit()
em.close();

Internal processing

Probably the simplest case. The dependant entity's primary key will also be used as the foreign key to the parent entity.

DatabaseField dependentField = getDescriptor().getPrimaryKeyField();
DatabaseField parentField = getReferenceDescriptor().getPrimaryKeyField();
oneToOneMapping.addForeignKeyField(dependentField, parentField);

// The example above will yield.
// oneToOneMapping.addForeignKeyField("JPA_LIEUTENANT_GENERAL.ID", "JPA_GENERAL.GENERAL_ID");

Example 5

The parent entity uses IdClass. The dependent's primary key class is of the same type as that of the parent entity and the dependent entity uses the EmbeddedId and MappedById annotations. The IdClass either needs to be annotated Embeddable or denoted as an embeddable class in the XML descriptor.

Model

@Entity
@Table(name="JPA_MAJOR_GENERAL")
@IdClass(GeneralId.class)
public class MajorGeneral {
    @Id
    @Column(name="F_NAME")
    protected String firstName;

    @Id
    @Column(name="L_NAME")
    protected String lastName;
    
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public GeneralId getPK() {
        return new GeneralId(firstName, lastName);
    }
}

@Embeddable
public class GeneralId {
    public String firstName;
    public String lastName;
    
    public GeneralId() {}
    
    public GeneralId(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public boolean equals(Object other) {
        if (other instanceof GeneralId) {
            final GeneralId otherGeneralId = (GeneralId) other;
            return (otherGeneralId.firstName.equals(firstName) && otherGeneralId.lastName.equals(lastName));
        }
        
        return false;
    }
}

@Entity
@Table(name="JPA_BRIGADIER_GENERAL")
public class BrigadierGeneral {
    @EmbeddedId 
    GeneralId id;
    
    @OneToOne(cascade=PERSIST) 
    @MappedById
    MajorGeneral majorGeneral;
    
    public GeneralId getId() {
        return id;
    }
    
    public MajorGeneral getMajorGeneral() {
        return majorGeneral;
    }
    
    public void setId(GeneralId id) {
        this.id = id;
    }

    public void setMajorGeneral(MajorGeneral majorGeneral) {
        this.majorGeneral = majorGeneral;
        id = majorGeneral.getPK();
    }
}

Test

MajorGeneral majorGeneral = new MajorGeneral();
BrigadierGeneral brigadierGeneral = new BrigadierGeneral();
        
majorGeneral.setFirstName("Major");
majorGeneral.setLastName("Bilko");
            
// Test the cascade persist on this mapping.
brigadierGeneral.setMajorGeneral(majorGeneral);
em.persist(brigadierGeneral);
            
em.commit();
em.close(); 

Internal processing

The EmbeddedId reference within the Embeddable class will be mapped to a DerivedIdClassAccessor which is an extension of an EmbeddedAccessor. Simply put, internally this is a nested embedded mapping.

From that mapping, we will iterate over its references mapping accessors (CorporalId in this class) and add a foreign key association to each part of that embedded id class.

for (MappingAccessor basicAccessor : getReferenceAccessors()) {
  String defaultFieldName = basicAccessor.getMapping().getField().getName();
  // getField will look for an attribute override field.
  DatabaseField dependentField = getField(defaultFieldName);

  EmbeddedIdAccessor embeddedIdAccessor = referenceDescriptor.getEmbeddedIdAccessor();
  // getField will look for an attribute override field.
  DatabaseField parentField = embeddedIdAccessor.getField(defaultFieldName);

  manyToOneMapping.addForeignKeyField(dependentField, parentField);
}

// The example above will yield.
// manyToOneMapping.addForeignKeyField("JPA_BRIGADIER_GENERAL.FIRSTNAME", "JPA_MAJOR_GENERAL.F_NAME");
// manyToOneMapping.addForeignKeyField("JPA_BRIGADIER_GENERAL.LASTNAME", "JPA_MAJOR_GENERAL.L_NAME");

// And to ensure the primary key class is properly initialized from the CMP3Policy
manyToOneMapping.setIsDerivedIdMapping(true);
manyToOneMapping.setMappedByIdValue("sargeantPK");

Example 6

The parent entity uses EmbeddedId. The dependent's primary key is of the same type as that of the parent.

Model

@Entity
@Table(name="JPA_LIEUTENANT")
public class Lieutenant {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name="firstName", column=@Column(name="F_NAME")),
        @AttributeOverride(name="lastName", column=@Column(name="L_NAME"))
    })
    LieutenantId id;
    
    public LieutenantId getId() {
        return id;
    }
    
    public void setId(LieutenantId id) {
        this.id = id;
    }
}

@Embeddable
public class LieutenantId {
    public String firstName;
    public String lastName;
    
    public LieutenantId() {}
    
    public LieutenantId(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public boolean equals(Object other) {
        if (other instanceof LieutenantId) {
            final LieutenantId otherLieutenantId = (LieutenantId) other;
            return (otherLieutenantId.firstName.equals(firstName) && otherLieutenantId.lastName.equals(lastName));
        }
        
        return false;
    }
}

@Entity
@Table(name="JPA_SECOND_LIEUTENANT")
public class SecondLieutenant {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name="firstName", column=@Column(name="FIRST_NAME")),
        @AttributeOverride(name="lastName", column=@Column(name="LAST_NAME"))
    })
    LieutenantId id;
    
    @OneToOne 
    @MappedById
    Lieutenant lieutenant;
    
    public LieutenantId getId() {
        return id;
    }
    
    public Lieutenant getLieutenant() {
        return lieutenant;
    }
    
    public void setId(LieutenantId id) {
        this.id = id;
    }

    public void setLieutenant(Lieutenant lieutenant) {
        this.lieutenant = lieutenant;
        id = lieutenant.getId();
    }
}

Test

Lieutenant lieutenant = new Lieutenant();
LieutenantId lieutenantId = new LieutenantId();
SecondLieutenant secondLieutenant = new SecondLieutenant();
        
lieutenantId.setFirstName("Lieutenant");
lieutenantId.setLastName("Dan");
lieutenant.setId(lieutenantId);
em.persist(lieutenant);
            
secondLieutenant.setLieutenant(lieutenant);
em.persist(secondLieutenant);
            
em.commit();
em.close();

Internal processing


Work Required

  1. Develop model for testing access type settings
    approx ? days
  2. Update Processing
    approx ? days

Back to the top