Search code examples
hibernatejpaeclipselink

Writability mapping of @OneToMany @MapKeyColumn relationship assumed to be on @Id field


I have a game entity, that references two scores via Map + @MapKeyColumn, where one entity is for the home team and one for the away team:

enter image description here

I decided to use a Boolean column is_home for this matter. I mapped the @MapKeyColumn relationship without insertable = false, updatable = false, making that annotation writable regarding the is_home column.

Since there may be only one writable field/annotation per column, I had to add insertable = false, updatable = false to the @Id mapping on the Score entity, field home, see below.

Game:

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

    @Id
    @Column
    private Integer id;

    @Basic(optional = false)
    @Column(name = "scheduled_tipoff")
    private LocalDateTime scheduledTipoff;

    @OneToMany(mappedBy = "game")
    @MapKeyColumn(name = "is_home")                    <- writable
    private Map<Boolean, Score> scores;

    ...
}

Score:

@Entity
@Table(name = "Scores")
@IdClass(ScoreId.class)
public class Score implements Serializable
{
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "is_home", insertable = false, updatable = false)          <- not writable
    private Boolean home;

    @Basic
    @Column(name = "final_score")
    private Integer finalScore;

    @Id
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "game_id")
    private Game game;

    ...
}

ScoreId:

public class ScoreId implements Serializable
{
    private static final long serialVersionUID = 1L;

    private Integer game;

    private Boolean home;

    ...
}

This results in an exception using EclipseLink:

Exception

    at java.lang.Thread.run(Thread.java:748)
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-28013] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Unable to deploy PersistenceUnit [BBStatsPU] in invalid state [DeployFailed].
Internal Exception: javax.persistence.PersistenceException: 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 [Scores.is_home].
Descriptor: RelationalDescriptor(net.bbstats.entity.Score --> [DatabaseTable(Scores)])

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

    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:634)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate.getAbstractSession(EntityManagerFactoryDelegate.java:222)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate.createEntityManagerImpl(EntityManagerFactoryDelegate.java:330)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManagerImpl(EntityManagerFactoryImpl.java:350)
    at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:313)
    at org.jboss.as.jpa.container.TransactionScopedEntityManager.createEntityManager(TransactionScopedEntityManager.java:187)
    at org.jboss.as.jpa.container.TransactionScopedEntityManager.getOrCreateTransactionScopedEntityManager(TransactionScopedEntityManager.java:157)
    at org.jboss.as.jpa.container.TransactionScopedEntityManager.getEntityManager(TransactionScopedEntityManager.java:87)
    at org.jboss.as.jpa.container.AbstractEntityManager.createNamedQuery(AbstractEntityManager.java:101)
    at net.bbstats.framework.service.Repository.findByNamedQuery(Repository.java:173)
    at net.bbstats.framework.service.BaseEntityService.findByNamedQuery(BaseEntityService.java:467)
    at net.bbstats.framework.service.BaseEntityService.findByNamedQuery(BaseEntityService.java:453)
    at net.bbstats.framework.service.BaseEntityService.findByNamedQuery(BaseEntityService.java:429)
    at net.bbstats.framework.service.BaseEntityService.findAllWithFetchGraph(BaseEntityService.java:385)
    ... 194 more
Caused by: Exception [EclipseLink-28013] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Unable to deploy PersistenceUnit [BBStatsPU] in invalid state [DeployFailed].
Internal Exception: javax.persistence.PersistenceException: 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 [Scores.is_home].
Descriptor: RelationalDescriptor(net.bbstats.entity.Score --> [DatabaseTable(Scores)])

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

    at org.eclipse.persistence.exceptions.EntityManagerSetupException.cannotDeployWithoutPredeploy(EntityManagerSetupException.java:193)
    ... 208 more
Caused by: javax.persistence.PersistenceException: 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 [Scores.is_home].
Descriptor: RelationalDescriptor(net.bbstats.entity.Score --> [DatabaseTable(Scores)])

QUESTION

I understand that JPA implementations might assume every @Id field to automatically be writable, but I wonder: does the JPA specify this anywhere?

I currently don't see why this writability assumption is being made.

If it's not specified, is this a bug then?


Solution

  • You can never change an ID value in an entity, so it is pretty much implied that updatable=false; doing so is creating a new identity and needs to be treated as an entirely different entity.

    The error message is a hold over from pre-JPA. EclipseLink tied insertable to updatable in their mappings to match the preexisting read-only concept it already had. EclipseLink (and the definition of an entity within JPA) requires that if an entity is to ever be inserted, it has an ID value it has control over within the entity - in this case, you are attempting to have the ID be set by some other, external mapping (the 'game.scores' relationship) which doesn't fit what it means to be an entity. This makes it more an embedded object.