【发布时间】:2018-03-27 10:05:37
【问题描述】:
我正在尝试使用 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$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<String, String>.
我没有尝试@CollectionTable,因为它
指定用于映射基本或可嵌入类型集合的表。
如果我无法弄清楚这一点,我可能会将Map<String, Entity> 转换为Set<WrappingEntity>,其中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