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:
parent
b402af2eae
commit
2fef8a4acc
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue