【问题标题】:How to map Map<String, Entity> with a non-unique value set in JPA 2.1?如何使用 JPA 2.1 中设置的非唯一值映射 Map<String, Entity>?
【发布时间】:2018-03-27 10:05:37
【问题描述】:

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

我有一个

@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$1.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$1.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

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

@MapKeyColumn 是基本类型之间的映射,例如Map&lt;String, String&gt;.

我没有尝试@CollectionTable,因为它

指定用于映射基本或可嵌入类型集合的表。

如果我无法弄清楚这一点,我可能会将Map&lt;String, Entity&gt; 转换为Set&lt;WrappingEntity&gt;,其中WrappingEntity 包含一个id、字符串值和Entity2 引用,这甚至可能更有效,但是我也想弄清楚这一点。该方法通常应该有效,因为具有Entity1 的id、字符串映射键和映射Entity2 值的表可以使用Entity1 的id 和字符串映射键作为主键。

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

【问题讨论】:

  • 你有什么架构?你让你的 JPA 提供者创建它吗?我使用的 JPA 提供程序在 Map 表(在您的情况下为 ENTITY1_ENTITY2)上创建了一个 PK 约束,并将 FK 列返回到 ENTITY1 和 KEY,因此 value 没有这样的约束,它工作正常。
  • @BillyFrost 我让 EclipseLink 2.5 生成架构(我在问题中添加了 persistence.xml 以澄清)。现在有一个明显的大问题:您使用哪个 JPA 提供程序来完成这项工作?
  • 我使用 DataNucleus JPA。我没有@MapKey,因为这是因为您将值表中的列表示的地图作为地图的键;我使用连接表来表示地图,例如datanucleus.org:15080/products/accessplatform_5_1/jpa/… 这是您想要实现的目标吗?因为如果在单个地图中多次使用该值,您显然无法将密钥存储在该值中
  • @BillyFrost 你是对的。这适用于 DataNucleus 5.1.4,但为什么不适用于 EclipseLink 2.5、Hibernate、5.2?感谢调查和评论。您会认为它在 EclipseLink 和 Hibernate 中不起作用是一个错误吗?在不切换到 DataNucleus 的情况下还有其他可能实现此功能吗?
  • @crizzis 这是一个很好的解释,我会接受它作为答案,谢谢。 @ManyToMany 在主键中也包含 Entity2.id(这符合您的解释)。

标签: java dictionary jpa jpa-2.1


【解决方案1】:

我不确定 JPA 2.1 规范是否将实体提到为一对多关联中的非唯一值,所以这并不是一个真正的错误,只是行为未指定(一些提供商可能支持这样的功能,偶然或设计)。

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

【讨论】:

    猜你喜欢
    • 2010-10-25
    • 2014-06-25
    • 2012-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-08
    • 2011-10-22
    • 2011-08-31
    相关资源
    最近更新 更多