Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 336122

Summary: ValidationException thrown for JoinColumns on OneToMany with composite primary key
Product: z_Archived Reporter: Chris Delahunt <christopher.delahunt>
Component: EclipselinkAssignee: Nobody - feel free to take it <nobody>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: tht.infernozeus
Version: unspecified   
Target Milestone: ---   
Hardware: All   
OS: All   
Whiteboard:
Attachments:
Description Flags
test case and potential fix none

Description Chris Delahunt CLA 2011-02-02 13:11:57 EST
The unidirectional OneToMany mapping will throw an exception unless there are the same number of join columns defined as there are pk fields in the target.  This is wrong, as it should only use the source entity's primary key fields.  
ie:
@Entity
@IdClass(org.eclipse.persistence.testing.models.jpa.advanced.compositepk.OfficePK.class)
public class Office {

    @Id
    protected int id;
    
    @Id
    protected String location;
            @OneToMany
    @JoinColumns({
        @JoinColumn(name="OFFICE_ID", referencedColumnName="ID"),
        @JoinColumn(name="OFFICE_LOC", referencedColumnName="LOCATION")
    })
    private Collection<Cubicle> cubicles;

If the Cubicle class does not have a 2 field composite pk, this results in:
 
Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.0.1.v20100213-r6600): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The @JoinColumns on the annotated element [cubicles] from the entity class [class Office ] 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
Comment 1 Chris Delahunt CLA 2011-02-02 13:22:57 EST
Created attachment 188177 [details]
test case and potential fix
Comment 2 Chris Delahunt CLA 2011-02-03 10:45:55 EST
fix checked into 2.3 (trunk) revision 8922
Comment 3 Ben Fox-Moore CLA 2011-07-26 08:39:56 EDT
I'm still having this issue in Eclipselink 2.3.0.

I have two tables (using generic names for the example):

Entity A
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| idRef       | int(11)      | NO   | UNI | NULL    | auto_increment |
| name        | varchar(45)  | NO   | PRI | NULL    |                |
| parent      | varchar(45)  | NO   | PRI | NULL    |                |
| description | varchar(255) | YES  |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

Entity B
+------------------+--------------+------+-----+---------+-------+
| Field            | Type         | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+-------+
| idEntityA        | int(11)      | NO   | PRI | NULL    |       |
| value            | varchar(255) | YES  |     | NULL    |       |
+------------------+--------------+------+-----+---------+-------+

As you can see, EntityB references EntityA via its idRef column which is unique, auto increments, but isn't the primary key. EntityA's primary key is the combination of name and parent.

If I create the entity classes for these two tables, I end up with

EntityA.java:

@Entity
@Table(name="entitya")
public class EntityA implements Serializable {
	private static final long serialVersionUID = 1L;
	private EntityAPK id;
	private int idRef;
	private String name;
	private String parent;
	private String description;
        public EntityA() {    }
	@EmbeddedId
	public EntityAPK getId() {
		return this.id;
	}
	public void setId(EntityAPK id) {
		this.id = id;
	}
	public int getIdRef() {
		return this.idRef;
	}
	public void setIdRef(int idRef) {
		this.idRef = idRef;
	}
	@MapsId("name")
	public String getName() {
		return this.name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@MapsId("parent")
	public String getParent() {
		return this.parent;
	}
	public void setParent(String parent) {
		this.parent = parent;
	}
	public String getDescription() {
		return this.description;
	}
	public void setDescription(String description) {
		this.description = description;
	}	
}


EntityB.java:

@Entity
@Table(name="entityb")
public class EntityB implements Serializable {
	private EntityA entitya;
	private String value;
        public EntityB() {
        }
	public String getValue() {
		return this.value;
	}
	public void setValue(String value) {
		this.value = value;
	}
	//bi-directional many-to-one association to Suite
        @ManyToOne
        @JoinColumns({
		@JoinColumn(name="idEntityA", referencedColumnName="idRef")
	})
	public Suite getEntityA() {
		return this.entitya;
	}
	public void setEntityA(EntityA entitya) {
		this.entitya = entitya;
	}	
}

but it still gives me the same error message:

The @JoinColumns on the annotated element [method getEntityA] from the entity class [class EntityB] 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.

but if I duplicate the JoinColumn annotation:
@JoinColumn(name="idEntityA", referencedColumnName="idRef")
it fixes the issue.
Comment 4 Chris Delahunt CLA 2011-07-26 11:08:02 EDT
The bug description covers a specific situation - A unidirection OneToMany where the join columns are specified.  The problem was that these join columns are foreign keys that exist in the target table, and so should reflect the ID fields of the Entity where the mapping was defined.  The bug was that this was not occuring - validation was using the target entities ID fields instead.  

In the example relationship, the OFFICE_ID and OFFICE_LOC feilds exist in the Cubicle table, and are foreign keys to the Office table.  If Cubicle only has a single ID field, a validation exception would be thrown when it should not - Cubicle's ID fields should not affect this mapping.  


JPA requires foriegn key fields to point to ID fields, so this validation is correct in the EntityA<->EntityB relationship defined.  EclipseLink has support for using non-id fields in relationships, but it is not advised as it may decrease performance as caching is based on primary keys.  A better option is to use EntityA's idRef as the Entity ID as it is a natural primary key anyway.  

If this is not possible, you could mark the mappings as transient and then add them in through a customizer and EclipseLink native API.
Comment 5 Eclipse Webmaster CLA 2022-06-09 10:22:52 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink