Community
Participate
Working Groups
Build ID: v20090713 Steps To Reproduce: Test passed in M4, but failed in M5. BaseAccount --Account(abstract Entity) --BankAccount(abstractMappedSuperclass) --CheckingAccount(Entity) BaseClient --Client(MappedSuperclass) --DummyClient(Entity) --VIPClient (Entity) Client and Account have 1xM relationship as Client.java @MappedSuperclass public class Client extends BaseClient implements java.io.Serializable { private String clientId; Collection<Account> accounts = new Vector<Account>(); @Id @Column(length=55) public String getClientId () { return this.clientId; } public void setClientId (String v) { this.clientId = v; } @OneToMany (fetch = FetchType.EAGER) @JoinTable(name="CLIENT_ACCT", joinColumns=@JoinColumn(name="C_ID", referencedColumnName="CLIENTID"), inverseJoinColumns=@JoinColumn(name="A_ID", referencedColumnName="ACCTNUM") ) public Collection<Account> getAccounts() { return this.accounts; } public void setAccounts(Collection<Account> v) { this.accounts = v; } public void addAccount(Account account){ accounts.add(account); incrementAccountCount(); } Deployment error in server.log mentioned about composite primary key and referenceColumnName. However, the composite primary key is not used in Client and Account, and referenceColumnName is specified. [#|2009-07-15T07:40:39.305-0700|SEVERE|glassfish|javax.enterprise.system.core.com.sun.enterprise.v3.server|_ThreadID=17;_ThreadName=Thread-1;|Exception while invoking class org.glassfish.persistence.jpa.JPADeployer prepare method javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services - 2.0.0.v20090713-r4647): org.eclipse.persistence.exceptions.EntityManagerSetupException Exception Description: Predeployment of PersistenceUnit [inherit] failed. Internal Exception: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.0.0.v20090713-r4647): org.eclipse.persistence.exceptions.ValidationException Exception Description: The @JoinColumns on the annotated element [method getAccounts] from the entity class [class ejb30.inherit.entity.Client] is incomplete. When the source entity class uses a composite primary key, a @JoinColumn must be specified for each join column using the @JoinColumns. Both the name and the referenceColumnName elements must be specified in each such @JoinColumn. at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:879) More information: The test source code are in the attachment of https://glassfish.dev.java.net/issues/show_bug.cgi?id=8734
Adjusting priority to P2 for 2.0 release as per http://wiki.eclipse.org/EclipseLink/Development/Bugs/Guidelines
>synopsis: Metamodel processing (fake PK field for MappedSuperclass) is leaking into real Metadata processing where a PK field already exists - we end up with a composite field (one real, one fake) on a OneToMany - fixing as part of bug# 266912
>There are several design issues I will need to answer as part of this fix http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_49:_20090723:_OneToMany_on_MappedSuperclass_with_JoinTable_causes_Composite_PK_in_error
>All new JPA 2.0 code modifications are categorized as enhancement until the JPA 2.0 RI is released.
>Starting reproduction
>Status: Failure reproduced, Solution fixes issue, patch and review pending http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_49:_20090723:_OneToMany_on_MappedSuperclass_with_JoinTable_causes_Composite_PK_in_error DI 49: 20090723: OneToMany on MappedSuperclass with JoinTable causes Composite PK in error Here, because of the fake RelationalDescriptor PK name for MappedSuperclasses added a month ago, there is a small issue arising for @OneToMany mappings on a MappedSuperclass with a JoinTable where the Fake PK is being added on top of the existing PK in the PK fields list. The result of this is that some of the Metamodel processing changes PK is leaking into the real world Metamodel processing code. > Reproduction: Adding the following unidirectional @OneToMany to a MappedSuperclass that defines an @Id like Person (inherited by the MappedSuperclass Corporation which is inherited by the Entity Manufacturer) @MappedSuperclass public abstract class Person { @Id @GeneratedValue(strategy=TABLE, generator="PERSON_MM_TABLE_GENERATOR") @TableGenerator( name="PERSON_MM_TABLE_GENERATOR", table="CMP3_MM_PERSON_SEQ", pkColumnName="SEQ_MM_NAME", valueColumnName="SEQ_MM_COUNT", pkColumnValue="CUST_MM_SEQ" ) // InstanceVariableAttributeAccessor testing @Column(name="PERSON_ID") private Integer id; // Verify special handling for PK for OneToMany (custom descriptor with fake PK name) // If a JoinTable with a JoinColumn is used - then we need a mappedBy on the inverse side here // However, bidirectional relationships are not allowed to MappedSuperclasses - as they have no identity // This @OneToMany implements internally as a @ManyToMany @OneToMany(fetch=EAGER, cascade=ALL) @JoinTable(name="CMP3_MM_HIST_EMPLOY", joinColumns = @JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID"), inverseJoinColumns = @JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID")) private Collection<Manufacturer> historicalEmployers = new HashSet<Manufacturer>(); >You will see the following exception Caused by: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.0.0.qualifier): org.eclipse.persistence.exceptions.ValidationException Exception Description: The @JoinColumns on the annotated element [field historicalEmployers] from the entity class [class org.eclipse.persistence.testing.models.jpa.metamodel.Person] is incomplete. When the source entity class uses a composite primary key, a @JoinColumn must be specified for each join column using the @JoinColumns. Both the name and the referencedColumnName elements must be specified in each such @JoinColumn. at org.eclipse.persistence.exceptions.ValidationException.incompleteJoinColumnsSpecified(ValidationException.java:1789) at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumnsAndValidate(MappingAccessor.java:483) at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.CollectionAccessor.processJoinTable(CollectionAccessor.java:578) at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.OneToManyAccessor.processManyToManyMapping(OneToManyAccessor.java:153) at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.OneToManyAccessor.process(OneToManyAccessor.java:108) at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.processRelationship(RelationshipAccessor.java:459) at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processRelationshipAccessors(MetadataProject.java:1046) at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1325) at org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor.processORMMetadata(MetadataProcessor.java:460) at org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.processORMetadata(PersistenceUnitProcessor.java:297) at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:849) >This validation exception is caused by 2 defined primary keys (one defined and the other added for MappedSuperclass processing). Because this particular MappedSuperclass already defines its' own @Id we should not add the fake proceessing Id here in this use case. descriptor MetadataDescriptor (id=75) m_descriptor RelationalDescriptor (id=74) primaryKeyFields ArrayList<E> (id=182) elementData Object[2] (id=195) [0] DatabaseField (id=197) name "__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=199) qualifiedName "__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=199) table DatabaseTable (id=200) type null typeName null [1] DatabaseField (id=186) name "PERSON_ID" (id=202) qualifiedName "__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME.PERSON_ID" (id=204) table DatabaseTable (id=151) type null typeName null modCount 2 size 2 m_primaryTable DatabaseTable (id=151) name "__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME" (id=157) qualifiedName "__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME" (id=157) >The 1st (Fake> key is added in stage 1 of metadata processing >Note: at the time this is a valid operation becuase the PK List is empty Thread [main] (Suspended (breakpoint at line 465 in MetadataProject)) MetadataProject.addMappedSuperclassAccessor(MetadataClass, MappedSuperclassAccessor) line: 465 EntityAccessor.addPotentialMappedSuperclass(MetadataClass) line: 199 EntityAccessor.discoverMappedSuperclassesAndInheritanceParents(boolean) line: 298 EntityAccessor.preProcess(boolean) line: 597 EntityAccessor.preProcess() line: 580 MetadataProject.processStage1() line: 1257 MetadataProcessor.processORMMetadata() line: 458 PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 297 EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 849 >The 2nd (Real) key is added in stage 2 of metadata processing Thread [main] (Suspended (breakpoint at line 458 in ClassDescriptor)) RelationalDescriptor(ClassDescriptor).addPrimaryKeyField(DatabaseField) line: 458 MetadataDescriptor.addPrimaryKeyField(DatabaseField, MappingAccessor) line: 331 IdAccessor.process() line: 82 MetadataDescriptor.processAccessors(MetadataDescriptor) line: 1242 MappedSuperclassAccessor(ClassAccessor).processAccessors() line: 787 MappedSuperclassAccessor.processMetamodelDescriptor() line: 883 MetadataProject.processStage2() line: 1285 MetadataProcessor.processORMMetadata() line: 459 PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 297 EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 849 > Q1) Do we need the inverseJoinColumns annotation It would seem not, because bidirectional (using mappedBy) is not allowed on a mappedSuperclass > Q2) How do we define the join table columns All the concrete entities must then define the @OneToMany fully with both joinColumns and inverseJoinColums >Solution: The simple solution would be - when there is an existing key, do not add another key. However, the real key is added after we have customized the descriptor by adding a fake key to allow MappedSuperclass descriptors to process. ----------------------------- >The full solution is to check the PK List for our custom MappedSuperclass fake key and remove it before adding the real one. >An even better solution would be to not add the "fake" key by checking that we "will" be adding a PK in the future - by possibly parsing the mappings in the stage 1 call Note: The following use cases are covered by this operation. UC1: The 2nd key is another fake key - we end up with a single one which is ok UC2: The 2nd key is a real key - we delete the fake one only UC3: The nth key is part of a composite key - we do not affect any existing composite keys already in the PK List Note: The following use cases may not be covered UCA: Two threads attempt to modify the PK List in an un-Synchronized state This UC may not be valid >Code: Rough POC MetadataDescriptor.java public void addPrimaryKeyField(DatabaseField field, MappingAccessor accessor) { // New code start /** * 266912: For MappedSuperclass processing, if a fake PK already exists, * delete the existing transient key uses solely by non-relational metamodel processing * before adding the real one. * In the case that this key is part of a real composite key, * the pk name check will still allow multiple keys to be added. */ // Iterate the existing fields and remove the first fake PK used for metamodel processing. List<DatabaseField> pkList = m_descriptor.getPrimaryKeyFields(); // This iteration is self modifying so keep it out of an Iterator for(int i=0; i<pkList.size(); i++) { if(pkList.get(i).getName().equals(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME)) { pkList.remove(i); // exit loop break; //i=pkList.size() + 1; } } // New code end m_descriptor.addPrimaryKeyField(field); // Store the primary primary key mappings based on their field name. m_primaryKeyAccessors.put(field.getName(), accessor); } >I will post a patch shortly after cleaning up the metadata code and running regression tests as well as adding a test that fails on a non-existent compositePK before the change.
Created attachment 144256 [details] Handle PK Field for MappedSuperclass - Option 1 - add in stage 1 and delete later in stage 2 - for reference only >See option 1 (not recommended) http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#Implementation_Option_1:_Remove_pseudo_PK_field_from_relational_descriptor_later_in_stage_2
Created attachment 144257 [details] Handle PK Field for MappedSuperclass - Option 3 - search for future IdAccessor and add only if not found in stage 1 - recommended >See option 3 (recommended) http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#Implementation_Option_3:_Determine_if_there_will_be_1_or_more_pk_fields_before_adding_pseudo_PK_field_in_stage_1
Created attachment 144275 [details] Handle PK Field for MappedSuperclass - Option 3b - call hasIdAccessor to check for future IdAccessor and add only if not found in stage 1 - no Iteration - reviewed by Guy - recommended
>See rev# 4845 Reviewed by: Guy (with refactor assistance for option 3) http://fisheye2.atlassian.com/changelog/eclipselink/?cs=4845
Mass update to change fixed in target.
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink