簡體   English   中英

@IdClass使用JPA和Hibernate生成'實例的標識符'

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

對於使用不區分大小寫的數據庫模式的JPA實體模型,當我使用@IdClass注釋時,我始終得到“實例的標識符被更改”異常。 對於具有“字符串”主鍵的對象,當數據庫中存在一個案例的字符串並且僅使用大小寫相同的字符串執行查詢時,會發生錯誤。

我查看了其他SO答案,它們的形式如下:a)不修改主鍵(我不是)和b)你的equals()/ hashCode()實現有缺陷。 對於'b',我嘗試過使用toLowerCase()equalsIgnoringCase()但無濟於事。 [此外,似乎Hibernate代碼直接設置屬性,而不是在發生'更改'時調用屬性設置器。

這是具體的錯誤:

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}

問:對於包含公司'Acura'(作為主鍵)的不區分大小寫的DB,使用@IdClass如何隨后查找其他大寫字母?

這是有問題的代碼(從空數據庫開始):

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);    
    }
}

這是'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 + "}";
        }
    }
}

注意:我知道當有一個主鍵時不需要使用@IdClass ; 以上是問題的最簡單的例子。

正如我所說,我相信即使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

出現此錯誤的原因是由於更改了托管實體的實體標識符。

在PersistenceContext的生命周期中,任何給定實體都可以有一個且只有一個托管實例。 為此,您無法更改現有的管理實體標識符。

在您的示例中,即使您啟動了一個新事務,您也必須記住PersistenContext尚未關閉,因此您仍然擁有一個附加到Hibernate會話的托管c1實體。

當您試圖找到公司時:

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

標識符與附加到當前會話的公司的標識符不匹配,因此發出查詢:

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

由於SQL是CASE INSENSITIVE,因此您實際上將選擇與當前托管公司實體(持久性c1 )相同的數據庫行。

但是,對於同一個數據庫行,您只能有一個托管實體,因此Hibernate將重用托管實體實例,但它會將標識符更新為:

new Company.Identity("ACURA");

您可以使用以下測試檢查此假設:

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

提交第二個事務時,flush將嘗試更新實體標識符(從'Acura'更改為'ACURA'),因此DefaultFlushEntityEventListener.checkId()方法將失敗。

據JavaDoc說,這項檢查適用於:

make(ing)確定(用戶)沒有破壞id

要解決此問題,您需要刪除此find方法調用:

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

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

assertTrue(em.contains(c1));

您似乎手動將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"));

在上面的代碼中你是否嘗試將“ACURA”改為“Acura”,這似乎是根本原因。 並且您使用相同的實例c1來表示具有不同ID的對象,即1表示“ACURA”,第2表示“Acura”。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM