HHH-8243 HHH-8329 - Avoid flushing in validity audit strategy

(cherry picked from commit f0bd12f823)
This commit is contained in:
Lukasz Antoniak 2013-12-04 01:30:47 +01:00 committed by Steve Ebersole
parent d59387cd01
commit 522ce1d974
6 changed files with 332 additions and 215 deletions

View File

@ -102,8 +102,9 @@ public class ActionQueue {
// an immutable array holding all 7 ExecutionLists in execution order
private final List<ExecutableList<?>> executableLists;
private final AfterTransactionCompletionProcessQueue afterTransactionProcesses;
private final BeforeTransactionCompletionProcessQueue beforeTransactionProcesses;
private transient boolean isTransactionCoordinatorShared;
private AfterTransactionCompletionProcessQueue afterTransactionProcesses;
private BeforeTransactionCompletionProcessQueue beforeTransactionProcesses;
/**
* Constructs an action queue bound to the given session.
@ -140,6 +141,7 @@ public class ActionQueue {
executableLists = Collections.unmodifiableList( tmp );
isTransactionCoordinatorShared = false;
afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session );
beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session );
@ -370,15 +372,21 @@ public class ActionQueue {
* @param success Was the transaction successful.
*/
public void afterTransactionCompletion(boolean success) {
if ( !isTransactionCoordinatorShared ) {
// Execute completion actions only in transaction owner (aka parent session).
afterTransactionProcesses.afterTransactionCompletion( success );
}
}
/**
* Execute any registered {@link org.hibernate.action.spi.BeforeTransactionCompletionProcess}
*/
public void beforeTransactionCompletion() {
if ( !isTransactionCoordinatorShared ) {
// Execute completion actions only in transaction owner (aka parent session).
beforeTransactionProcesses.beforeTransactionCompletion();
}
}
/**
* Check whether any insertion or deletion actions are currently queued.
@ -542,6 +550,24 @@ public class ActionQueue {
return insertions.size();
}
public TransactionCompletionProcesses getTransactionCompletionProcesses() {
return new TransactionCompletionProcesses( beforeTransactionProcesses, afterTransactionProcesses );
}
/**
* Bind transaction completion processes to make them shared between primary and secondary session.
* Transaction completion processes are always executed by transaction owner (primary session),
* but can be registered using secondary session too.
*
* @param processes Transaction completion processes.
* @param isTransactionCoordinatorShared Flag indicating shared transaction context.
*/
public void setTransactionCompletionProcesses(TransactionCompletionProcesses processes, boolean isTransactionCoordinatorShared) {
this.isTransactionCoordinatorShared = isTransactionCoordinatorShared;
this.beforeTransactionProcesses = processes.beforeTransactionCompletionProcesses;
this.afterTransactionProcesses = processes.afterTransactionCompletionProcesses;
}
public void sortCollectionActions() {
if ( session.getFactory().getSettings().isOrderUpdatesEnabled() ) {
// sort the updates by fk
@ -575,11 +601,11 @@ public class ActionQueue {
}
public boolean hasAfterTransactionActions() {
return !afterTransactionProcesses.processes.isEmpty();
return isTransactionCoordinatorShared ? false : afterTransactionProcesses.hasActions();
}
public boolean hasBeforeTransactionActions() {
return !beforeTransactionProcesses.processes.isEmpty();
return isTransactionCoordinatorShared ? false : beforeTransactionProcesses.hasActions();
}
public boolean hasAnyQueuedActions() {
@ -645,30 +671,40 @@ public class ActionQueue {
return rtn;
}
/**
* Encapsulates behavior needed for after transaction processing
*/
private static class BeforeTransactionCompletionProcessQueue {
private SessionImplementor session;
private static abstract class AbstractTransactionCompletionProcessQueue<T> {
protected SessionImplementor session;
// Concurrency handling required when transaction completion process is dynamically registered
// inside event listener (HHH-7478).
private Queue<BeforeTransactionCompletionProcess> processes = new ConcurrentLinkedQueue<BeforeTransactionCompletionProcess>();
protected Queue<T> processes = new ConcurrentLinkedQueue<T>();
private BeforeTransactionCompletionProcessQueue(SessionImplementor session) {
private AbstractTransactionCompletionProcessQueue(SessionImplementor session) {
this.session = session;
}
public void register(BeforeTransactionCompletionProcess process) {
public void register(T process) {
if ( process == null ) {
return;
}
processes.add( process );
}
public boolean hasActions() {
return !processes.isEmpty();
}
}
/**
* Encapsulates behavior needed for before transaction processing
*/
private static class BeforeTransactionCompletionProcessQueue extends AbstractTransactionCompletionProcessQueue<BeforeTransactionCompletionProcess> {
private BeforeTransactionCompletionProcessQueue(SessionImplementor session) {
super( session );
}
public void beforeTransactionCompletion() {
for ( BeforeTransactionCompletionProcess process : processes ) {
while ( !processes.isEmpty() ) {
try {
process.doBeforeTransactionCompletion( session );
processes.poll().doBeforeTransactionCompletion( session );
}
catch (HibernateException he) {
throw he;
@ -677,39 +713,27 @@ public class ActionQueue {
throw new AssertionFailure( "Unable to perform beforeTransactionCompletion callback", e );
}
}
processes.clear();
}
}
/**
* Encapsulates behavior needed for after transaction processing
*/
private static class AfterTransactionCompletionProcessQueue {
private SessionImplementor session;
private static class AfterTransactionCompletionProcessQueue extends AbstractTransactionCompletionProcessQueue<AfterTransactionCompletionProcess> {
private Set<String> querySpacesToInvalidate = new HashSet<String>();
// Concurrency handling required when transaction completion process is dynamically registered
// inside event listener (HHH-7478).
private Queue<AfterTransactionCompletionProcess> processes = new ConcurrentLinkedQueue<AfterTransactionCompletionProcess>();
private AfterTransactionCompletionProcessQueue(SessionImplementor session) {
this.session = session;
super( session );
}
public void addSpaceToInvalidate(String space) {
querySpacesToInvalidate.add( space );
}
public void register(AfterTransactionCompletionProcess process) {
if ( process == null ) {
return;
}
processes.add( process );
}
public void afterTransactionCompletion(boolean success) {
for ( AfterTransactionCompletionProcess process : processes ) {
while ( !processes.isEmpty() ) {
try {
process.doAfterTransactionCompletion( success, session );
processes.poll().doAfterTransactionCompletion( success, session );
}
catch (CacheException ce) {
LOG.unableToReleaseCacheLock( ce );
@ -719,7 +743,6 @@ public class ActionQueue {
throw new AssertionFailure( "Exception releasing cache locks", e );
}
}
processes.clear();
if ( session.getFactory().getSettings().isQueryCacheEnabled() ) {
session.getFactory().getUpdateTimestampsCache().invalidate(
@ -731,6 +754,21 @@ public class ActionQueue {
}
}
/**
* Wrapper class allowing to bind the same transaction completion process queues in different sessions.
*/
public static class TransactionCompletionProcesses {
private final BeforeTransactionCompletionProcessQueue beforeTransactionCompletionProcesses;
private final AfterTransactionCompletionProcessQueue afterTransactionCompletionProcesses;
private TransactionCompletionProcesses(
BeforeTransactionCompletionProcessQueue beforeTransactionCompletionProcessQueue,
AfterTransactionCompletionProcessQueue afterTransactionCompletionProcessQueue) {
this.beforeTransactionCompletionProcesses = beforeTransactionCompletionProcessQueue;
this.afterTransactionCompletionProcesses = afterTransactionCompletionProcessQueue;
}
}
/**
* Order the {@link #insertions} queue such that we group inserts against the same entity together (without
* violating constraints). The original order is generated by cascade order, which in turn is based on the

View File

@ -106,6 +106,7 @@ import org.hibernate.engine.profile.Fetch;
import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.query.spi.QueryPlanCache;
import org.hibernate.engine.query.spi.ReturnMetadata;
import org.hibernate.engine.spi.ActionQueue;
import org.hibernate.engine.spi.CacheImplementor;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.engine.spi.Mapping;
@ -1578,6 +1579,10 @@ public final class SessionFactoryImpl
return null;
}
protected ActionQueue.TransactionCompletionProcesses getTransactionCompletionProcesses() {
return null;
}
@Override
public Session openSession() {
log.tracef( "Opening Hibernate Session. tenant=%s, owner=%s", tenantIdentifier, sessionOwner );
@ -1586,6 +1591,7 @@ public final class SessionFactoryImpl
sessionFactory,
sessionOwner,
getTransactionCoordinator(),
getTransactionCompletionProcesses(),
autoJoinTransactions,
sessionFactory.settings.getRegionFactory().nextTimestamp(),
interceptor,

View File

@ -232,6 +232,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
final SessionFactoryImpl factory,
final SessionOwner sessionOwner,
final TransactionCoordinatorImpl transactionCoordinator,
final ActionQueue.TransactionCompletionProcesses transactionCompletionProcesses,
final boolean autoJoinTransactions,
final long timestamp,
final Interceptor interceptor,
@ -266,6 +267,9 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
this.transactionCoordinator = transactionCoordinator;
this.isTransactionCoordinatorShared = true;
this.autoJoinTransactions = false;
if ( transactionCompletionProcesses != null ) {
actionQueue.setTransactionCompletionProcesses( transactionCompletionProcesses, true );
}
if ( autoJoinTransactions ) {
LOG.debug(
"Session creation specified 'autoJoinTransactions', which is invalid in conjunction " +
@ -365,8 +369,8 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
return transactionCoordinator.close();
}
else {
if ( getActionQueue().hasAfterTransactionActions() ){
LOG.warn( "On close, shared Session had after transaction actions that have not yet been processed" );
if ( getActionQueue().hasBeforeTransactionActions() || getActionQueue().hasAfterTransactionActions() ) {
LOG.warn( "On close, shared Session had before / after transaction actions that have not yet been processed" );
}
else {
transactionCoordinator.removeObserver( transactionObserver );
@ -2318,6 +2322,13 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
return shareTransactionContext ? session.transactionCoordinator : super.getTransactionCoordinator();
}
@Override
protected ActionQueue.TransactionCompletionProcesses getTransactionCompletionProcesses() {
return shareTransactionContext ?
session.getActionQueue().getTransactionCompletionProcesses() :
super.getTransactionCompletionProcesses();
}
@Override
public SharedSessionBuilder interceptor() {
return interceptor( session.interceptor );

View File

@ -23,6 +23,7 @@
*/
package org.hibernate.envers.internal.synchronization;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.engine.spi.SessionImplementor;
@ -45,9 +46,13 @@ public class SessionCacheCleaner {
public void scheduleAuditDataRemoval(final Session session, final Object data) {
((EventSource) session).getActionQueue().registerProcess(
new AfterTransactionCompletionProcess() {
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
if ( !session.isClosed() ) {
((Session) session).evict( data );
public void doAfterTransactionCompletion(boolean success, SessionImplementor sessionImplementor) {
if ( !sessionImplementor.isClosed() ) {
try {
( (Session) sessionImplementor ).evict( data );
}
catch ( HibernateException ignore ) {
}
}
}
}

View File

@ -10,8 +10,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
@ -39,8 +43,6 @@ import org.hibernate.type.CollectionType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.MIDDLE_ENTITY_ALIAS;
import static org.hibernate.envers.internal.entities.mapper.relation.query.QueryConstants.REVISION_PARAMETER;
@ -73,7 +75,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
/**
* getter for the revision entity field annotated with @RevisionTimestamp
*/
private Getter revisionTimestampGetter;
private Getter revisionTimestampGetter = null;
private final SessionCacheCleaner sessionCacheCleaner;
@ -83,20 +85,17 @@ public class ValidityAuditStrategy implements AuditStrategy {
public void perform(
final Session session,
String entityName,
final String entityName,
final AuditConfiguration auditCfg,
final Serializable id,
Object data,
final Object data,
final Object revision) {
final AuditEntitiesConfiguration audEntitiesCfg = auditCfg.getAuditEntCfg();
final String auditedEntityName = audEntitiesCfg.getAuditEntityName( entityName );
final String revisionInfoEntityName = auditCfg.getAuditEntCfg().getRevisionInfoEntityName();
final SessionImplementor sessionImplementor = (SessionImplementor) session;
final Dialect dialect = sessionImplementor.getFactory().getDialect();
// Save the audit data
session.save( auditedEntityName, data );
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
// Update the end date of the previous row.
//
@ -107,17 +106,18 @@ public class ValidityAuditStrategy implements AuditStrategy {
// null end date exists for each identifier.
final boolean reuseEntityIdentifier = auditCfg.getGlobalCfg().isAllowIdentifierReuse();
if ( reuseEntityIdentifier || getRevisionType( auditCfg, data ) != RevisionType.ADD ) {
// Register transaction completion process to guarantee execution of UPDATE statement after INSERT.
( (EventSource) session ).getActionQueue().registerProcess( new BeforeTransactionCompletionProcess() {
@Override
public void doBeforeTransactionCompletion(final SessionImplementor sessionImplementor) {
final Queryable productionEntityQueryable = getQueryable( entityName, sessionImplementor );
final Queryable rootProductionEntityQueryable = getQueryable(
productionEntityQueryable.getRootEntityName(),
sessionImplementor
productionEntityQueryable.getRootEntityName(), sessionImplementor
);
final Queryable auditedEntityQueryable = getQueryable( auditedEntityName, sessionImplementor );
final Queryable rootAuditedEntityQueryable = getQueryable(
auditedEntityQueryable.getRootEntityName(),
sessionImplementor
auditedEntityQueryable.getRootEntityName(), sessionImplementor
);
final Queryable revisionInfoEntityQueryable = getQueryable( revisionInfoEntityName, sessionImplementor );
final String updateTableName;
if ( UnionSubclassEntityPersister.class.isInstance( rootProductionEntityQueryable ) ) {
@ -133,32 +133,19 @@ public class ValidityAuditStrategy implements AuditStrategy {
updateTableName = rootAuditedEntityQueryable.getTableName();
}
// first we need to flush the session in order to have the new audit data inserted
// todo: expose org.hibernate.internal.SessionImpl.autoFlushIfRequired via SessionImplementor
// for now, we duplicate some of that logic here
autoFlushIfRequired( sessionImplementor, rootAuditedEntityQueryable, revisionInfoEntityQueryable );
final Type revisionInfoIdType = sessionImplementor.getFactory()
.getEntityPersister( revisionInfoEntityName )
.getIdentifierType();
final String revEndColumnName = rootAuditedEntityQueryable.toColumns(
auditCfg.getAuditEntCfg()
.getRevisionEndFieldName()
)[0];
final Type revisionInfoIdType = sessionImplementor.getFactory().getEntityPersister( revisionInfoEntityName ).getIdentifierType();
final String revEndColumnName = rootAuditedEntityQueryable.toColumns( auditCfg.getAuditEntCfg().getRevisionEndFieldName() )[0];
final boolean isRevisionEndTimestampEnabled = auditCfg.getAuditEntCfg().isRevisionEndTimestampEnabled();
// update audit_ent set REVEND = ? [, REVEND_TSTMP = ?] where (prod_ent_id) = ? and REV <> ? and REVEND is null
final Update update = new Update( dialect ).setTableName( updateTableName );
final Update update = new Update( sessionImplementor.getFactory().getDialect() ).setTableName( updateTableName );
// set REVEND = ?
update.addColumn( revEndColumnName );
// set [, REVEND_TSTMP = ?]
if ( isRevisionEndTimestampEnabled ) {
update.addColumn(
rootAuditedEntityQueryable.toColumns(
auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName()
)[0]
rootAuditedEntityQueryable.toColumns( auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName() )[0]
);
}
@ -166,10 +153,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
update.addPrimaryKeyColumns( rootProductionEntityQueryable.getIdentifierColumnNames() );
// where REV <> ?
update.addWhereColumn(
rootAuditedEntityQueryable.toColumns(
auditCfg.getAuditEntCfg().getRevisionNumberPath()
)[0],
"<> ?"
rootAuditedEntityQueryable.toColumns( auditCfg.getAuditEntCfg().getRevisionNumberPath() )[0], "<> ?"
);
// where REVEND is null
update.addWhereColumn( revEndColumnName, " is null" );
@ -177,14 +161,12 @@ public class ValidityAuditStrategy implements AuditStrategy {
// Now lets execute the sql...
final String updateSql = update.toStatementString();
int rowCount = session.doReturningWork(
int rowCount = ( (Session) sessionImplementor ).doReturningWork(
new ReturningWork<Integer>() {
@Override
public Integer execute(Connection connection) throws SQLException {
PreparedStatement preparedStatement = sessionImplementor.getTransactionCoordinator()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( updateSql );
.getJdbcCoordinator().getStatementPreparer().prepareStatement( updateSql );
try {
int index = 1;
@ -194,10 +176,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
revision
);
revisionInfoIdType.nullSafeSet(
preparedStatement,
revisionNumber,
index,
sessionImplementor
preparedStatement, revisionNumber, index, sessionImplementor
);
index += revisionInfoIdType.getColumnSpan( sessionImplementor.getFactory() );
@ -209,10 +188,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName()
);
revEndTsType.nullSafeSet(
preparedStatement,
revisionEndTimestamp,
index,
sessionImplementor
preparedStatement, revisionEndTimestamp, index, sessionImplementor
);
index += revEndTsType.getColumnSpan( sessionImplementor.getFactory() );
}
@ -232,9 +208,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
// nothing to bind....
return sessionImplementor.getTransactionCoordinator()
.getJdbcCoordinator()
.getResultSetReturn()
.executeUpdate( preparedStatement );
.getJdbcCoordinator().getResultSetReturn().executeUpdate( preparedStatement );
}
finally {
sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().release(
@ -251,29 +225,15 @@ public class ValidityAuditStrategy implements AuditStrategy {
);
}
}
});
}
sessionCacheCleaner.scheduleAuditDataRemoval( session, data );
}
private Queryable getQueryable(String entityName, SessionImplementor sessionImplementor) {
return (Queryable) sessionImplementor.getFactory().getEntityPersister( entityName );
}
private void autoFlushIfRequired(
SessionImplementor sessionImplementor,
Queryable auditedEntityQueryable,
Queryable revisionInfoEntityQueryable) {
final Set<String> querySpaces = new HashSet<String>();
querySpaces.add( auditedEntityQueryable.getTableName() );
querySpaces.add( revisionInfoEntityQueryable.getTableName() );
final AutoFlushEvent event = new AutoFlushEvent( querySpaces, (EventSource) sessionImplementor );
final Iterable<AutoFlushEventListener> listeners = sessionImplementor.getFactory().getServiceRegistry()
.getService( EventListenerRegistry.class )
.getEventListenerGroup( EventType.AUTO_FLUSH )
.listeners();
for ( AutoFlushEventListener listener : listeners ) {
listener.onAutoFlush( event );
}
}
@SuppressWarnings({"unchecked"})
public void performCollectionChange(
Session session, String entityName, String propertyName, AuditConfiguration auditCfg,
@ -321,12 +281,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
// ADD, we may need to update the last revision.
if ( l.size() > 0 ) {
updateLastRevision(
session,
auditCfg,
l,
originalId,
persistentCollectionChangeData.getEntityName(),
revision
session, auditCfg, l, originalId, persistentCollectionChangeData.getEntityName(), revision
);
}
@ -366,10 +321,7 @@ public class ValidityAuditStrategy implements AuditStrategy {
Parameters subParm = rootParameters.addSubParameters( "or" );
rootParameters.addWhereWithNamedParam( revisionProperty, addAlias, inclusive ? "<=" : "<", REVISION_PARAMETER );
subParm.addWhereWithNamedParam(
revisionEndProperty + ".id",
addAlias,
inclusive ? ">" : ">=",
REVISION_PARAMETER
revisionEndProperty + ".id", addAlias, inclusive ? ">" : ">=", REVISION_PARAMETER
);
subParm.addWhere( revisionEndProperty, addAlias, "is", "null", false );
}
@ -417,4 +369,3 @@ public class ValidityAuditStrategy implements AuditStrategy {
return new Date( (Long) revEndTimestampObj );
}
}

View File

@ -0,0 +1,106 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. 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 Inc.
*
* 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.test.integration.flush;
import javax.persistence.EntityManager;
import java.util.Arrays;
import java.util.List;
import org.hibernate.FlushMode;
import org.hibernate.envers.RevisionType;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.test.Priority;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.junit.Test;
import org.hibernate.testing.TestForIssue;
import static org.junit.Assert.assertEquals;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@TestForIssue( jiraKey = "HHH-8243" )
public class CommitFlush extends AbstractFlushTest {
private Integer id = null;
@Override
public FlushMode getFlushMode() {
return FlushMode.COMMIT;
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// Revision 1
em.getTransaction().begin();
StrTestEntity entity = new StrTestEntity( "x" );
em.persist( entity );
em.getTransaction().commit();
id = entity.getId();
// Revision 2
em.getTransaction().begin();
entity = em.find( StrTestEntity.class, entity.getId() );
entity.setStr( "y" );
entity = em.merge( entity );
em.getTransaction().commit();
em.close();
}
@Test
public void testRevisionsCounts() {
assertEquals( Arrays.asList( 1, 2 ), getAuditReader().getRevisions( StrTestEntity.class, id ) );
}
@Test
public void testHistoryOfId() {
StrTestEntity ver1 = new StrTestEntity( "x", id );
StrTestEntity ver2 = new StrTestEntity( "y", id );
assertEquals( ver1, getAuditReader().find( StrTestEntity.class, id, 1 ) );
assertEquals( ver2, getAuditReader().find( StrTestEntity.class, id, 2 ) );
}
@Test
public void testCurrent() {
assertEquals( new StrTestEntity( "y", id ), getEntityManager().find( StrTestEntity.class, id ) );
}
@Test
public void testRevisionTypes() {
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 );
}
}