We are using jboss 4.2CR1 and Hibernate 3.2.1 and get an Assertion Error on flushing when touching LAZY 1:m:n relations in an PostUpdateListener, even when only updating a simple property on the 1-side Entity of this relation and do NOTHING on the relation itself.
Quote:
09:23:12,699 ERROR [AssertionFailure] an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: collection [com.xyz.server.core.ejb.entity.equipment.EquipmentEntity.repairs] was not processed by flush()
at org.hibernate.engine.CollectionEntry.postFlush(CollectionEntry.java:205)
at org.hibernate.event.def.AbstractFlushingEventListener.postFlush(AbstractFlushingEventListener.java:333)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:28)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.ejb.AbstractEntityManagerImpl$1.beforeCompletion(AbstractEntityManagerImpl.java:515)
at org.jboss.tm.TransactionImpl.doBeforeCompletion(TransactionImpl.java:1491)
There seems to be a bug, when processing the n-side of a 1:n relation [= PersistentCollection I get for a 1:n relation via event.getOldState() or event.getState()] inside an Event-Listener, when the entities of this PersistentCollection have by themselve further 1:n relations to a 3rd-entity.
It seems a little bit complicated, to reproduce this failure:
You need 3 enties with relations like following:
Code:
EquipmentType <---> Equipment <----> Repairs
1 : n 1 : m
If we update only a SIMPLE property (not relation) in
EquipmentType, it claims, that flush() [inside PostupdateListener] has not processed the PersistentCollection of
Repairs in Equipment!
We use the following PostUpdateEventlistener:
Code:
package com.qualitype.lims.server.common.audit;
/**
* *
* This is the base for all AuditEventListeners. Dependent of the exact event
* it's examing the difference between old and new object. This differences were
* logged into the datbase via
* @see(AuditRecordEntity.class)
*
*/
public abstract class AuditBaseListener extends BaseListener implements
CallbackHandlerConsumer {
EntityCallbackHandler callbackHandler;
public void setCallbackHandler(EntityCallbackHandler handler) {
this.callbackHandler = handler;
}
/**
* audits the changes of an entity after an update of this entity
*
* @param event -
* the PostUpdate event
*/
protected void audit(PostUpdateEvent event) {
// audit update events
auditChanges(DatabaseOperation.UPDATE, event.getOldState(), event
.getState(), event.getPersister().getPropertyNames(),
(AuditableEntity) event.getEntity());
}
/**
* examines old and new object and logs the changes of persistent data into
* an audit-record
*
* @param event
* the type of event (INSERT, UPDATE, DELETE) being logged.
*/
@SuppressWarnings( { "boxing", "nls" })
private void auditChanges(DatabaseOperation event, Object[] oldValues,
Object[] newValues, String[] properties, AuditableEntity entity) {
// synchronized (getClass()) {
// get a new database Session
Session session = getCurrentSession();
try {
// create base AuditRecord
AuditRecordEntity auditRecord = new AuditRecordEntity(
AuditableEntity.getCurrentUser(), entity, event);
// Iterate through all the fields in the object
for (int ii = 0; ii < properties.length; ii++) {
AuditChangedFieldEntity auditFieldRecord = null;
String beforeValue = "";
String afterValue = "";
if (properties[ii].equals("version")
|| properties[ii].equals("createdBy")
|| properties[ii].equals("updatedBy")
|| properties[ii].equals("createdTime")
|| properties[ii].equals("updatedTime"))
continue;
if (oldValues != null)
beforeValue = getFormatedString(oldValues[ii]);
if (newValues != null)
afterValue = getFormatedString(newValues[ii]);
if (event.equals(DatabaseOperation.UPDATE)) {
if (afterValue.equals(beforeValue)) {
// Values haven't changed so loop to next property
continue;
} else {
auditFieldRecord = new AuditChangedFieldEntity(
properties[ii], beforeValue, afterValue);
}
} else if (event.equals(DatabaseOperation.DELETE)) {
auditFieldRecord = new AuditChangedFieldEntity(
properties[ii], beforeValue, afterValue);
} else if (event.equals(DatabaseOperation.INSERT)) {
// brand new collections will appear as [-1, -1, -1, -1,
// -1,
// -1, -1, -1, -1, -1]
// unless we get all the id values after the new owning
// entity has been persisted
if (afterValue.contains("-1")) // -1 //$NON-NLS-1$
// indicates unsaved value
{
Integer count = Arrays.asList(properties).indexOf(
properties[ii]);
Object object = newValues[count];
if (object instanceof Collection) {
afterValue = (toString((Collection) object));
}
}
auditFieldRecord = new AuditChangedFieldEntity(
properties[ii], beforeValue, afterValue);
}
if (auditFieldRecord != null) {
// database linking
auditFieldRecord.setAuditRecord(auditRecord);
auditRecord.getChangedFields().add(auditFieldRecord);
}
}
// save auditRecord and cascading save auditFieldRecords
session.save(auditRecord);
} catch (HibernateException e) {
e.printStackTrace();
throw new CallbackException(e);
} finally {
if (session != null) {
/*
* there seems to be a change in Hibernate since 3.1 so default
* option autoFlush=true for JTA-SessionContext is not working
* anymore. We have explicitly to flush the session.
*
* http://opensource.atlassian.com/projects/hibernate/browse/HHH-2259?page=all
*/
session.flush();
// TODO: session.close() ?
session = null;
}
}
}
protected static Session getCurrentSession() throws HibernateException {
if (sessionFactory == null) {
try {
InitialContext ctx = new InitialContext();
sessionFactory = (SessionFactory) ctx
.lookup("java:hibernate/SessionFactory");
} catch (NamingException e) {
e.printStackTrace();
throw new HibernateException(e);
}
}
/**
* INFO: in case of a JTA datasources (like mostly under jboss), the
* implenatation-class for Interface "CurrentSession" is
* JTACurrentSession, which always returns a session(=connection) from
* the jboss pool. These session should automatically be configured for
* autoFlush and autoClose.
*/
// return sessionFactory.getCurrentSession();
return sessionFactory.openSession(sessionFactory.getCurrentSession()
.connection());
}
}
If we iterate through Event.getOldState() and getting the Equipment-Collection associated with EquipmentType and do something simple with this collection, for example like following way in the EventListener:Code:
private String getFormatedString(Object object) {
String value = ""; //$NON-NLS-1$
try {
if (object != null) {
value = (object instanceof Collection) ? toString((Collection) object)
: toString(object);
}
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
private String toString(Collection collection) {
method-versions
StringBuilder str = new StringBuilder(START);
for (Object object : collection) {
str.append(toString(object));
str.append(DELIM);
}
if (!collection.isEmpty())
str.delete(str.lastIndexOf(DELIM), str.length());
str.append(END);
return str.toString();
}
.
.
.
Hibernate will then throw the AssertionFailure:
Quote:
09:23:12,699 ERROR [AssertionFailure] an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: collection [com.xyz.server.core.ejb.entity.equipment.EquipmentEntity.repairs] was not processed by flush()
This failure seems only go away, if:
- I comment out the whole method toString(Collection collection) - indicating a reason for the assertion-failure is inside this method
or
- we set the 1:M relation to EAGER or CASCADE-STYLE to MERGE.
Finally: There seems to be a bug, when processing the n-side of a 1:n relation inside an Event-Listener, when the entities of the n-side collection have by themselve further 1:n relations to a 3rd-entity? Any hint to avoid this assertion Failure ?
Jboss 4.2CR1
Hibernate 3.2.1
EJB3