Kawu
Kawu

Reputation: 14003

Embedded ID mapping fails with DescriptorException: There should be one non-read-only mapping defined for the primary key field (EclipseLink)

I have the following very simple design:

enter image description here

A club has multiple teams, and each team having a team type code (a gender - age group code) plus an ordinal (team) number, e.g. MALE OVER 20 would be "MO20", with the ordinal number representing the 1st, 2nd etc. team for that age group. This is the PK for the Teams. You get the point.

Club mappings:

@Entity
@Table(name = "Clubs")
public class Club implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Id
    @Column
    private Integer id;

    @Basic(optional = false)
    @Column
    private String name;

    @Basic(optional = false)
    @Column
    private String code;

    @OneToMany(mappedBy = "club")
    private List<Team> teams;

    ...
}

Team mappings:

@Entity
@Table(name = "Teams")
public class Team implements Serializable
{
    private static final long serialVersionUID = 1L;

    @EmbeddedId
    private TeamPk embeddedId;

    @MapsId("clubId")
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "club_id", insertable = false, updatable = false)
    private Club club;

    ...
}

@Embeddable
public class TeamPk implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Column(name = "club_id")
    private Integer clubId;

    @Column(name = "team_type_code")
    private String teamTypeCode;

    @Column(name = "ordinal_nbr")
    private Integer ordinalNbr;

    ...
}

Exception:

Caused by: Exception [EclipseLink-28019] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Deployment of PersistenceUnit [BBStatsPU] failed. Close all factories for this PersistenceUnit.
Internal Exception: Exception [EclipseLink-0] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.IntegrityException
Descriptor Exceptions: 
---------------------------------------------------------

Exception [EclipseLink-46] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: There should be one non-read-only mapping defined for the primary key field [Teams.club_id].
Descriptor: RelationalDescriptor(net.bbstatstest.i265.entity.Team --> [DatabaseTable(Teams)])

Runtime Exceptions: 
---------------------------------------------------------

    at org.eclipse.persistence.exceptions.EntityManagerSetupException.deployFailed(EntityManagerSetupException.java:241)
    ... 182 more
Caused by: Exception [EclipseLink-0] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.IntegrityException
Descriptor Exceptions: 
---------------------------------------------------------

Exception [EclipseLink-46] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: There should be one non-read-only mapping defined for the primary key field [Teams.club_id].
Descriptor: RelationalDescriptor(net.bbstatstest.i265.entity.Team --> [DatabaseTable(Teams)])

Runtime Exceptions: 
---------------------------------------------------------

    at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:762)
    at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:698)
    at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:629)
    at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.postConnectDatasource(DatabaseSessionImpl.java:868)
    at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.loginAndDetectDatasource(DatabaseSessionImpl.java:811)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:256)
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:772)
    ... 180 more

EclipseLink complains about the column mappings for Teams.club_id:

There should be one non-read-only mapping defined for the primary key field [Teams.club_id].

But why?

The embedded ID field or rather the ID class in place has @Column on the clubId field without insertable = true, updatable = true, so it defaults to being writable, at least that's what the docs say: https://javaee.github.io/javaee-spec/javadocs/javax/persistence/Column.html#insertable--

Hence all other club_id mappings must be insertable = true, updatable = true to be read-only, like the Team.club relationship.

QUESTION:

What's wrong here?

Note, that the whole thing works when switching the insertable = ..., updatable = ... between Team.club and TeamPk.clubId.

Why is EclipseLink choking here? Looks like a bug to me.

PS: I'm using EclipseLink 2.7.7. (hibernate tag only added to attract more attention)

Upvotes: 0

Views: 414

Answers (1)

crizzis
crizzis

Reputation: 10716

By putting @MapsId("clubId") on the association, you're saying 'clubId does not provide its own mapping. Instead, the mapping on this property here should be used to map the clubId property'.

This means that when persisting the entity, Eclipselink will use Team.club's id to populate the club_id join column, and then, it will use the value of that join column, to populate the Java property Team.embeddedId.clubId, whenever the entity is fetched/refreshed. Whatever mapping you put on Team.embeddedId.clubId will be ignored.

The error comes from the fact that Eclipselink does not consider club_id to be mapped twice. Instead, only the mapping on Team.club is taken into account. Being the only mapping for a primary key property, it may not be insertable = false, updatable = false.

The solution is simple:

  1. If you want Team.club to control the value of the club_id column, drop the insertable = false, updatable = false
  2. Conversely, if you want Team.embeddedId.clubId to control club_id, drop @MapsId altogether

Upvotes: 1

Related Questions