如何使用 JPA 2.1 中设置的非唯一值映射 Map<String, Entity>?

How to map Map<String, Entity> with a non-unique value set in JPA 2.1?

我正在尝试使用 JPA 2.1 中设置的非唯一值映射 Map<String, Entity>。目前这种关系可以是单向的。关于如何用唯一值集映射 Map<String, Entity> 有数万亿种解释,但这个问题与此无关。

我有一个

@Entity
public class Entity2 implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private Long id;

    [constructors, getter and setter]

@Entity
public class Entity1 implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private Long id;
    @OneToMany
    @MapKey(name = "id")
    private Map<String, Entity2> entity2Map = new HashMap<>();

我的 persistence.xml 包含

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="richtercloud_jpa-map-with-non-unique-value-set_war_1.0-SNAPSHOTPU" transaction-type="JTA">
    <jta-data-source>jdbc/sample</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="javax.persistence.schema-generation.database.action" value="create"/>
    </properties>
  </persistence-unit>
</persistence>

导致

Entity2 entity2 = new Entity2(1l);
Entity1 entity1 = new Entity1(2l);
entity1.getEntity2Map().put("a", entity2);
entity1.getEntity2Map().put("b", entity2);
entityManager.persist(entity2);
entityManager.persist(entity1);

由于(我拼命尝试将输出切换为英文,但似乎没有弄清楚;实质是两个 Entity2 引用导致违反主键约束):

Caused by: javax.transaction.RollbackException: Transaction marked for rollback.
    at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:490)
    at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:878)
    at com.sun.ejb.containers.EJBContainerTransactionManager.completeNewTx(EJBContainerTransactionManager.java:721)
    ... 64 more
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.4.qualifier): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Die Anweisung wurde abgebrochen, weil sie in einer für 'ENTITY1_ENTITY2' definierten Vorgabe für einen eindeutigen oder Primärschlüssel-Constraint bzw. für einen von 'SQL180327114054560' identifizierten und eindeutigen Index zu einem duplizierten Schlüsselwert geführt hätte.
Error Code: 20000
Call: INSERT INTO ENTITY1_ENTITY2 (entity2Map_ID, Entity1_ID) VALUES (?, ?)
    bind => [2 parameters bound]
Query: DataModifyQuery(name="entity2Map" sql="INSERT INTO ENTITY1_ENTITY2 (entity2Map_ID, Entity1_ID) VALUES (?, ?)")
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.handleException(EntityManagerSetupImpl.java:745)
    at org.eclipse.persistence.transaction.AbstractSynchronizationListener.handleException(AbstractSynchronizationListener.java:275)
    at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:170)
    at org.eclipse.persistence.transaction.JTASynchronizationListener.beforeCompletion(JTASynchronizationListener.java:68)
    at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:452)
    ... 66 more
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.4.qualifier): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Die Anweisung wurde abgebrochen, weil sie in einer für 'ENTITY1_ENTITY2' definierten Vorgabe für einen eindeutigen oder Primärschlüssel-Constraint bzw. für einen von 'SQL180327114054560' identifizierten und eindeutigen Index zu einem duplizierten Schlüsselwert geführt hätte.
Error Code: 20000
Call: INSERT INTO ENTITY1_ENTITY2 (entity2Map_ID, Entity1_ID) VALUES (?, ?)
    bind => [2 parameters bound]
Query: DataModifyQuery(name="entity2Map" sql="INSERT INTO ENTITY1_ENTITY2 (entity2Map_ID, Entity1_ID) VALUES (?, ?)")
    at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:331)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:902)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:964)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:633)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:560)
    at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2056)
    at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:306)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeNoSelectCall(DatasourceCallQueryMechanism.java:271)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeNoSelect(DatasourceCallQueryMechanism.java:251)
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.executeNoSelect(StatementQueryMechanism.java:118)
    at org.eclipse.persistence.queries.DataModifyQuery.executeDatabaseQuery(DataModifyQuery.java:85)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
    at org.eclipse.persistence.internal.sessions.AbstractSession.internalExecuteQuery(AbstractSession.java:3271)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
    at org.eclipse.persistence.mappings.ManyToManyMapping.insertIntoRelationTable(ManyToManyMapping.java:672)
    at org.eclipse.persistence.mappings.ManyToManyMapping.performDataModificationEvent(ManyToManyMapping.java:831)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:160)
    at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4264)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3168)
    at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:355)
    at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:158)
    ... 68 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: Die Anweisung wurde abgebrochen, weil sie in einer für 'ENTITY1_ENTITY2' definierten Vorgabe für einen eindeutigen oder Primärschlüssel-Constraint bzw. für einen von 'SQL180327114054560' identifizierten und eindeutigen Index zu einem duplizierten Schlüsselwert geführt hätte.
    at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(Unknown Source)
    at org.apache.derby.client.am.SqlException.getSQLException(Unknown Source)
    at org.apache.derby.client.am.PreparedStatement.executeUpdate(Unknown Source)
    at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:125)
    at sun.reflect.GeneratedMethodAccessor117.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.gjc.spi.jdbc40.ProfiledConnectionWrapper40.invoke(ProfiledConnectionWrapper40.java:437)
    at com.sun.proxy.$Proxy341.executeUpdate(Unknown Source)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:892)
    ... 92 more
Caused by: org.apache.derby.client.am.SqlException: Die Anweisung wurde abgebrochen, weil sie in einer für 'ENTITY1_ENTITY2' definierten Vorgabe für einen eindeutigen oder Primärschlüssel-Constraint bzw. für einen von 'SQL180327114054560' identifizierten und eindeutigen Index zu einem duplizierten Schlüsselwert geführt hätte.
    at org.apache.derby.client.am.Statement.completeExecute(Unknown Source)
    at org.apache.derby.client.net.NetStatementReply.parseEXCSQLSTTreply(Unknown Source)
    at org.apache.derby.client.net.NetStatementReply.readExecute(Unknown Source)
    at org.apache.derby.client.net.StatementReply.readExecute(Unknown Source)
    at org.apache.derby.client.net.NetPreparedStatement.readExecute_(Unknown Source)
    at org.apache.derby.client.am.PreparedStatement.readExecute(Unknown Source)
    at org.apache.derby.client.am.PreparedStatement.flowExecute(Unknown Source)
    at org.apache.derby.client.am.PreparedStatement.executeUpdateX(Unknown Source)
    ... 100 more

在基于 JTA 的 Java EE 项目中使用 EclipseLink 和 Apache Derby 部署到 Payara 4.1.2.181。

@MapKeyColumn 是基本类型之间的映射,例如Map<String, String>.

我没有尝试 @CollectionTable 因为它

Specifies the table that is used for the mapping of collections of basic or embeddable types.

万一我想不通,我可能会将 Map<String, Entity> 转换为 Set<WrappingEntity>,其中 WrappingEntity 包含一个 ID、字符串值和 Entity2 参考可能更有效,但我也想弄清楚这一点。该方法通常应该有效,因为带有 Entity1 的 id、字符串映射键和映射 Entity2 值的 table 可以使用 Entity1 的 id 和字符串映射键作为主键。

可以在 https://gitlab.com/krichter/jpa-map-with-non-unique-value-set.

找到 SSCCE

我不确定 JPA 2.1 规范是否提到实体作为 to-many 关联中的 non-unique 值,所以这不是真正的错误,只是行为未指定(某些提供者可能支持这样的功能,偶然或设计)。

话虽如此,Entity2.id 的唯一约束存在于 one-to-many 关联的联接 table 中是有意义的。我会尝试切换到 @ManyToMany(风险自负,因为...未指定的行为)