Ran into this issue today:
<openjpa-1.2.2-r422266:898935 fatal user error> org.apache.openjpa.persistence.InvalidStateException: Detected reentrant flush. Make sure your flush-time instance callback methods or event listeners do not invoke any operations that require the in-progress flush to complete. at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:1923) at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1698) at org.apache.openjpa.kernel.QueryImpl.isInMemory(QueryImpl.java:956) at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:838) at org.apache.openjpa.kernel.QueryImpl.execute(QueryImpl.java:779) at org.apache.openjpa.kernel.DelegatingQuery.execute(DelegatingQuery.java:525)
This is a tricky exception since the verbose description does not clarify the intent.
The problem:
I had to write my own Auditing since we weren’t using Hibernate (and so no Envers) but openjpa 1.2.2. Don’t ask me why. It’s a big financial company and they like Websphere 7 (which “ships” openjpa 1.2).
A sample “Profile” entity
@Entity
@EntityListeners({ProfileAuditListener.class})
public class Profile implements Serializable {
...
}
The standard JPA Entity Listener:
@Named
public class ProfileAuditListener implements ApplicationContextAware {
private ProfileAuditDao auditDao;
@PostUpdate
@Transactional
public void postUpdate(Profile newProfile) {
ProfileAudit audit = new ProfileAudit();
...
...
Profile oldProfile = profileDao.read(newProfile.getEmail());
// InvalidStateException thrown above
...
// Compare field by field for editable fields
...
auditDao.audit(audit);
}
}
A simple Dao
@Repository("profileDao")
public class JPAProfileDao implements ProfileDao {
@Transactional(readOnly = true)
public Profile read(String email) {
List<Profile> profiles = em.createNamedQuery("findByEmail").setParameter("email", email);
if (profiles.size() != 0) {
return profiles.get(0);
}
return null;
}
}
As you can see, nothing fancy here. An Audit interceptor trying to see the difference in ‘Profile’ properties just after updated the entity in db. However an exception is thrown where I execute a read-only Transactional query.
Explanation:
The exception indicates that the event listener might be executing a flush accidentally from another operation and OpenJPA doesnt allow a flush to occur during a flush. Wait, a flush? But I’m not flushing anywhere, in fact — I think it’s all default flushing.
That’s the catch. The default querying in openjpa is FlushModeType.AUTO and that causes a flush to occur even if it’s a @Transactional(readOnly=true)
Solution:
set the flush mode to FlushModeType.COMMIT on that particular query.
You can also set a system wide property like setting false flushBeforeQueries = true in persistence.xml but I wouldn’t recommend that unless you want this behavior explicitly. For instance, my unit tests needed the auto flush to work since I persist and read in the same transaction (Spring Transactional tests).
In general, your use case might be different from mine but the underlying concept is same. Look for the accidental flushes from openjpa. If in doubt, always tune up the DEBUG logging.
One Comment
Thanks! This really solve the problem.
Post a Comment