簡體   English   中英

間歇性錯誤org.hibernate.PersistentObjectException:傳遞給persist的分離實體

[英]Intermittent error org.hibernate.PersistentObjectException: detached entity passed to persist

我收到異常org.hibernate.PersistentObjectException:傳遞給persist的分離實體。 從本論壇和其他地方的眾多帖子中,我了解到這種情況發生在兩種情況下(不考慮One-One注釋等),

  1. 超出范圍的交易存在問題
  2. 將id設置為應自動生成的位置。

我看到這些都沒有發生在我的代碼中。 我無法重現錯誤,因為我沒有最初觸發它的數據。 在其他數據上,它運行得很好。 我在下面提供了一個SCCE:

public class MyProcessor {
    private MyImportEJB myEJB = MyImportEJB.getInstance();
    private List<MyClass> saveQueue = new ArrayList<MyClass>();
    public void process() {
        List<X> rawData = getListOfX();
        for(X x:rawData) {
             processX();
         }
         saveFoos(saveQueue);   
     }

     public void saveOrUpdateFoos(List<Foo> foos) {

        for(MyClass foo:foos) {
              MyClass existingFoo = myEJB.getFoosForWidAndDates(foo.getWid(), foo.getEffBeginDt(),foo.getEffEndDt());
              if(existingFoo == null) saveQueue.add(foo);
              else {
                   existingFoo.updateIfDifferent(foo);
                   saveQueue.add(existingFoo);
              }
         }

         if(saveQueue.size() > 5000) {
             myEJB.saveObjects(saveQueue);
             saveQueue.clear();
         }
     }

     public void processX() {
          ArrayList<MyClass> foos = new ArrayList<MyClass>();

          if(x.reportPeriod != null && x.gravity != null){
              MyClass foo = new MyClass();
              foo.setId(null);
              foo.setWId(x.getWid());
              foo.setEffBeginDt(x.reportPeriod);
              foo.setEffEndDt(addOneMonth(x.reportPeriod));
              foo.setGravity(x.gravity);

              foos.add(foo);
          }
          saveOrUpdateFoos(foos);

     }
 }

MyImportEJB.java:

@Stateless
@EJB(name = "MyImportEJB", beanInterface = MyImportEJB.class)
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
@PermitAll
public class MyImportEJB{
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void saveObjects(List<? extends P> mappedObjects) 
    {
        for (P mappedObject : mappedObjects)
        {
            this.saveObject(mappedObject);
        }
    }


    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void saveObject(P mappedObject) 
    {
        EntityManager entityManager = this.getEntityManager();

        Object identifier = this.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(mappedObject);
        if (identifier != null) {
            Object existingObject = entityManager.find(mappedObject.getClass(), identifier);

            if (existingObject != null) {
                entityManager.merge(mappedObject);
                return;
            }
         }

         entityManager.persist(mappedObject);
    }

    public MyClass getFoosForWidAndDates(Integer wid, Calendar effBeginDt, Calendar effEndDt) {
         try {
            return (MyClass)((this.entityManager
        .createQuery("select M from MyClass M where wid = :wid and effBeginDt = :effBeginDt and effEndDt = :effEndDt ", MyClass.class)
        .setParameter("wid",wid)
        .setParameter("effBeginDt", effBeginDt)
        .setParameter("effEndDt", effEndDt)).getSingleResult());
         } catch(NoResultException | NonUniqueResultException e) {
            return null;
        }
    }
 }

MyClass.java

public MyClass{

      @Id
      @GeneratedValue(strategy=GenerationType.IDENTITY)
      @Column(name = "Id")
      private Integer id;

      @Column(name = "wid")
      private Integer wId;

      @Column(name = "eff_begin_dt")
      private Calendar effBeginDt;

      @Column(name = "eff_end_dt")
      private Calendar effEndDt;

      @Column(name = "gravity")
      private Double gravity;

      private Integer dataDownloadId;

      public void updateIfDifferent(MyClass other) {
          if(other.gravity != null && other.gravity != this.gravity) this.gravity = other.gravity;
          //same for effBeginDt andeffEndDt

      }

 }

