HHH-3543: using BeforeTransactionCompletionProcess instead of a transaction synchronization. That way exceptions are not swallowed if one is thrown in the audit synchronization/process, and the tx is rolled back
git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@19180 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
parent
8403e128f6
commit
d4295cc604
|
@ -30,7 +30,7 @@ import java.util.WeakHashMap;
|
|||
import org.hibernate.envers.entities.EntitiesConfigurations;
|
||||
import org.hibernate.envers.revisioninfo.RevisionInfoNumberReader;
|
||||
import org.hibernate.envers.revisioninfo.RevisionInfoQueryCreator;
|
||||
import org.hibernate.envers.synchronization.AuditSyncManager;
|
||||
import org.hibernate.envers.synchronization.AuditProcessManager;
|
||||
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.cfg.AnnotationConfiguration;
|
||||
|
@ -42,7 +42,7 @@ import org.hibernate.annotations.common.reflection.ReflectionManager;
|
|||
public class AuditConfiguration {
|
||||
private final GlobalConfiguration globalCfg;
|
||||
private final AuditEntitiesConfiguration auditEntCfg;
|
||||
private final AuditSyncManager auditSyncManager;
|
||||
private final AuditProcessManager auditProcessManager;
|
||||
private final EntitiesConfigurations entCfg;
|
||||
private final RevisionInfoQueryCreator revisionInfoQueryCreator;
|
||||
private final RevisionInfoNumberReader revisionInfoNumberReader;
|
||||
|
@ -51,8 +51,8 @@ public class AuditConfiguration {
|
|||
return auditEntCfg;
|
||||
}
|
||||
|
||||
public AuditSyncManager getSyncManager() {
|
||||
return auditSyncManager;
|
||||
public AuditProcessManager getSyncManager() {
|
||||
return auditProcessManager;
|
||||
}
|
||||
|
||||
public GlobalConfiguration getGlobalCfg() {
|
||||
|
@ -80,7 +80,7 @@ public class AuditConfiguration {
|
|||
RevisionInfoConfigurationResult revInfoCfgResult = revInfoCfg.configure(cfg, reflectionManager);
|
||||
auditEntCfg = new AuditEntitiesConfiguration(properties, revInfoCfgResult.getRevisionInfoEntityName());
|
||||
globalCfg = new GlobalConfiguration(properties);
|
||||
auditSyncManager = new AuditSyncManager(revInfoCfgResult.getRevisionInfoGenerator());
|
||||
auditProcessManager = new AuditProcessManager(revInfoCfgResult.getRevisionInfoGenerator());
|
||||
revisionInfoQueryCreator = revInfoCfgResult.getRevisionInfoQueryCreator();
|
||||
revisionInfoNumberReader = revInfoCfgResult.getRevisionInfoNumberReader();
|
||||
entCfg = new EntitiesConfigurator().configure(cfg, reflectionManager, globalCfg, auditEntCfg,
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.hibernate.envers.entities.RelationDescription;
|
|||
import org.hibernate.envers.entities.RelationType;
|
||||
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
|
||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||
import org.hibernate.envers.synchronization.AuditSync;
|
||||
import org.hibernate.envers.synchronization.AuditProcess;
|
||||
import org.hibernate.envers.synchronization.work.*;
|
||||
import org.hibernate.envers.tools.Tools;
|
||||
import org.hibernate.envers.RevisionType;
|
||||
|
@ -68,7 +68,7 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
|
||||
private AuditConfiguration verCfg;
|
||||
|
||||
private void generateBidirectionalCollectionChangeWorkUnits(AuditSync verSync, EntityPersister entityPersister,
|
||||
private void generateBidirectionalCollectionChangeWorkUnits(AuditProcess auditProcess, EntityPersister entityPersister,
|
||||
String entityName, Object[] newState, Object[] oldState,
|
||||
SessionImplementor session) {
|
||||
// Checking if this is enabled in configuration ...
|
||||
|
@ -112,7 +112,7 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
id = (Serializable) idMapper.mapToIdFromEntity(newValue);
|
||||
}
|
||||
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(session, toEntityName, verCfg, id, newValue));
|
||||
auditProcess.addWorkUnit(new CollectionChangeWorkUnit(session, toEntityName, verCfg, id, newValue));
|
||||
}
|
||||
|
||||
if (oldValue != null) {
|
||||
|
@ -132,7 +132,7 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
id = (Serializable) idMapper.mapToIdFromEntity(oldValue);
|
||||
}
|
||||
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(session, toEntityName, verCfg, id, oldValue));
|
||||
auditProcess.addWorkUnit(new CollectionChangeWorkUnit(session, toEntityName, verCfg, id, oldValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,14 +143,14 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
String entityName = event.getPersister().getEntityName();
|
||||
|
||||
if (verCfg.getEntCfg().isVersioned(entityName)) {
|
||||
AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
|
||||
AuditProcess auditProcess = verCfg.getSyncManager().get(event.getSession());
|
||||
|
||||
AuditWorkUnit workUnit = new AddWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
|
||||
event.getId(), event.getPersister(), event.getState());
|
||||
verSync.addWorkUnit(workUnit);
|
||||
auditProcess.addWorkUnit(workUnit);
|
||||
|
||||
if (workUnit.containsWork()) {
|
||||
generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, event.getState(),
|
||||
generateBidirectionalCollectionChangeWorkUnits(auditProcess, event.getPersister(), entityName, event.getState(),
|
||||
null, event.getSession());
|
||||
}
|
||||
}
|
||||
|
@ -160,14 +160,14 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
String entityName = event.getPersister().getEntityName();
|
||||
|
||||
if (verCfg.getEntCfg().isVersioned(entityName)) {
|
||||
AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
|
||||
AuditProcess auditProcess = verCfg.getSyncManager().get(event.getSession());
|
||||
|
||||
AuditWorkUnit workUnit = new ModWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
|
||||
event.getId(), event.getPersister(), event.getState(), event.getOldState());
|
||||
verSync.addWorkUnit(workUnit);
|
||||
auditProcess.addWorkUnit(workUnit);
|
||||
|
||||
if (workUnit.containsWork()) {
|
||||
generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, event.getState(),
|
||||
generateBidirectionalCollectionChangeWorkUnits(auditProcess, event.getPersister(), entityName, event.getState(),
|
||||
event.getOldState(), event.getSession());
|
||||
}
|
||||
}
|
||||
|
@ -177,20 +177,20 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
String entityName = event.getPersister().getEntityName();
|
||||
|
||||
if (verCfg.getEntCfg().isVersioned(entityName)) {
|
||||
AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
|
||||
AuditProcess auditProcess = verCfg.getSyncManager().get(event.getSession());
|
||||
|
||||
AuditWorkUnit workUnit = new DelWorkUnit(event.getSession(), event.getPersister().getEntityName(), verCfg,
|
||||
event.getId(), event.getPersister(), event.getDeletedState());
|
||||
verSync.addWorkUnit(workUnit);
|
||||
auditProcess.addWorkUnit(workUnit);
|
||||
|
||||
if (workUnit.containsWork()) {
|
||||
generateBidirectionalCollectionChangeWorkUnits(verSync, event.getPersister(), entityName, null,
|
||||
generateBidirectionalCollectionChangeWorkUnits(auditProcess, event.getPersister(), entityName, null,
|
||||
event.getDeletedState(), event.getSession());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateBidirectionalCollectionChangeWorkUnits(AuditSync verSync, AbstractCollectionEvent event,
|
||||
private void generateBidirectionalCollectionChangeWorkUnits(AuditProcess auditProcess, AbstractCollectionEvent event,
|
||||
PersistentCollectionChangeWorkUnit workUnit,
|
||||
RelationDescription rd) {
|
||||
// Checking if this is enabled in configuration ...
|
||||
|
@ -209,13 +209,13 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
Object relatedObj = changeData.getChangedElement();
|
||||
Serializable relatedId = (Serializable) relatedIdMapper.mapToIdFromEntity(relatedObj);
|
||||
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), relatedEntityName, verCfg,
|
||||
auditProcess.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), relatedEntityName, verCfg,
|
||||
relatedId, relatedObj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateFakeBidirecationalRelationWorkUnits(AuditSync verSync, PersistentCollection newColl, Serializable oldColl,
|
||||
private void generateFakeBidirecationalRelationWorkUnits(AuditProcess auditProcess, PersistentCollection newColl, Serializable oldColl,
|
||||
String collectionEntityName, String referencingPropertyName,
|
||||
AbstractCollectionEvent event,
|
||||
RelationDescription rd) {
|
||||
|
@ -242,13 +242,13 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
AuditWorkUnit nestedWorkUnit = new CollectionChangeWorkUnit(event.getSession(), realRelatedEntityName, verCfg,
|
||||
relatedId, relatedObj);
|
||||
|
||||
verSync.addWorkUnit(new FakeBidirectionalRelationWorkUnit(event.getSession(), realRelatedEntityName, verCfg,
|
||||
auditProcess.addWorkUnit(new FakeBidirectionalRelationWorkUnit(event.getSession(), realRelatedEntityName, verCfg,
|
||||
relatedId, referencingPropertyName, event.getAffectedOwnerOrNull(), rd, revType,
|
||||
changeData.getChangedElementIndex(), nestedWorkUnit));
|
||||
}
|
||||
|
||||
// We also have to generate a collection change work unit for the owning entity.
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), collectionEntityName, verCfg,
|
||||
auditProcess.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), collectionEntityName, verCfg,
|
||||
event.getAffectedOwnerIdOrNull(), event.getAffectedOwnerOrNull()));
|
||||
}
|
||||
|
||||
|
@ -257,7 +257,7 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
String entityName = event.getAffectedOwnerEntityName();
|
||||
|
||||
if (verCfg.getEntCfg().isVersioned(entityName)) {
|
||||
AuditSync verSync = verCfg.getSyncManager().get(event.getSession());
|
||||
AuditProcess auditProcess = verCfg.getSyncManager().get(event.getSession());
|
||||
|
||||
String ownerEntityName = ((AbstractCollectionPersister) collectionEntry.getLoadedPersister()).getOwnerEntityName();
|
||||
String referencingPropertyName = collectionEntry.getRole().substring(ownerEntityName.length() + 1);
|
||||
|
@ -266,20 +266,20 @@ public class AuditEventListener implements PostInsertEventListener, PostUpdateEv
|
|||
// null in case of collections of non-entities.
|
||||
RelationDescription rd = verCfg.getEntCfg().get(entityName).getRelationDescription(referencingPropertyName);
|
||||
if (rd != null && rd.getMappedByPropertyName() != null) {
|
||||
generateFakeBidirecationalRelationWorkUnits(verSync, newColl, oldColl, entityName,
|
||||
generateFakeBidirecationalRelationWorkUnits(auditProcess, newColl, oldColl, entityName,
|
||||
referencingPropertyName, event, rd);
|
||||
} else {
|
||||
PersistentCollectionChangeWorkUnit workUnit = new PersistentCollectionChangeWorkUnit(event.getSession(),
|
||||
entityName, verCfg, newColl, collectionEntry, oldColl, event.getAffectedOwnerIdOrNull(),
|
||||
referencingPropertyName);
|
||||
verSync.addWorkUnit(workUnit);
|
||||
auditProcess.addWorkUnit(workUnit);
|
||||
|
||||
if (workUnit.containsWork()) {
|
||||
// There are some changes: a revision needs also be generated for the collection owner
|
||||
verSync.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), event.getAffectedOwnerEntityName(),
|
||||
auditProcess.addWorkUnit(new CollectionChangeWorkUnit(event.getSession(), event.getAffectedOwnerEntityName(),
|
||||
verCfg, event.getAffectedOwnerIdOrNull(), event.getAffectedOwnerOrNull()));
|
||||
|
||||
generateBidirectionalCollectionChangeWorkUnits(verSync, event, workUnit, rd);
|
||||
generateBidirectionalCollectionChangeWorkUnits(auditProcess, event, workUnit, rd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@ import org.hibernate.envers.query.AuditEntity;
|
|||
import org.hibernate.envers.query.AuditQueryCreator;
|
||||
import static org.hibernate.envers.tools.ArgumentsTools.checkNotNull;
|
||||
import static org.hibernate.envers.tools.ArgumentsTools.checkPositive;
|
||||
import org.hibernate.envers.synchronization.AuditSync;
|
||||
|
||||
import org.hibernate.envers.synchronization.AuditProcess;
|
||||
|
||||
import org.hibernate.NonUniqueResultException;
|
||||
import org.hibernate.Query;
|
||||
|
@ -201,10 +202,10 @@ public class AuditReaderImpl implements AuditReaderImplementor {
|
|||
}
|
||||
|
||||
// Obtaining the current audit sync
|
||||
AuditSync auditSync = verCfg.getSyncManager().get((EventSource) session);
|
||||
AuditProcess auditProcess = verCfg.getSyncManager().get((EventSource) session);
|
||||
|
||||
// And getting the current revision data
|
||||
return (T) auditSync.getCurrentRevisionData(session, persist);
|
||||
return (T) auditProcess.getCurrentRevisionData(session, persist);
|
||||
}
|
||||
|
||||
public AuditQueryCreator createQuery() {
|
||||
|
|
|
@ -27,38 +27,31 @@ import java.util.HashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import javax.transaction.Synchronization;
|
||||
|
||||
import org.hibernate.action.BeforeTransactionCompletionProcess;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.envers.revisioninfo.RevisionInfoGenerator;
|
||||
import org.hibernate.envers.synchronization.work.AuditWorkUnit;
|
||||
import org.hibernate.envers.tools.Pair;
|
||||
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.event.EventSource;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public class AuditSync implements Synchronization {
|
||||
public class AuditProcess implements BeforeTransactionCompletionProcess {
|
||||
private final RevisionInfoGenerator revisionInfoGenerator;
|
||||
private final AuditSyncManager manager;
|
||||
private final EventSource session;
|
||||
|
||||
private final Transaction transaction;
|
||||
private final LinkedList<AuditWorkUnit> workUnits;
|
||||
private final Queue<AuditWorkUnit> undoQueue;
|
||||
private final Map<Pair<String, Object>, AuditWorkUnit> usedIds;
|
||||
|
||||
private Object revisionData;
|
||||
|
||||
public AuditSync(AuditSyncManager manager, EventSource session, RevisionInfoGenerator revisionInfoGenerator) {
|
||||
this.manager = manager;
|
||||
this.session = session;
|
||||
public AuditProcess(RevisionInfoGenerator revisionInfoGenerator) {
|
||||
this.revisionInfoGenerator = revisionInfoGenerator;
|
||||
|
||||
transaction = session.getTransaction();
|
||||
workUnits = new LinkedList<AuditWorkUnit>();
|
||||
undoQueue = new LinkedList<AuditWorkUnit>();
|
||||
usedIds = new HashMap<Pair<String, Object>, AuditWorkUnit>();
|
||||
|
@ -134,47 +127,30 @@ public class AuditSync implements Synchronization {
|
|||
return revisionData;
|
||||
}
|
||||
|
||||
public void beforeCompletion() {
|
||||
public void doBeforeTransactionCompletion(SessionImplementor session) {
|
||||
if (workUnits.size() == 0 && undoQueue.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// see: http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4178431
|
||||
if (FlushMode.isManualFlushMode(session.getFlushMode()) || session.isClosed()) {
|
||||
Session temporarySession = null;
|
||||
try {
|
||||
temporarySession = session.getFactory().openTemporarySession();
|
||||
|
||||
executeInSession(temporarySession);
|
||||
|
||||
temporarySession.flush();
|
||||
} finally {
|
||||
if (temporarySession != null) {
|
||||
temporarySession.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
executeInSession(session);
|
||||
|
||||
// Explicity flushing the session, as the auto-flush may have already happened.
|
||||
session.flush();
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// Rolling back the transaction in case of any exceptions
|
||||
//noinspection finally
|
||||
// see: http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4178431
|
||||
if (FlushMode.isManualFlushMode(session.getFlushMode())) {
|
||||
Session temporarySession = null;
|
||||
try {
|
||||
if (session.getTransaction().isActive()) {
|
||||
session.getTransaction().rollback();
|
||||
}
|
||||
} finally {
|
||||
//noinspection ThrowFromFinallyBlock
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
temporarySession = session.getFactory().openTemporarySession();
|
||||
|
||||
public void afterCompletion(int i) {
|
||||
manager.remove(transaction);
|
||||
executeInSession(temporarySession);
|
||||
|
||||
temporarySession.flush();
|
||||
} finally {
|
||||
if (temporarySession != null) {
|
||||
temporarySession.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
executeInSession((Session) session);
|
||||
|
||||
// Explicity flushing the session, as the auto-flush may have already happened.
|
||||
session.flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ package org.hibernate.envers.synchronization;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.hibernate.action.AfterTransactionCompletionProcess;
|
||||
import org.hibernate.engine.SessionImplementor;
|
||||
import org.hibernate.envers.revisioninfo.RevisionInfoGenerator;
|
||||
|
||||
import org.hibernate.Transaction;
|
||||
|
@ -34,32 +36,33 @@ import org.hibernate.event.EventSource;
|
|||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
*/
|
||||
public class AuditSyncManager {
|
||||
private final Map<Transaction, AuditSync> auditSyncs;
|
||||
public class AuditProcessManager {
|
||||
private final Map<Transaction, AuditProcess> auditProcesses;
|
||||
private final RevisionInfoGenerator revisionInfoGenerator;
|
||||
|
||||
public AuditSyncManager(RevisionInfoGenerator revisionInfoGenerator) {
|
||||
auditSyncs = new ConcurrentHashMap<Transaction, AuditSync>();
|
||||
public AuditProcessManager(RevisionInfoGenerator revisionInfoGenerator) {
|
||||
auditProcesses = new ConcurrentHashMap<Transaction, AuditProcess>();
|
||||
|
||||
this.revisionInfoGenerator = revisionInfoGenerator;
|
||||
}
|
||||
|
||||
public AuditSync get(EventSource session) {
|
||||
Transaction transaction = session.getTransaction();
|
||||
public AuditProcess get(EventSource session) {
|
||||
final Transaction transaction = session.getTransaction();
|
||||
|
||||
AuditSync verSync = auditSyncs.get(transaction);
|
||||
if (verSync == null) {
|
||||
AuditProcess auditProcess = auditProcesses.get(transaction);
|
||||
if (auditProcess == null) {
|
||||
// No worries about registering a transaction twice - a transaction is single thread
|
||||
verSync = new AuditSync(this, session, revisionInfoGenerator);
|
||||
auditSyncs.put(transaction, verSync);
|
||||
auditProcess = new AuditProcess(revisionInfoGenerator);
|
||||
auditProcesses.put(transaction, auditProcess);
|
||||
|
||||
transaction.registerSynchronization(verSync);
|
||||
session.getActionQueue().registerProcess(auditProcess);
|
||||
session.getActionQueue().registerProcess(new AfterTransactionCompletionProcess() {
|
||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||
auditProcesses.remove(transaction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return verSync;
|
||||
}
|
||||
|
||||
public void remove(Transaction transaction) {
|
||||
auditSyncs.remove(transaction);
|
||||
return auditProcess;
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ public class ExceptionListener extends AbstractEntityTest {
|
|||
cfg.addAnnotatedClass(ExceptionListenerRevEntity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expectedExceptions = RuntimeException.class)
|
||||
public void testTransactionRollback() throws InterruptedException {
|
||||
// Trying to persist an entity - however the listener should throw an exception, so the entity
|
||||
// shouldn't be persisted
|
||||
|
@ -49,9 +49,12 @@ public class ExceptionListener extends AbstractEntityTest {
|
|||
StrTestEntity te = new StrTestEntity("x");
|
||||
em.persist(te);
|
||||
em.getTransaction().commit();
|
||||
}
|
||||
|
||||
@Test(dependsOnMethods = "testTransactionRollback")
|
||||
public void testDataNotPersisted() {
|
||||
// Checking if the entity became persisted
|
||||
em = getEntityManager();
|
||||
EntityManager em = getEntityManager();
|
||||
em.getTransaction().begin();
|
||||
Long count = (Long) em.createQuery("select count(s) from StrTestEntity s where s.str = 'x'").getSingleResult();
|
||||
assert count == 0l;
|
||||
|
|
Loading…
Reference in New Issue