java @IdClass 使用 JPA 和 Hibernate 生成“实例的标识符已更改”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28231818/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 13:13:27  来源:igfitidea点击:

@IdClass Produces 'Identifier of an Instance was Altered' with JPA and Hibernate

javahibernatejpaormentity

提问by GoZoner

For a JPA entity model using a case-insensitive database schema, when I use a @IdClass annotation I consistently get 'identifier of an instance was altered' exception. For an object with a 'string' primary key, the error occurs when an string of one case exists in the database and a query is performed with the same string differing only in case.

对于使用不区分大小写的数据库模式的 JPA 实体模型,当我使用 @IdClass 注释时,我始终收到“实例标识符已更改”异常。对于带有'string'主键的对象,当数据库中存在一个大小写的字符串并且使用相同的字符串执行查询时会发生错误,只是大小写不同。

I've looked at other SO answers and they are of the form: a) don't modify the primary key (I'm not) and b) your equals()/hashCode() implementations are flawed. For 'b' I've tried using toLowerCase()and equalsIgnoringCase()but to no avail. [Additionally, it seems the Hibernate code is directly setting properties, rather than calling property setters when the 'altering' occurs.]

我看过其他 SO 答案,它们的形式是:a)不要修改主键(我不是)和 b)你的 equals()/hashCode() 实现有缺陷。对于'b',我试过使用toLowerCase()equalsIgnoringCase()但无济于事。[此外,似乎 Hibernate 代码是直接设置属性,而不是在发生“更改”时调用属性设置器。]

Here is the specific error:

这是具体的错误:

Caused by: javax.persistence.PersistenceException: org.hibernate.HibernateException: 
identifier of an instance of db.Company was altered 
 from {Company.Identity [62109154] ACURA}
   to {Company.Identity [63094242] Acura}

Q: For a case insensitive DB containing a company 'Acura' (as primary key), using @IdClass how do I subsequently find other capitalizations?

问:对于包含公司“Acura”(作为主键)的不区分大小写的数据库,使用 @IdClass 我如何随后找到其他大写?

Here is the offending code (starting with an empty database):

这是有问题的代码(从空数据库开始):

public class Main {    
    public static void main(String[] args) {
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("mobile.mysql");
        EntityManager em = emf.createEntityManager();

        em.getTransaction().begin();

        Company c1 = new Company ("Acura");
        em.persist(c1);

        em.getTransaction().commit();
        em.getTransaction().begin();

        c1 = em.find (Company.class, new Company.Identity("ACURA"));

        em.getTransaction().commit();
        em.close();
        System.exit (0);    
    }
}

and here is the 'db.Company' implementation:

这是“db.Company”的实现:

@Entity
@IdClass(Company.Identity.class)
public class Company implements Serializable {

    @Id
    protected String name;

    public Company(String name) {
        this.name = name;
    }

    public Company() { }

    @Override
    public int hashCode () {
        return name.hashCode();
    }

    @Override
    public boolean equals (Object that) {
        return this == that ||
                (that instanceof Company &&
                        this.name.equals(((Company) that).name));}

    @Override
    public String toString () {
        return "{Company@" + hashCode() + " " + name + "}";
    }

    //

    public static class Identity implements Serializable {
        protected String name;

        public Identity(String name) {
            this.name = name;
        }

        public Identity() { }

        @Override
        public int hashCode () {
            return name.hashCode();
        }

        @Override
        public boolean equals (Object that) {
            return this == that ||
                    (that instanceof Identity &&
                        this.name.equals(((Identity)that).name));
        }

        @Override
        public String toString () {
            return "{Company.Identity [" + hashCode() + "] " + name + "}";
        }
    }
}

Note: I know using @IdClassisn't needed when there is a single primary key; the above is the simplest example of the problem.

注意:我知道@IdClass当只有一个主键时不需要使用;以上是问题的最简单示例。

As I said, I believe this problem persists even when the hashCode()/equals() methods are made case insensitive; however, suggestions taken.

正如我所说,即使 hashCode()/equals() 方法不区分大小写,我相信这个问题仍然存在;然而,采取的建议。

