【问题标题】:EclipseLink: "Updates are not allowed" on a native query INSERT INTOEclipseLink:本机查询 INSERT INTO 上的“不允许更新”
【发布时间】:2015-02-13 13:20:34
【问题描述】:

自从从 EL 2.1 升级到 EclipseLink 2.5 后,我们在之前运行的代码上得到了 PersistenceException。 场景非常简单:我们有两个相同的表,唯一的区别是一个是另一个的“历史”版本。基本上,当我们确定某行不再更改时,我们将该行移至历史记录表。这意味着虽然 id 是在第一个表上生成的,但它是在历史表上“继承的”。以下是实体:

@Entity
@Table(name = "DOCUMENT")
public class Document implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    // Some other fields
}

@Entity
@Table(name = "DOCUMENT_HISTORY")
public class DocumentHistory implements Serializable {
    @Id
    private long id;
    // Some other fields
}

为了将一行移动到历史表中,我们使用原生查询(因为有些列没有映射为实体类中的字段):

String query = "INSERT INTO DOCUMENT_HISTORY SELECT * FROM DOCUMENT t WHERE t.id=?1";
Query updateQuery = entityManager.createNativeQuery(query);
updateQuery.setParameter(1, document.getId());
updateQuery.executeUpdate();

执行此查询时,有时会抛出此异常:

javax.persistence.PersistenceException: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): 
org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.myc.entities.jpa.company.DocumentHistory] is mapped to a primary key column in the database. Updates are not allowed.
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:868)
    at org.eclipse.persistence.internal.jpa.QueryImpl.performPreQueryFlush(QueryImpl.java:963)
    at org.eclipse.persistence.internal.jpa.QueryImpl.executeUpdate(QueryImpl.java:296)
    at org.myc.utility.jpa.user.DocumentManager.toDocumentHistory(DocumentManager.java:141)
    at java.lang.Thread.run(Thread.java:662)
Caused by: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [id] of class [org.myc.entities.jpa.company.DocumentHistory] is mapped to a primary key column in the database. Updates are not allowed.
    at org.eclipse.persistence.exceptions.ValidationException.primaryKeyUpdateDisallowed(ValidationException.java:2548)
    at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.writeFromObjectIntoRowWithChangeRecord(AbstractDirectMapping.java:1257)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildRowForUpdateWithChangeSet(ObjectBuilder.java:1768)
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1030)
    at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
    at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:899)
    at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:798)
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1793)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1775)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1726)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:267)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsForClassWithChangeSet(CommitManager.java:192)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:138)
    at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4196)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1587)
    at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:452)
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:863)
    ... 5 more

有人遇到过同样的问题吗?为什么你认为这不会发生在所有行上,而只是其中一些行(没有明显的特定特征)? 提前致谢, 卢克

P.S.:EL 对原生查询执行映射/查询验证是否正确?

【问题讨论】:

    标签: mysql jpa eclipselink


    【解决方案1】:

    好的,我发现了问题,并且与该查询完全无关,所以我仍然不知道为什么会在那里抛出异常。 问题在于,我们没有使用entityManager.find() 来获取新插入的历史记录行,而是创建了一个未附加到持久性上下文的 DocumentHistory 实例(具有相同的 ID),然后使用该实例执行其他操作。 希望它可以帮助某人。

    【讨论】:

    • "which was not attached to a persistence context" 这是使用我在中提到的 INSERT 语句跳过事务缓存(持久性上下文)和共享缓存的副作用我在上面的评论。
    • 谢谢@wypieprz,我不明白你指的是什么。
    【解决方案2】:

    如 JPA 2.0 规范中所述,executeUpdate() 执行更新或删除语句,因此不支持插入。

    您可以使用 EntityManager(在事务中)通过持久化和刷新实体来将实体插入到底层数据库中。在您的特定情况下,一个虚拟示例可能如下所示:

    // transaction starts
    Document doc = em.find(Document.class, id);
    DocumentHistory docHistory = new DocumentHistory(doc);
    em.persist(docHistory);
    // transaction ends
    

    注意:您可以考虑使用EclipseLink's auditing 功能,而不是专有解决方案(这很好):

    EclipseLink 还支持完整的历史记录,它允许在镜像历史记录表中跟踪对数据库所做的所有更改的完整历史记录。

    【讨论】:

    • JPA 2.0 规范还声明 executeUpdate() throws IllegalStateException 用于 SELECT 语句或条件查询。您还可以在任何地方找到很多使用带有 INSERT 语句的 executeUpdate 的示例。因此,我不认为这是问题所在(或者 EL 2.1 不符合 JPA 2.0?)。我不能使用persist(),因为有些表列没有映射为字段,使用此方法不会复制。感谢您的审计建议,我不知道它的存在,但这不是我们使用历史表的目的。
    • 是的,SELECT 语句绝对应该与em.create*Query 方法一起使用。至于executedUpdate,确实,将INSERT 描述为不鼓励 而不是不支持 可能会更好、更充分。您可以将INSERT(甚至ALTER)与JPA 一起使用,但这会导致缓存实体和数据库之间存在不一致。您还可以通过使用@Transient 注释它们来防止复制不可持久的字段,因此em.persist() 不应损害您的ORM。
    • 关于字段,问题是相反的。假设在 DB 表中有一个名为“type”的列,但在实体类中没有映射该列的字段“type”。为了将该列值从 Document 复制到 DocumentHistory,我相信除了使用本机 SQL 之外别无他法。
    • 是什么阻止你在实体类中添加“type”字段?
    • 在编译时不知道会有一个“类型”列:)
    猜你喜欢
    • 2014-04-22
    • 1970-01-01
    • 2016-07-27
    • 2013-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多