间歇性错误org.hibernate.PersistentObjectException:传递给persist的分离实体

时间:2014-12-19 22:59:37

标签: java hibernate jpa ejb javabeans

我收到异常org.hibernate.PersistentObjectException:传递给persist的分离实体。从本论坛和其他地方的众多帖子中,我了解到这种情况发生在两种情况下(不考虑One-One注释等),

  1. 超出范围的交易存在问题
  2. 设置id应自动生成的位置。
  3. 我发现我的代码中没有发生这些情况。我无法重现错误,因为我没有最初触发它的数据。在其他数据上,它运行得很好。我在下面提供了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)&lt; - MyImportEJB.saveObject&lt; -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。我想知道是否有可能正在设置ID&#34;引擎盖下#34;通过Hibernate达到一个不可接受的值

3 个答案:

答案 0 :(得分:0)

我认为这可能是你的问题

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

您正在填充saveQueue最多5000个,然后您尝试保留/合并它们。我不确定Hibernate,但EclipseLink的默认共享缓存大小为1000.这意味着,您可以填充saveQueue,例如,2000现有Foo s,并且当您调用{时对它们{1}},它们不再位于实体管理器的缓存中。这导致在已经设置persist()的已分离实例上调用persist()

在你的情况下,我认为在所有对象上调用id是完全可以的,无论天气是否是现有对象,因为它会更新现有对象,并保留新对象。

答案 1 :(得分:0)

我希望您使用最新的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并且只使用托管对象,这将需要所有操作的单个事务,因此需要重新设计事物。

答案 2 :(得分:0)

我再次在生产服务器上运行代码,并在分离的实体异常之前得到了TransactionRollbackException。我认为这可能是我观察到的例外的实际原因。

由于我试图保存的大量对象,事务超时并被回滚,这导致Foo对象变得分离。不知何故,TransactionRollbackException第一次没有出现在日志中,可能是因为它恰好发生在12:0 am UT。为了支持这一理论,在我将批量减小到~1000之后,该程序到目前为止还没有崩溃

对于那些感兴趣的人,TransactionRollbackException会导致实体分离:entityManager.getTransaction().rollback() detaches entities?

有些方法可以在发生这种情况时对此进行处理,但为了简单起见,我选择不执行它们