HHH-4670:

- changing the way work unit collision is resolved: now work units are merged, the result is a work unit (possibly new)
- work units should behave as immutable objects
- test

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18208 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Adam Warski 2009-12-11 12:40:05 +00:00
parent b402af2eae
commit 2fef8a4acc
15 changed files with 160 additions and 122 deletions

View File

@ -86,22 +86,16 @@ public class AuditSync implements Synchronization {
if (usedIds.containsKey(usedIdsKey)) {
AuditWorkUnit other = usedIds.get(usedIdsKey);
// The entity with entityId has two work units; checking which one should be kept.
switch (vwu.dispatch(other)) {
case FIRST:
// Simply not adding the second
break;
AuditWorkUnit result = vwu.dispatch(other);
case SECOND:
removeWorkUnit(other);
usedIds.put(usedIdsKey, vwu);
workUnits.offer(vwu);
break;
if (result != other) {
removeWorkUnit(other);
case NONE:
removeWorkUnit(other);
break;
}
if (result != null) {
usedIds.put(usedIdsKey, result);
workUnits.offer(result);
} // else: a null result means that no work unit should be kept
} // else: the result is the same as the work unit already added. No need to do anything.
} else {
usedIds.put(usedIdsKey, vwu);
workUnits.offer(vwu);

View File

@ -41,8 +41,7 @@ public abstract class AbstractAuditWorkUnit implements AuditWorkUnit {
protected final SessionImplementor sessionImplementor;
protected final AuditConfiguration verCfg;
protected final Serializable id;
private final String entityName;
protected final String entityName;
private Object performedData;

View File

@ -38,15 +38,22 @@ import org.hibernate.persister.entity.EntityPersister;
* @author Adam Warski (adam at warski dot org)
*/
public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit {
private final Object[] state;
private final String[] propertyNames;
private final Map<String, Object> data;
public AddWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id, EntityPersister entityPersister, Object[] state) {
super(sessionImplementor, entityName, verCfg, id);
this.state = state;
this.propertyNames = entityPersister.getPropertyNames();
data = new HashMap<String, Object>();
verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,
entityPersister.getPropertyNames(), state, null);
}
public AddWorkUnit(SessionImplementor sessionImplementor, String entityName, AuditConfiguration verCfg,
Serializable id, Map<String, Object> data) {
super(sessionImplementor, entityName, verCfg, id);
this.data = data;
}
public boolean containsWork() {
@ -54,34 +61,30 @@ public class AddWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
}
public void perform(Session session, Object revisionData) {
Map<String, Object> data = new HashMap<String, Object>();
fillDataWithId(data, revisionData, RevisionType.ADD);
verCfg.getEntCfg().get(getEntityName()).getPropertyMapper().map(sessionImplementor, data,
propertyNames, state, null);
session.save(verCfg.getAuditEntCfg().getAuditEntityName(getEntityName()), data);
setPerformed(data);
}
public KeepCheckResult check(AddWorkUnit second) {
return KeepCheckResult.FIRST;
public AuditWorkUnit merge(AddWorkUnit second) {
return second;
}
public KeepCheckResult check(ModWorkUnit second) {
return KeepCheckResult.SECOND;
public AuditWorkUnit merge(ModWorkUnit second) {
return new AddWorkUnit(sessionImplementor, entityName, verCfg, id, second.getData());
}
public KeepCheckResult check(DelWorkUnit second) {
return KeepCheckResult.NONE;
public AuditWorkUnit merge(DelWorkUnit second) {
return null;
}
public KeepCheckResult check(CollectionChangeWorkUnit second) {
return KeepCheckResult.FIRST;
public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
return this;
}
public KeepCheckResult dispatch(KeepCheckVisitor first) {
return first.check(this);
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
}

View File

@ -28,7 +28,7 @@ import org.hibernate.Session;
/**
* @author Adam Warski (adam at warski dot org)
*/
public interface AuditWorkUnit extends KeepCheckVisitor, KeepCheckDispatcher {
public interface AuditWorkUnit extends WorkUnitMergeVisitor, WorkUnitMergeDispatcher {
Object getEntityId();
String getEntityName();

View File

@ -62,23 +62,23 @@ public class CollectionChangeWorkUnit extends AbstractAuditWorkUnit implements A
setPerformed(data);
}
public KeepCheckResult check(AddWorkUnit second) {
return KeepCheckResult.SECOND;
public AuditWorkUnit merge(AddWorkUnit second) {
return second;
}
public KeepCheckResult check(ModWorkUnit second) {
return KeepCheckResult.SECOND;
public AuditWorkUnit merge(ModWorkUnit second) {
return second;
}
public KeepCheckResult check(DelWorkUnit second) {
return KeepCheckResult.SECOND;
public AuditWorkUnit merge(DelWorkUnit second) {
return second;
}
public KeepCheckResult check(CollectionChangeWorkUnit second) {
return KeepCheckResult.FIRST;
public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
return this;
}
public KeepCheckResult dispatch(KeepCheckVisitor first) {
return first.check(this);
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
}

View File

@ -67,23 +67,23 @@ public class DelWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
setPerformed(data);
}
public KeepCheckResult check(AddWorkUnit second) {
return KeepCheckResult.NONE;
public AuditWorkUnit merge(AddWorkUnit second) {
return null;
}
public KeepCheckResult check(ModWorkUnit second) {
return KeepCheckResult.NONE;
public AuditWorkUnit merge(ModWorkUnit second) {
return null;
}
public KeepCheckResult check(DelWorkUnit second) {
return KeepCheckResult.FIRST;
public AuditWorkUnit merge(DelWorkUnit second) {
return this;
}
public KeepCheckResult check(CollectionChangeWorkUnit second) {
return KeepCheckResult.FIRST;
public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
return this;
}
public KeepCheckResult dispatch(KeepCheckVisitor first) {
return first.check(this);
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
}

View File

@ -1,35 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.synchronization.work;
/**
* Possible outcomes of selecting which work unit to keep, in case there are two work units for the same entity
* with the same id.
* @author Adam Warski (adam at warski dot org)
*/
public enum KeepCheckResult {
FIRST,
SECOND,
NONE
}

View File

@ -62,23 +62,27 @@ public class ModWorkUnit extends AbstractAuditWorkUnit implements AuditWorkUnit
setPerformed(data);
}
public KeepCheckResult check(AddWorkUnit second) {
return KeepCheckResult.FIRST;
public Map<String, Object> getData() {
return data;
}
public KeepCheckResult check(ModWorkUnit second) {
return KeepCheckResult.SECOND;
public AuditWorkUnit merge(AddWorkUnit second) {
return this;
}
public KeepCheckResult check(DelWorkUnit second) {
return KeepCheckResult.SECOND;
public AuditWorkUnit merge(ModWorkUnit second) {
return second;
}
public KeepCheckResult check(CollectionChangeWorkUnit second) {
return KeepCheckResult.FIRST;
public AuditWorkUnit merge(DelWorkUnit second) {
return second;
}
public KeepCheckResult dispatch(KeepCheckVisitor first) {
return first.check(this);
public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
return this;
}
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
return first.merge(this);
}
}

View File

@ -58,6 +58,16 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
.mapCollectionChanges(referencingPropertyName, collection, snapshot, id);
}
public PersistentCollectionChangeWorkUnit(SessionImplementor sessionImplementor, String entityName,
AuditConfiguration verCfg, Serializable id,
List<PersistentCollectionChangeData> collectionChanges,
String referencingPropertyName) {
super(sessionImplementor, entityName, verCfg, id);
this.collectionChanges = collectionChanges;
this.referencingPropertyName = referencingPropertyName;
}
public boolean containsWork() {
return collectionChanges != null && collectionChanges.size() != 0;
}
@ -83,23 +93,23 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
return collectionChanges;
}
public KeepCheckResult check(AddWorkUnit second) {
public AuditWorkUnit merge(AddWorkUnit second) {
return null;
}
public KeepCheckResult check(ModWorkUnit second) {
public AuditWorkUnit merge(ModWorkUnit second) {
return null;
}
public KeepCheckResult check(DelWorkUnit second) {
public AuditWorkUnit merge(DelWorkUnit second) {
return null;
}
public KeepCheckResult check(CollectionChangeWorkUnit second) {
public AuditWorkUnit merge(CollectionChangeWorkUnit second) {
return null;
}
public KeepCheckResult dispatch(KeepCheckVisitor first) {
public AuditWorkUnit dispatch(WorkUnitMergeVisitor first) {
if (first instanceof PersistentCollectionChangeWorkUnit) {
PersistentCollectionChangeWorkUnit original = (PersistentCollectionChangeWorkUnit) first;
@ -115,26 +125,25 @@ public class PersistentCollectionChangeWorkUnit extends AbstractAuditWorkUnit im
persistentCollectionChangeData);
}
// Storing the current changes
List<PersistentCollectionChangeData> newChanges = new ArrayList<PersistentCollectionChangeData>();
newChanges.addAll(collectionChanges);
// This will be the list with the resulting (merged) changes.
List<PersistentCollectionChangeData> mergedChanges = new ArrayList<PersistentCollectionChangeData>();
// And building the change list again
collectionChanges.clear();
// Including only those original changes, which are not overshadowed by new ones.
for (PersistentCollectionChangeData originalCollectionChangeData : original.getCollectionChanges()) {
if (!newChangesIdMap.containsKey(getOriginalId(originalCollectionChangeData))) {
collectionChanges.add(originalCollectionChangeData);
mergedChanges.add(originalCollectionChangeData);
}
}
// Finally adding all of the new changes to the end of the list
collectionChanges.addAll(newChanges);
mergedChanges.addAll(getCollectionChanges());
return new PersistentCollectionChangeWorkUnit(sessionImplementor, entityName, verCfg, id, mergedChanges,
referencingPropertyName);
} else {
throw new RuntimeException("Trying to merge a " + first + " with a PersitentCollectionChangeWorkUnit. " +
"This is not really possible.");
}
return KeepCheckResult.SECOND;
}
private Object getOriginalId(PersistentCollectionChangeData persistentCollectionChangeData) {

View File

@ -27,11 +27,11 @@ package org.hibernate.envers.synchronization.work;
* Visitor patter dispatcher.
* @author Adam Warski (adam at warski dot org)
*/
public interface KeepCheckDispatcher {
public interface WorkUnitMergeDispatcher {
/**
* Shuold be invoked on the second work unit.
* @param first First work unit (that is, the one added earlier).
* @return Which work unit should be kept.
* @return The work unit that is the result of the merge.
*/
KeepCheckResult dispatch(KeepCheckVisitor first);
AuditWorkUnit dispatch(WorkUnitMergeVisitor first);
}

View File

@ -27,9 +27,9 @@ package org.hibernate.envers.synchronization.work;
* Visitor pattern visitor. All methods should be invoked on the first work unit.
* @author Adam Warski (adam at warski dot org)
*/
public interface KeepCheckVisitor {
KeepCheckResult check(AddWorkUnit second);
KeepCheckResult check(ModWorkUnit second);
KeepCheckResult check(DelWorkUnit second);
KeepCheckResult check(CollectionChangeWorkUnit second);
public interface WorkUnitMergeVisitor {
AuditWorkUnit merge(AddWorkUnit second);
AuditWorkUnit merge(ModWorkUnit second);
AuditWorkUnit merge(DelWorkUnit second);
AuditWorkUnit merge(CollectionChangeWorkUnit second);
}

View File

@ -24,11 +24,15 @@
package org.hibernate.envers.test.integration.flush;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.RevisionType;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import org.hibernate.FlushMode;
@ -87,4 +91,16 @@ public class DoubleFlushAddMod extends AbstractFlushTest {
assert getAuditReader().find(StrTestEntity.class, id, 1).equals(ver1);
assert getAuditReader().find(StrTestEntity.class, id, 2).equals(ver2);
}
@Test
public void testRevisionTypes() {
@SuppressWarnings({"unchecked"}) List<Object[]> results =
getAuditReader().createQuery()
.forRevisionsOfEntity(StrTestEntity.class, false, true)
.add(AuditEntity.id().eq(id))
.getResultList();
assertEquals(results.get(0)[2], RevisionType.ADD);
assertEquals(results.get(1)[2], RevisionType.MOD);
}
}

View File

@ -24,11 +24,15 @@
package org.hibernate.envers.test.integration.flush;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.RevisionType;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import org.hibernate.FlushMode;
@ -83,4 +87,16 @@ public class DoubleFlushModDel extends AbstractFlushTest {
assert getAuditReader().find(StrTestEntity.class, id, 1).equals(ver1);
assert getAuditReader().find(StrTestEntity.class, id, 2) == null;
}
@Test
public void testRevisionTypes() {
@SuppressWarnings({"unchecked"}) List<Object[]> results =
getAuditReader().createQuery()
.forRevisionsOfEntity(StrTestEntity.class, false, true)
.add(AuditEntity.id().eq(id))
.getResultList();
assertEquals(results.get(0)[2], RevisionType.ADD);
assertEquals(results.get(1)[2], RevisionType.DEL);
}
}

View File

@ -24,11 +24,15 @@
package org.hibernate.envers.test.integration.flush;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.RevisionType;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import org.hibernate.FlushMode;
@ -84,4 +88,16 @@ public class DoubleFlushModMod extends AbstractFlushTest {
assert getAuditReader().find(StrTestEntity.class, id, 1).equals(ver1);
assert getAuditReader().find(StrTestEntity.class, id, 2).equals(ver2);
}
@Test
public void testRevisionTypes() {
@SuppressWarnings({"unchecked"}) List<Object[]> results =
getAuditReader().createQuery()
.forRevisionsOfEntity(StrTestEntity.class, false, true)
.add(AuditEntity.id().eq(id))
.getResultList();
assertEquals(results.get(0)[2], RevisionType.ADD);
assertEquals(results.get(1)[2], RevisionType.MOD);
}
}

View File

@ -24,11 +24,15 @@
package org.hibernate.envers.test.integration.flush;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.RevisionType;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import org.hibernate.FlushMode;
@ -96,4 +100,16 @@ public class ManualFlush extends AbstractFlushTest {
public void testCurrent() {
assert getEntityManager().find(StrTestEntity.class, id).equals(new StrTestEntity("z", id));
}
@Test
public void testRevisionTypes() {
@SuppressWarnings({"unchecked"}) List<Object[]> results =
getAuditReader().createQuery()
.forRevisionsOfEntity(StrTestEntity.class, false, true)
.add(AuditEntity.id().eq(id))
.getResultList();
assertEquals(results.get(0)[2], RevisionType.ADD);
assertEquals(results.get(1)[2], RevisionType.MOD);
}
}