Intermittent error org.hibernate.PersistentObjectException: deleted object passed for save

I get an org.hibernate.PersistentObjectException exception: a separate object is passed in for saving. From the many posts on this forum and elsewhere, I understand that this happens in two cases (not taking into account One-One annotations, etc.),

  • There is a problem with a transaction going out of scope
  • The identifier is set where it should be automatically generated.

I do not see anything like this with my code. I cannot reproduce the error because I do not have the data that originally caused it. According to other sources, it works fine. I have provided SCCE below:

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> 

An exception is thrown when the entityManager.persist (mappedObject) <- MyImportEJB.saveObject <-MyImportEJB.saveObjects method is called. I do not have a line number

I tried to write a sample program where I get the existingFoo object from the database, update and save it, because it was the most likely source of the error. But I could not reproduce the error. Please, help.

EDIT: Here are the details of getListofX () on request

from MyProcessor.java:

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

from the file 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>(); } } 

EDIT: Also added info on getFoosForWidAndDates (). I was suggested that I set the identifier of the new Foo to null before adding it to the save queue. I would like to know if it is possible that the identifier is set under "hoodernate" under Hibernate to an unacceptable value

+6
source share
3 answers

I ran the code again on the production server, and I got a TransactionRollbackException before a single object's exceptions. I think this may be the actual cause of the exception that I have observed.

The transaction worked and was discarded due to the huge number of objects that I tried to save, and this led to the Foo objects being disconnected. Somehow, TransactionRollbackException did not appear in the logs for the first time, probably because it happened at 12:00 AM UT. In support of this theory, the program did not crash until I reduced the batch size to ~ 1000

For those who are interested, TransactionRollbackException leads to detachment of entities: entityManager.getTransaction (). Rollback () separates objects?

There are ways to handle this when this happens, but I refused to implement them for simplicity

0
source

I think this may be your problem.

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

You fill saveQueue to 5000 and then try to save / merge them. I'm not sure about Hibernate, but the default cache size for EclipseLink is 1000. This means that you can fill saveQueue , for example, the existing Foo 2000, and by the time you call persist() on them, they are no longer in object manager cache. This results in a persist() call for a single instance that has already set id .

In your case, I think it would be normal to call entityManager.merge(mappedObject) on all objects, regardless of the weather, is it an existing object or not, because it will update existing ones and save new ones.

0
source

Hope you are using the latest Hibernate . And I expect this to be a typo here:

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

Otherwise, it looks pretty interesting, you can try to do the following:

  • Reset the session after each (10, 1000) stored objects, for example:

     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(); } } 
  • Use a less exotic way to check if an object is saved:

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

    Or it’s better to completely avoid merge and just work with managed objects, this will require one transaction for all operations, so redesigning things will be required.

0
source

Source: https://habr.com/ru/post/979964/


All Articles