HHH-6614 - Fix and test
This commit is contained in:
parent
0418725042
commit
4e5e0ddd60
|
@ -32,6 +32,7 @@ import org.hibernate.envers.EntityTrackingRevisionListener;
|
||||||
import org.hibernate.envers.RevisionListener;
|
import org.hibernate.envers.RevisionListener;
|
||||||
import org.hibernate.envers.RevisionType;
|
import org.hibernate.envers.RevisionType;
|
||||||
import org.hibernate.envers.entities.PropertyData;
|
import org.hibernate.envers.entities.PropertyData;
|
||||||
|
import org.hibernate.envers.synchronization.SessionCacheCleaner;
|
||||||
import org.hibernate.envers.tools.reflection.ReflectionTools;
|
import org.hibernate.envers.tools.reflection.ReflectionTools;
|
||||||
import org.hibernate.property.Setter;
|
import org.hibernate.property.Setter;
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
||||||
private final Setter revisionTimestampSetter;
|
private final Setter revisionTimestampSetter;
|
||||||
private final boolean timestampAsDate;
|
private final boolean timestampAsDate;
|
||||||
private final Class<?> revisionInfoClass;
|
private final Class<?> revisionInfoClass;
|
||||||
|
private final SessionCacheCleaner sessionCacheCleaner;
|
||||||
|
|
||||||
public DefaultRevisionInfoGenerator(String revisionInfoEntityName, Class<?> revisionInfoClass,
|
public DefaultRevisionInfoGenerator(String revisionInfoEntityName, Class<?> revisionInfoClass,
|
||||||
Class<? extends RevisionListener> listenerClass,
|
Class<? extends RevisionListener> listenerClass,
|
||||||
|
@ -69,10 +71,13 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
||||||
// Default listener - none
|
// Default listener - none
|
||||||
listener = null;
|
listener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionCacheCleaner = new SessionCacheCleaner();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveRevisionData(Session session, Object revisionData) {
|
public void saveRevisionData(Session session, Object revisionData) {
|
||||||
session.save(revisionInfoEntityName, revisionData);
|
session.save(revisionInfoEntityName, revisionData);
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval(session, revisionData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object generate() {
|
public Object generate() {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.hibernate.envers.configuration.GlobalConfiguration;
|
||||||
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
|
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
|
||||||
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
|
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
|
||||||
import org.hibernate.envers.entities.mapper.relation.MiddleIdData;
|
import org.hibernate.envers.entities.mapper.relation.MiddleIdData;
|
||||||
|
import org.hibernate.envers.synchronization.SessionCacheCleaner;
|
||||||
import org.hibernate.envers.tools.query.Parameters;
|
import org.hibernate.envers.tools.query.Parameters;
|
||||||
import org.hibernate.envers.tools.query.QueryBuilder;
|
import org.hibernate.envers.tools.query.QueryBuilder;
|
||||||
|
|
||||||
|
@ -18,14 +19,22 @@ import org.hibernate.envers.tools.query.QueryBuilder;
|
||||||
* @author Stephanie Pau
|
* @author Stephanie Pau
|
||||||
*/
|
*/
|
||||||
public class DefaultAuditStrategy implements AuditStrategy {
|
public class DefaultAuditStrategy implements AuditStrategy {
|
||||||
|
private final SessionCacheCleaner sessionCacheCleaner;
|
||||||
|
|
||||||
|
public DefaultAuditStrategy() {
|
||||||
|
sessionCacheCleaner = new SessionCacheCleaner();
|
||||||
|
}
|
||||||
|
|
||||||
public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data,
|
public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data,
|
||||||
Object revision) {
|
Object revision) {
|
||||||
session.save(auditCfg.getAuditEntCfg().getAuditEntityName(entityName), data);
|
session.save(auditCfg.getAuditEntCfg().getAuditEntityName(entityName), data);
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void performCollectionChange(Session session, AuditConfiguration auditCfg,
|
public void performCollectionChange(Session session, AuditConfiguration auditCfg,
|
||||||
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
|
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
|
||||||
session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());
|
session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval(session, persistentCollectionChangeData.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
|
||||||
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
import org.hibernate.envers.entities.mapper.id.IdMapper;
|
||||||
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
|
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
|
||||||
import org.hibernate.envers.entities.mapper.relation.MiddleIdData;
|
import org.hibernate.envers.entities.mapper.relation.MiddleIdData;
|
||||||
|
import org.hibernate.envers.synchronization.SessionCacheCleaner;
|
||||||
import org.hibernate.envers.tools.query.Parameters;
|
import org.hibernate.envers.tools.query.Parameters;
|
||||||
import org.hibernate.envers.tools.query.QueryBuilder;
|
import org.hibernate.envers.tools.query.QueryBuilder;
|
||||||
import org.hibernate.property.Getter;
|
import org.hibernate.property.Getter;
|
||||||
|
@ -45,6 +46,12 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
||||||
/** getter for the revision entity field annotated with @RevisionTimestamp */
|
/** getter for the revision entity field annotated with @RevisionTimestamp */
|
||||||
private Getter revisionTimestampGetter = null;
|
private Getter revisionTimestampGetter = null;
|
||||||
|
|
||||||
|
private final SessionCacheCleaner sessionCacheCleaner;
|
||||||
|
|
||||||
|
public ValidityAuditStrategy() {
|
||||||
|
sessionCacheCleaner = new SessionCacheCleaner();
|
||||||
|
}
|
||||||
|
|
||||||
public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data,
|
public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data,
|
||||||
Object revision) {
|
Object revision) {
|
||||||
AuditEntitiesConfiguration audEntCfg = auditCfg.getAuditEntCfg();
|
AuditEntitiesConfiguration audEntCfg = auditCfg.getAuditEntCfg();
|
||||||
|
@ -73,6 +80,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
||||||
|
|
||||||
// Save the audit data
|
// Save the audit data
|
||||||
session.save(auditedEntityName, data);
|
session.save(auditedEntityName, data);
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked"})
|
@SuppressWarnings({"unchecked"})
|
||||||
|
@ -105,6 +113,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
||||||
|
|
||||||
// Save the audit data
|
// Save the audit data
|
||||||
session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());
|
session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval(session, persistentCollectionChangeData.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addEndRevisionNulLRestriction(AuditConfiguration auditCfg, QueryBuilder qb) {
|
private void addEndRevisionNulLRestriction(AuditConfiguration auditCfg, QueryBuilder qb) {
|
||||||
|
@ -177,7 +186,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
||||||
|
|
||||||
// Saving the previous version
|
// Saving the previous version
|
||||||
session.save(auditedEntityName, previousData);
|
session.save(auditedEntityName, previousData);
|
||||||
|
sessionCacheCleaner.scheduleAuditDataRemoval(session, previousData);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Cannot find previous revision for entity " + auditedEntityName + " and id " + id);
|
throw new RuntimeException("Cannot find previous revision for entity " + auditedEntityName + " and id " + id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.hibernate.envers.synchronization;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.action.AfterTransactionCompletionProcess;
|
||||||
|
import org.hibernate.engine.SessionImplementor;
|
||||||
|
import org.hibernate.event.EventSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class responsible for evicting audit data entries that have been stored in the session level cache.
|
||||||
|
* This operation increases Envers performance in case of massive entity updates without clearing persistence context.
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class SessionCacheCleaner {
|
||||||
|
/**
|
||||||
|
* Schedules audit data removal from session level cache after transaction completion. The operation is performed
|
||||||
|
* regardless of commit success.
|
||||||
|
* @param session Active Hibernate session.
|
||||||
|
* @param data Audit data that shall be evicted (e.g. revision data or entity snapshot)
|
||||||
|
*/
|
||||||
|
public void scheduleAuditDataRemoval(final Session session, final Object data) {
|
||||||
|
((EventSource) session).getActionQueue().registerProcess(new AfterTransactionCompletionProcess() {
|
||||||
|
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||||
|
((Session) session).evict(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.hibernate.envers.test.performance;
|
||||||
|
|
||||||
|
import org.hibernate.MappingException;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.engine.EntityEntry;
|
||||||
|
import org.hibernate.engine.PersistenceContext;
|
||||||
|
import org.hibernate.engine.SessionImplementor;
|
||||||
|
import org.hibernate.envers.DefaultRevisionEntity;
|
||||||
|
import org.hibernate.envers.test.AbstractSessionTest;
|
||||||
|
import org.hibernate.envers.test.entities.StrTestEntity;
|
||||||
|
import org.hibernate.envers.test.entities.onetomany.SetRefEdEntity;
|
||||||
|
import org.hibernate.envers.test.entities.onetomany.SetRefIngEntity;
|
||||||
|
import org.testng.Assert;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||||
|
*/
|
||||||
|
public class EvictAuditDataAfterCommitTest extends AbstractSessionTest {
|
||||||
|
@Override
|
||||||
|
protected void initMappings() throws MappingException, URISyntaxException {
|
||||||
|
config.addAnnotatedClass(StrTestEntity.class);
|
||||||
|
config.addAnnotatedClass(SetRefEdEntity.class);
|
||||||
|
config.addAnnotatedClass(SetRefIngEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionCacheClear() {
|
||||||
|
getSession().getTransaction().begin();
|
||||||
|
StrTestEntity ste = new StrTestEntity("data");
|
||||||
|
getSession().persist(ste);
|
||||||
|
getSession().getTransaction().commit();
|
||||||
|
checkEmptyAuditSessionCache(getSession(), "org.hibernate.envers.test.entities.StrTestEntity_AUD");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionCacheCollectionClear() {
|
||||||
|
final String[] auditEntityNames = new String[] {"org.hibernate.envers.test.entities.onetomany.SetRefEdEntity_AUD",
|
||||||
|
"org.hibernate.envers.test.entities.onetomany.SetRefIngEntity_AUD"};
|
||||||
|
|
||||||
|
SetRefEdEntity ed1 = new SetRefEdEntity(1, "data_ed_1");
|
||||||
|
SetRefEdEntity ed2 = new SetRefEdEntity(2, "data_ed_2");
|
||||||
|
SetRefIngEntity ing1 = new SetRefIngEntity(3, "data_ing_1");
|
||||||
|
SetRefIngEntity ing2 = new SetRefIngEntity(4, "data_ing_2");
|
||||||
|
|
||||||
|
getSession().getTransaction().begin();
|
||||||
|
getSession().persist(ed1);
|
||||||
|
getSession().persist(ed2);
|
||||||
|
getSession().persist(ing1);
|
||||||
|
getSession().persist(ing2);
|
||||||
|
getSession().getTransaction().commit();
|
||||||
|
checkEmptyAuditSessionCache(getSession(), auditEntityNames);
|
||||||
|
|
||||||
|
getSession().getTransaction().begin();
|
||||||
|
ed1 = (SetRefEdEntity) getSession().load(SetRefEdEntity.class, ed1.getId());
|
||||||
|
ing1.setReference(ed1);
|
||||||
|
ing2.setReference(ed1);
|
||||||
|
getSession().getTransaction().commit();
|
||||||
|
checkEmptyAuditSessionCache(getSession(), auditEntityNames);
|
||||||
|
|
||||||
|
getSession().getTransaction().begin();
|
||||||
|
ed2 = (SetRefEdEntity) getSession().load(SetRefEdEntity.class, ed2.getId());
|
||||||
|
Set<SetRefIngEntity> reffering = new HashSet<SetRefIngEntity>();
|
||||||
|
reffering.add(ing1);
|
||||||
|
reffering.add(ing2);
|
||||||
|
ed2.setReffering(reffering);
|
||||||
|
getSession().getTransaction().commit();
|
||||||
|
checkEmptyAuditSessionCache(getSession(), auditEntityNames);
|
||||||
|
|
||||||
|
getSession().getTransaction().begin();
|
||||||
|
ed2 = (SetRefEdEntity) getSession().load(SetRefEdEntity.class, ed2.getId());
|
||||||
|
ed2.getReffering().remove(ing1);
|
||||||
|
getSession().getTransaction().commit();
|
||||||
|
checkEmptyAuditSessionCache(getSession(), auditEntityNames);
|
||||||
|
|
||||||
|
getSession().getTransaction().begin();
|
||||||
|
ed2 = (SetRefEdEntity) getSession().load(SetRefEdEntity.class, ed2.getId());
|
||||||
|
ed2.getReffering().iterator().next().setData("mod_data_ing_2");
|
||||||
|
getSession().getTransaction().commit();
|
||||||
|
checkEmptyAuditSessionCache(getSession(), auditEntityNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEmptyAuditSessionCache(Session session, String ... auditEntityNames) {
|
||||||
|
List<String> entityNames = Arrays.asList(auditEntityNames);
|
||||||
|
PersistenceContext persistenceContext = ((SessionImplementor) session).getPersistenceContext();
|
||||||
|
for (Object entry : persistenceContext.getEntityEntries().values()) {
|
||||||
|
EntityEntry entityEntry = (EntityEntry) entry;
|
||||||
|
if (entityNames.contains(entityEntry.getEntityName())) {
|
||||||
|
assert false : "Audit data shall not be stored in the session level cache. This causes performance issues.";
|
||||||
|
}
|
||||||
|
Assert.assertFalse(DefaultRevisionEntity.class.getName().equals(entityEntry.getEntityName()),
|
||||||
|
"Revision entity shall not be stored in the session level cache. This causes performance issues.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,6 +84,8 @@
|
||||||
<package name="org.hibernate.envers.test.integration.entityNames.oneToManyNotAudited" />
|
<package name="org.hibernate.envers.test.integration.entityNames.oneToManyNotAudited" />
|
||||||
<package name="org.hibernate.envers.test.integration.entityNames.singleAssociatedAudited" />
|
<package name="org.hibernate.envers.test.integration.entityNames.singleAssociatedAudited" />
|
||||||
<package name="org.hibernate.envers.test.integration.entityNames.singleAssociatedNotAudited" />
|
<package name="org.hibernate.envers.test.integration.entityNames.singleAssociatedNotAudited" />
|
||||||
|
|
||||||
|
<package name="org.hibernate.envers.test.performance" />
|
||||||
'>
|
'>
|
||||||
]>
|
]>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue