【问题标题】:Writability mapping of @OneToMany @MapKeyColumn relationship assumed to be on @Id field@OneToMany @MapKeyColumn 关系的可写性映射假定在 @Id 字段上
【发布时间】:2020-10-26 11:15:29
【问题描述】:

我有一个游戏实体,通过Map + @MapKeyColumn 引用两个分数,其中一个实体用于主队,一个用于客队:

我决定为此使用Booleanis_home。我映射了@MapKeyColumn 关系没有 insertable = false, updatable = false,使该注释可写关于is_home 列。

由于每列可能只有一个可写字段/注释,我不得不将insertable = false, updatable = false 添加到Score 实体字段home 上的@Id 映射,见下文。

游戏:

@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;

    ...
}

得分:

@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;

    ...
}

这会导致使用 EclipseLink 的异常:

例外

    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)])

问题

我知道 JPA 实现可能假设每个 @Id 字段都自动可写,但我想知道:JPA 是否在任何地方指定了这一点?

我目前不明白为什么要做出这种可写性假设。

如果没有指定,那么这是一个错误吗?

【问题讨论】:

  • 您永远无法更改实体中的 ID 值,因此几乎暗示了可更新 = 假,只是 eclipseLink 在其映射中将可插入与可更新绑定以匹配先前存在的只读概念它适用于 JPA。 JPA 要求,如果要插入一个实体,它必须有一个它可以在实体内控制的 ID 值 - 在这种情况下,您试图让 ID 由其他一些外部映射(game.scores 关系) 这不符合成为实体的含义。这使它更像是一个嵌入式对象。
  • 您介意将其发布为答案吗?它很可能不会变得更好......

标签: hibernate jpa eclipselink


【解决方案1】:

您永远无法更改实体中的 ID 值,因此几乎暗示了 updatable=false;这样做是在创建一个新的身份,需要被视为一个完全不同的实体。

错误消息是 JPA 之前的保留。 EclipseLink 在其映射中将可插入与可更新绑定,以匹配它已经拥有的预先存在的只读概念。 EclipseLink(以及 JPA 中实体的定义)要求如果要插入一个实体,它有一个它可以在实体内控制的 ID 值 - 在这种情况下,您试图让某个 ID 设置其他外部映射(“game.scores”关系)不符合实体的含义。这使它更像是一个嵌入式对象。

【讨论】:

    【解决方案2】:

    在将cascade = CascadeType.ALL 添加到您的@OneToMany 映射后,我能够成功地保存具有多个分数的Game 实体:

    @OneToMany(mappedBy = "game", cascade = CascadeType.ALL) // added cascading
    @MapKeyColumn(name = "is_home")
    private Map<Boolean, Score> scores;
    

    例子:

    Game game = new Game();
    game.setId(4);
    // ...
          
    Score score1 = new Score();
    score1.setGame(game);
    score1.setHome(true);
    score1.setFinalScore(25);
          
    Score score2 = new Score();
    score2.setGame(game);
    score2.setHome(false);
    score2.setFinalScore(26);
          
    game.getScores().put(score1.getHome(), score1);
    game.getScores().put(score2.getHome(), score2);
          
    entityManager.persist(game);
    

    生成了以下 sql:

    23:27:50,396 DEBUG SQL:127 - 
    /* insert com.my.entities.Game */
    insert into TEST_SCHEMA.TST_GAMES (gm_id) values (?)
    23:27:50,408 DEBUG SQL:127 - 
    /* insert com.my.entities.Score */
    insert into TEST_SCHEMA.TST_SCORES (final_score, is_home, game_id) 
    values (?, ?, ?)
    
    23:27:50,415 DEBUG SQL:127 - 
    /* insert com.my.entities.Score */
    insert into TEST_SCHEMA.TST_SCORES (final_score, is_home, game_id) 
    values (?, ?, ?)
    
    23:27:50,440 DEBUG SQL:127 - 
    update TEST_SCHEMA.TST_SCORES  
    set sc_is_home=? 
    where sc_is_home=? and sc_game_id=?
    
    23:27:50,443 DEBUG SQL:127 - 
    update TEST_SCHEMA.TST_SCORES  
    set sc_is_home=? 
    where sc_is_home=? and sc_game_id=?
    

    使用 Hibernate 5.4.10.Final 进行了测试。 (不添加上面提到的级联休眠也会产生异常)

    【讨论】:

    • 我知道我可以像这样映射它,谢谢。但这不是我要问的。问题是,任何 JPA 提供者期望 @Id 字段可写的假设是否错误。顺便说一句,这也适用于 MapKey 注释。
    • 您没有提供导致您的问题异常中提到的确切情况。请检查我更新的答案。您是否使用相同的方法进行Game 保存?如果是这样,添加 cascade = CascadeType.ALL 可能会有所帮助。
    • WildFly 20 在部署/启动应用程序时发生异常。所以不涉及读取和存储。唯一的例外是 EclipseLink。我只是添加了hibernate标签以吸引更多关注。
    猜你喜欢
    • 2012-04-14
    • 1970-01-01
    • 2012-02-08
    • 2013-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多