...
INFO: HHH000232: Schema update complete
Hibernate: insert into Company (name) values (?)
Hibernate: select company0_.name as name1_0_0_ from Company company0_ where company0_.name=?
Exception in thread "main" javax.persistence.RollbackException: Error while committing the transaction
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:94)
    at com.lambdaspace.Main.main(Main.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of db.Company was altered from {Company.Identity [62109154] ACURA} to {Company.Identity [63094242] Acura}
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:82)
    ... 6 more
Caused by: org.hibernate.HibernateException: identifier of an instance of db.Company was altered from {Company.Identity [62109154] ACURA} to {Company.Identity [63094242] Acura}
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:80)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:192)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:152)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:231)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:102)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:55)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77)
    ... 6 more

回答by Vlad Mihalcea

The reason for this error is due to changing the entity identifier of a managed entity.

此错误的原因是由于更改了受管实体的实体标识符。

During the life-time of a PersistenceContext, there can be one and only one managed instance of any given entity. For this, you can't change an existing managed entity identifier.

在 PersistenceContext 的生命周期内,任何给定实体可以只有一个托管实例。为此,您不能更改现有的托管实体标识符。

In you example, even if you start a new transaction, you must remember that the PersistenContext has not been closed, so you still have a managed c1entity attached to the Hibernate Session.

在你的例子中,即使你开始一个新的事务,你也必须记住 PersistenContext 还没有关闭,所以你仍然有一个c1附加到 Hibernate Session的托管实体。

When you try to find the Company:

当您尝试查找公司时:

c1 = em.find (Company.class, new Company.Identity("ACURA"));

The identifier doesn't match the one for the Company that's being attached to the current Session, so a query is issued:

该标识符与附加到当前会话的公司的标识符不匹配,因此发出查询:

Hibernate: select company0_.name as name1_0_0_ from Company company0_ where company0_.name=?

Because SQL is CASE INSENSITIVE, you will practically select the same database row as the current managed Company entity (the persisted c1).

因为 SQL 是 CASE INSENSITIVE,您实际上将选择与当前托管的 Company 实体(持久化的c1)相同的数据库行。

But you can have only one managed entity for the same database row, so Hibernate will reuse the managed entity instance, but it will update the identifier to:

但是对于同一数据库行只能有一个托管实体,因此 Hibernate 将重用托管实体实例,但它会将标识符更新为:

new Company.Identity("ACURA");

You can check this assumptions with the following test:

您可以通过以下测试检查此假设:

String oldId = c1.name;
Company c2 = em.find (Company.class, new Company.Identity("ACURA"));
assertSame(c1, c2);
assertFalse(oldId.equals(c2.name));

When the second transaction is committed, the flush will try to update the entity identifier (which changed from 'Acura' to 'ACURA') and so the DefaultFlushEntityEventListener.checkId()method will fail.

提交第二个事务时,刷新将尝试更新实体标识符(从“Acura”更改为“ACURA”),因此DefaultFlushEntityEventListener.checkId()方法将失败。

According to teh JavaDoc, this check is for:

根据 teh JavaDoc,此检查用于:

make(ing) sure (the) user didn't mangle the id

make(ing) 确保 (the) 用户没有破坏 id

To fix it, you need to remove this find method call:

要修复它,您需要删除此 find 方法调用:

c1 = em.find (Company.class, new Company.Identity("ACURA"));

You can check that c1is already attached:

您可以检查c1是否已附加:

assertTrue(em.contains(c1));

回答by Bilbo Baggins

you seem to be manually assigning ids to your persistent object which is being managed by the JPA itself and you are trying to alter the id which is already there with this entity, which is not allowed.

您似乎正在手动将 id 分配给由 JPA 本身管理的持久对象,并且您正在尝试更改该实体已经存在的 id,这是不允许的。

    Company c1 = new Company ("Acura");
    em.persist(c1);

    em.getTransaction().commit();
    em.getTransaction().begin();

    c1 = em.find (Company.class, new Company.Identity("ACURA"));

In above piece of code did you try changing "ACURA" to "Acura" that seems to be the root cause. and you are using the same instance c1 to represents both the object having different ids i.e. 1 with "ACURA" and 2nd with "Acura".

在上面的一段代码中,您是否尝试将“ACURA”更改为“Acura”,这似乎是根本原因。并且您使用相同的实例 c1 来表示具有不同 id 的对象,即 1 带有“ACURA”,而 2nd 带有“Acura”。