persistence@glassfish.java.net

Re: "Invalid composite primary key specification"

From: Marina Vatkina <Marina.Vatkina_at_Sun.COM>
Date: Mon, 06 Aug 2007 20:52:03 -0700

Jeffrey,

You @JoinColumn should reference the PK on the ENTITY table, which is most
probably just ENTITY_NAME.

Now, if you use TopLink to generate the tables, you do not need the mapping
annotation at all - TopLink will do the right thing for you.

Regards,
-marina

Jeffrey Blattman wrote:
> after playing with this some, i found the following works ... p.s., note
> that i have an entity class w/ the name "Entity" ... so don't let that
> confuse you :)
>
> /_at_IdClass(com.sun.portal.pom.PreferenceId.class)
> @javax.persistence.Entity
> public class Preference implements Serializable {
> @Id
> @Column(name="PREFERENCE_NAME", nullable=false)
> private String name;
>
> @Id
> @Column(nullable = false, name="ENTITY_ID")
> private String entityId = null;
>
> @ManyToOne(optional = false, cascade = CascadeType.ALL)
> * @JoinColumns({
> @JoinColumn(insertable=false, updatable=false, nullable=false,
> name="ENTITY_ID", referencedColumnName="ENTITY_NAME"),
> @JoinColumn(insertable=false, updatable=false, nullable=false,
> name="PREFERENCE_NAME", referencedColumnName="PREFERENCE_NAME")
> })
> * private Entity entity;/
>
> i am however seeing this at runtime ...
>
> /[TopLink Warning]: 2007.08.06
> 03:37:48.226--ServerSession(4926903)--Exception [TOPLINK-4002] (Oracle
> TopLink Essentials - 2.0 (Build 56 (07/18/2007))):
> oracle.toplink.essentials.exceptions.DatabaseException
> Internal Exception: java.sql.SQLException: Constraint
> 'PREFERENCEENTITYID' is invalid: there is no unique or primary key
> constraint on table 'APP.ENTITY' that matches the number and types of
> the columns in the foreign key.
> Error Code: -1
> Call: ALTER TABLE PREFERENCE ADD CONSTRAINT PREFERENCEENTITYID FOREIGN
> KEY (ENTITY_ID, PREFERENCE_NAME) REFERENCES ENTITY (ENTITY_NAME,
> PREFERENCE_NAME)
> /
> not sure if it is related?
>
> Jeffrey Blattman wrote:
>
>> hi again markus,
>>
>> Markus Fuchs wrote:
>>
>>> Hi Jeffrey,
>>>
>>> Jeffrey Blattman wrote:
>>>
>>>> thanks markus,
>>>>
>>>> this gets me further. the complexity is this, i essentially have
>>>> three entities
>>>>
>>>> 1. Page
>>>> 2. Portlet
>>>> 3. Preferenence
>>>>
>>>> simple right? a page contains a portlet contains preferences. a
>>>> delete / update / etc of Page should affect all its portlets, and a
>>>> delete / update / etc of a portlet should affect its preferences.
>>>> each of these has a name.
>>>>
>>>> to model the containment, Portlet's ID is the page name + it's own
>>>> name (PortletId). that one is simple. however, when we go a level
>>>> deeper to Preference, Preference's ID is the Portlet's ID + it's own
>>>> name (PreferenceId).
>>>>
>>>> how do i model that? PortletId is *not* a simple type, so I can't
>>>> use it as an @Id field in Preference. how do i define Preference's
>>>> ID fields?
>>>>
>>> Id fields:
>>>
>>> In page: pageId
>>> In Portlet: pageId & portletId, IdClass has pageId & portletId fields
>>> In preference: pageId & portletId & preferenceId, IdClass has pageId
>>> & portletId & preferenceId fields
>>
>> so the general ID is that each "contained" entity needs to have simple
>> typed ID fields for every containing entity class.
>>
>> this is sort of confusing, because it means that a Preference needs to
>> know about pages, when ideally all it would care about is the
>> containing entity above it. but i think i get it. for example, as long
>> as a Portlet has a unique ID, why does a Preference needs to know or
>> care about the Page? the Portlet has already encapsulated the Page ID
>> into it's ID, why does it need to be exposed to the Preference?
>>
>>>
>>>> when i just make it a @JoinColumn instead,
>>>>
>>>> / @ManyToOne(optional = false, cascade = CascadeType.ALL)
>>>> @Column(insertable=false, updatable=false, nullable=false,
>>>> name="PORTLET_ID")
>>>> @XmlTransient
>>>> private Portlet portlet;
>>>>
>>>> /i get this ...
>>>>
>>>> /Exception Description: The @JoinColumns on the annotated element
>>>> [private com.sun.portal.pom.Entity
>>>> com.sun.portal.pom.Preference.entity] from the entity class [class
>>>> com.sun.portal.pom.Preference] is incomplete. W*hen 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.
>>>> /
>>>
>>> Looks like you used /_at_JoinColumn*s*. Please check.
>>>
>>> /
>>
>> no, i didn't. the message is telling me that i need to specify >1
>> @JoinColumn's, and use a @JoinColumns to contain them. i think.
>>
>> i eventually got something that appears to work, on TopLink. i had to
>> remove the @JoinColumn from the @ManyToOne in Preference and Portlet.
>> this is course means i'm missing insertable and updatable = false
>> settings.
>>
>>>> what am i supposed to use for the join columns? since PortletId
>>>> encapsulates both the Page name and Portlet name, why does it want
>>>> to to specify more than that?
>>>>
>>>> i feel like i must be making this more complicated than it needs to
>>>> be. all i want is to model some objects containing other objects
>>>> where all operations cascade.
>>>>
>>> This should be doable. First start with the Page and Portlet classes.
>>> When this works, add Preferences and model the relationships and
>>> IdClass accordingly.
>>>
>> i know the 2-level case works. the 3 level case is giving me trouble
>> because i have a composite ID that has (at least conceptually) another
>> composite ID as a field.
>>
>>> -- markus.
>>>
>>>> ?
>>>>
>>>> Markus Fuchs wrote:
>>>>
>>>>> Hi Jeffrey,
>>>>>
>>>>> Entities should not use relationship fields as part of the primary
>>>>> key. The JPA spec says (please see 2.1.4.):
>>>>>
>>>>> The primary key (or field or property of a composite primary key)
>>>>> should be one of the following types:
>>>>> any Java primitive type; any primitive wrapper type;
>>>>> java.lang.String; java.util.Date;
>>>>> java.sql.Date.
>>>>>
>>>>> Try changing:
>>>>>
>>>>>public abstract class Entity implements Serializable {
>>>>> @Id
>>>>> @Column (nullable = false, name="ENTITY_NAME")
>>>>> @XmlAttribute
>>>>> private String name;
>>>>>
>>>>> @Id
>>>>> @Column (nullable = false, name="PAGE_ID")
>>>>> private String pageId;
>>>>>
>>>>>
>>>>> @Column (nullable = false, name="PAGE_ID")
>>>>> @ManyToOne (cascade = CascadeType.ALL, insertable=false, updatable=false)
>>>>> private Page page = null;
>>>>>...
>>>>>}
>>>>>
>>>>>public class EntityId implements Serializable {
>>>>> private String name;
>>>>> private String pageId;
>>>>>...
>>>>>}
>>>>>
>>>>>
>>>>>
>>>>> -- markus.
>>>>>
>>>>> Entity
>>>>> Jeffrey Blattman wrote:
>>>>>
>>>>>> hi,
>>>>>>
>>>>>> 2.0-b56, i'm seeing this ...
>>>>>>
>>>>>> /Exception Description: Invalid composite primary key
>>>>>> specification. The names of the primary key fields or properties
>>>>>> in the primary key class [com.sun.portal.pom.EntityId] and those
>>>>>> of the entity bean class [class com.sun.portal.pom.Entity] must
>>>>>> correspond and their types must be the same. Also, ensure that you
>>>>>> have specified id elements for the corresponding attributes in XML
>>>>>> and/or an @Id on the corresponding fields or properties of the
>>>>>> entity class.
>>>>>> /
>>>>>> i've attached the two classes. Entity has IDs /name/ and /page/,
>>>>>> and EntityId has fields /name/ and /page/. same name, same type. i
>>>>>> can zip the maven2 project so the problem can be reproduced, if
>>>>>> that helps.
>>>>>>
>>>>>> what am i missing?
>>>>>>
>>>>>> thanks.
>>>>>