HHH-6614 - Fix and test

This commit is contained in:
Lukasz Antoniak 2011-09-08 22:48:34 +02:00
parent d71a15d1c1
commit 578de2e5a2
5 changed files with 168 additions and 11 deletions

View File

@ -29,6 +29,7 @@ import org.hibernate.envers.EntityTrackingRevisionListener;
import org.hibernate.envers.RevisionListener;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.entities.PropertyData;
import org.hibernate.envers.synchronization.SessionCacheCleaner;
import org.hibernate.envers.tools.reflection.ReflectionTools;
import org.hibernate.property.Setter;
@ -45,6 +46,7 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
private final Setter revisionTimestampSetter;
private final boolean timestampAsDate;
private final Class<?> revisionInfoClass;
private final SessionCacheCleaner sessionCacheCleaner;
public DefaultRevisionInfoGenerator(String revisionInfoEntityName, Class<?> revisionInfoClass,
Class<? extends RevisionListener> listenerClass,
@ -69,10 +71,13 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
// Default listener - none
listener = null;
}
sessionCacheCleaner = new SessionCacheCleaner();
}
public void saveRevisionData(Session session, Object revisionData) {
session.save(revisionInfoEntityName, revisionData);
sessionCacheCleaner.scheduleAuditDataRemoval(session, revisionData);
}
public Object generate() {

View File

@ -1,13 +1,17 @@
package org.hibernate.envers.strategy;
import java.io.Serializable;
import org.hibernate.Session;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.envers.configuration.AuditConfiguration;
import org.hibernate.envers.configuration.GlobalConfiguration;
import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
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.QueryBuilder;
import org.hibernate.event.spi.EventSource;
/**
* Default strategy is to simply persist the audit data.
@ -16,14 +20,22 @@ import org.hibernate.envers.tools.query.QueryBuilder;
* @author Stephanie Pau
*/
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,
Object revision) {
session.save(auditCfg.getAuditEntCfg().getAuditEntityName(entityName), data);
sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
}
public void performCollectionChange(Session session, AuditConfiguration auditCfg,
PersistentCollectionChangeData persistentCollectionChangeData, Object revision) {
session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());
sessionCacheCleaner.scheduleAuditDataRemoval(session, persistentCollectionChangeData.getData());
}

View File

@ -12,6 +12,7 @@ import org.hibernate.envers.entities.mapper.PersistentCollectionChangeData;
import org.hibernate.envers.entities.mapper.id.IdMapper;
import org.hibernate.envers.entities.mapper.relation.MiddleComponentData;
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.QueryBuilder;
import org.hibernate.property.Getter;
@ -43,6 +44,12 @@ public class ValidityAuditStrategy implements AuditStrategy {
/** getter for the revision entity field annotated with @RevisionTimestamp */
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,
Object revision) {
AuditEntitiesConfiguration audEntCfg = auditCfg.getAuditEntCfg();
@ -71,6 +78,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
// Save the audit data
session.save(auditedEntityName, data);
sessionCacheCleaner.scheduleAuditDataRemoval(session, data);
}
@SuppressWarnings({"unchecked"})
@ -103,6 +111,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
// Save the audit data
session.save(persistentCollectionChangeData.getEntityName(), persistentCollectionChangeData.getData());
sessionCacheCleaner.scheduleAuditDataRemoval(session, persistentCollectionChangeData.getData());
}
private void addEndRevisionNulLRestriction(AuditConfiguration auditCfg, QueryBuilder qb) {
@ -175,7 +184,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
// Saving the previous version
session.save(auditedEntityName, previousData);
sessionCacheCleaner.scheduleAuditDataRemoval(session, previousData);
} else {
throw new RuntimeException("Cannot find previous revision for entity " + auditedEntityName + " and id " + id);
}

View File

@ -0,0 +1,27 @@
package org.hibernate.envers.synchronization;
import org.hibernate.Session;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.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);
}
});
}
}

View File

@ -0,0 +1,104 @@
package org.hibernate.envers.test.performance;
import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.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.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.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
@TestForIssue(jiraKey = "HHH-6614")
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
@TestForIssue(jiraKey = "HHH-6614")
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("Revision entity shall not be stored in the session level cache. This causes performance issues.",
DefaultRevisionEntity.class.getName().equals(entityEntry.getEntityName()));
}
}
}