persistence.xml中

  <?xml version="1.0" encoding="UTF-8" ?>

 <persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
    <persistence-unit name="ProdData">
        <description>ProdData Database Persistence Unit</description>
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:jboss/ProdDataJNDI</jta-data-source>
        <class>path.to.MyClass</class>
        <class>path.to.X</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <properties>
            <property name="hibernate.show_sql" value="false" />
         </properties>
    </persistence-unit>
 </persistence>

調用entityManager.persist(mappedObject)< - MyImportEJB.saveObject <-MyImportEJB.saveObjects時拋出異常。 我沒有行號

我嘗試編寫一個示例程序,從數據庫中獲取一個existingFoo對象,更新並保存它,因為這是最可能的錯誤來源。 但我無法重現錯誤。 請幫忙。

編輯:以下是getListofX()的詳細信息

來自MyProcessor.java:

public List<X> getListOfX() {
    return myImportEJB.getUnprocessedIds(X.class, 30495);
}

從文件MyImportEJB.java:

@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public List<Integer> getUnprocessedIds(Class<? extends ProductionRawData> clazz, Integer dataDownloadId) {
    String canonicalName = clazz.getCanonicalName();

    String queryStr = "select id from " + canonicalName + " where datadownloadId = :dataDownloadId and isProcessed != 1";

    TypedQuery<Integer> query = this.entityManager.createQuery(queryStr, Integer.class)
        .setParameter("dataDownloadId", dataDownloadId);
    try {
        return query.getResultList();
    } catch(NoResultException nre) {
         return new ArrayList<T>();
    }
}

編輯:還添加了getFoosForWidAndDates()的詳細信息。 有人告訴我,在將新Foo的id添加到保存隊列之前,我將其設置為null。 我想知道是否有可能將Hibernate的“引擎蓋”設置為不可接受的值

我想這可能是你的問題

 if(saveQueue.size() > 5000) {
     myEJB.saveObjects(saveQueue);
     saveQueue.clear();
 }

您正在將saveQueue填充到5000,然后您嘗試保留/合並它們。 我不確定Hibernate,但EclipseLink的默認共享緩存大小是1000.這意味着,你可以填充saveQueue ,例如,2000現有的Foo ,當你在它們上調用persist()時,它們就不是在實體管理器的緩存中更長。 這導致在已經設置了id已分離實例上調用persist()

在你的情況下,我認為在所有對象上調用entityManager.merge(mappedObject)是完全可以的,無論天氣是否是現有對象,因為它將更新現有對象,並持久保存新對象。

我希望你使用最新的Hibernate 我希望這是一個錯字:

if(existingFoo == null) saveQueue.add(existingFoo); // if it is a null, you add it?

否則它看起來很有趣,你可以嘗試做以下事情:

  1. 在每個(10,1000)持久化對象之后刷新會話,如下所示:

     public void saveObjects(List<? extends P> mappedObjects) { for (P mappedObject : mappedObjects) { this.saveObject(mappedObject); // some condition if you have a lot of objects this.entityManager.flush(); } } 
  2. 使用不太奇特的方式來檢查實體是否已經存在:

     public void saveObject(P mappedObject) { EntityManager entityManager = this.getEntityManager(); if (mappedObject.getId() != null) { entityManager.merge(mappedObject); return; } entityManager.persist(mappedObject); } 

    或者最好完全避免merge並且只使用托管對象,這將需要所有操作的單個事務,因此需要重新設計事物。

我再次在生產服務器上運行代碼,並在分離的實體異常之前得到了TransactionRollbackException。 我認為這可能是我觀察到的例外的實際原因。

事務超時並由於我試圖保存的大量對象而被回滾,這導致Foo對象變得分離。 不知何故,TransactionRollbackException第一次沒有出現在日志中,可能是因為它恰好發生在12:0 am UT。 為了支持這一理論,在將批量減小到~1000之后,該程序到目前為止還沒有崩潰

對於那些感興趣的人,TransactionRollbackException會導致實體分離: entityManager.getTransaction()。rollback()分離實體?

有可能在發生這種情況時處理這個問題,但為了簡單起見,我選擇不執行它們

暫無
暫無

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

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