HHH-8243 HHH-8329 - Avoid flushing in validity audit strategy
This commit is contained in:
parent
db677c7d68
commit
f0bd12f823
|
@ -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,14 +372,20 @@ public class ActionQueue {
|
|||
* @param success Was the transaction successful.
|
||||
*/
|
||||
public void afterTransactionCompletion(boolean success) {
|
||||
afterTransactionProcesses.afterTransactionCompletion( 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() {
|
||||
beforeTransactionProcesses.beforeTransactionCompletion();
|
||||
if ( !isTransactionCoordinatorShared ) {
|
||||
// Execute completion actions only in transaction owner (aka parent session).
|
||||
beforeTransactionProcesses.beforeTransactionCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,173 +106,134 @@ 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 ) {
|
||||
final Queryable productionEntityQueryable = getQueryable( entityName, sessionImplementor );
|
||||
final Queryable rootProductionEntityQueryable = getQueryable(
|
||||
productionEntityQueryable.getRootEntityName(),
|
||||
sessionImplementor
|
||||
);
|
||||
final Queryable auditedEntityQueryable = getQueryable( auditedEntityName, sessionImplementor );
|
||||
final Queryable rootAuditedEntityQueryable = getQueryable(
|
||||
auditedEntityQueryable.getRootEntityName(),
|
||||
sessionImplementor
|
||||
);
|
||||
final Queryable revisionInfoEntityQueryable = getQueryable( revisionInfoEntityName, sessionImplementor );
|
||||
// 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
|
||||
);
|
||||
final Queryable auditedEntityQueryable = getQueryable( auditedEntityName, sessionImplementor );
|
||||
final Queryable rootAuditedEntityQueryable = getQueryable(
|
||||
auditedEntityQueryable.getRootEntityName(), sessionImplementor
|
||||
);
|
||||
|
||||
final String updateTableName;
|
||||
if ( UnionSubclassEntityPersister.class.isInstance( rootProductionEntityQueryable ) ) {
|
||||
// this is the condition causing all the problems in terms of the generated SQL UPDATE
|
||||
// the problem being that we currently try to update the in-line view made up of the union query
|
||||
//
|
||||
// this is extremely hacky means to get the root table name for the union subclass style entities.
|
||||
// hacky because it relies on internal behavior of UnionSubclassEntityPersister
|
||||
// !!!!!! NOTICE - using subclass persister, not root !!!!!!
|
||||
updateTableName = auditedEntityQueryable.getSubclassTableName( 0 );
|
||||
}
|
||||
else {
|
||||
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 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 );
|
||||
// set REVEND = ?
|
||||
update.addColumn( revEndColumnName );
|
||||
// set [, REVEND_TSTMP = ?]
|
||||
if ( isRevisionEndTimestampEnabled ) {
|
||||
update.addColumn(
|
||||
rootAuditedEntityQueryable.toColumns(
|
||||
auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName()
|
||||
)[0]
|
||||
);
|
||||
}
|
||||
|
||||
// where (prod_ent_id) = ?
|
||||
update.addPrimaryKeyColumns( rootProductionEntityQueryable.getIdentifierColumnNames() );
|
||||
// where REV <> ?
|
||||
update.addWhereColumn(
|
||||
rootAuditedEntityQueryable.toColumns(
|
||||
auditCfg.getAuditEntCfg().getRevisionNumberPath()
|
||||
)[0],
|
||||
"<> ?"
|
||||
);
|
||||
// where REVEND is null
|
||||
update.addWhereColumn( revEndColumnName, " is null" );
|
||||
|
||||
// Now lets execute the sql...
|
||||
final String updateSql = update.toStatementString();
|
||||
|
||||
int rowCount = session.doReturningWork(
|
||||
new ReturningWork<Integer>() {
|
||||
@Override
|
||||
public Integer execute(Connection connection) throws SQLException {
|
||||
PreparedStatement preparedStatement = sessionImplementor.getTransactionCoordinator()
|
||||
.getJdbcCoordinator()
|
||||
.getStatementPreparer()
|
||||
.prepareStatement( updateSql );
|
||||
|
||||
try {
|
||||
int index = 1;
|
||||
|
||||
// set REVEND = ?
|
||||
final Number revisionNumber = auditCfg.getRevisionInfoNumberReader().getRevisionNumber(
|
||||
revision
|
||||
);
|
||||
revisionInfoIdType.nullSafeSet(
|
||||
preparedStatement,
|
||||
revisionNumber,
|
||||
index,
|
||||
sessionImplementor
|
||||
);
|
||||
index += revisionInfoIdType.getColumnSpan( sessionImplementor.getFactory() );
|
||||
|
||||
// set [, REVEND_TSTMP = ?]
|
||||
if ( isRevisionEndTimestampEnabled ) {
|
||||
final Object revEndTimestampObj = revisionTimestampGetter.get( revision );
|
||||
final Date revisionEndTimestamp = convertRevEndTimestampToDate( revEndTimestampObj );
|
||||
final Type revEndTsType = rootAuditedEntityQueryable.getPropertyType(
|
||||
auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName()
|
||||
);
|
||||
revEndTsType.nullSafeSet(
|
||||
preparedStatement,
|
||||
revisionEndTimestamp,
|
||||
index,
|
||||
sessionImplementor
|
||||
);
|
||||
index += revEndTsType.getColumnSpan( sessionImplementor.getFactory() );
|
||||
}
|
||||
|
||||
// where (prod_ent_id) = ?
|
||||
final Type idType = rootProductionEntityQueryable.getIdentifierType();
|
||||
idType.nullSafeSet( preparedStatement, id, index, sessionImplementor );
|
||||
index += idType.getColumnSpan( sessionImplementor.getFactory() );
|
||||
|
||||
// where REV <> ?
|
||||
final Type revType = rootAuditedEntityQueryable.getPropertyType(
|
||||
auditCfg.getAuditEntCfg().getRevisionNumberPath()
|
||||
);
|
||||
revType.nullSafeSet( preparedStatement, revisionNumber, index, sessionImplementor );
|
||||
|
||||
// where REVEND is null
|
||||
// nothing to bind....
|
||||
|
||||
return sessionImplementor.getTransactionCoordinator()
|
||||
.getJdbcCoordinator()
|
||||
.getResultSetReturn()
|
||||
.executeUpdate( preparedStatement );
|
||||
}
|
||||
finally {
|
||||
sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().release(
|
||||
preparedStatement
|
||||
);
|
||||
}
|
||||
}
|
||||
final String updateTableName;
|
||||
if ( UnionSubclassEntityPersister.class.isInstance( rootProductionEntityQueryable ) ) {
|
||||
// this is the condition causing all the problems in terms of the generated SQL UPDATE
|
||||
// the problem being that we currently try to update the in-line view made up of the union query
|
||||
//
|
||||
// this is extremely hacky means to get the root table name for the union subclass style entities.
|
||||
// hacky because it relies on internal behavior of UnionSubclassEntityPersister
|
||||
// !!!!!! NOTICE - using subclass persister, not root !!!!!!
|
||||
updateTableName = auditedEntityQueryable.getSubclassTableName( 0 );
|
||||
}
|
||||
else {
|
||||
updateTableName = rootAuditedEntityQueryable.getTableName();
|
||||
}
|
||||
);
|
||||
|
||||
if ( rowCount != 1 && ( !reuseEntityIdentifier || ( getRevisionType( auditCfg, data ) != RevisionType.ADD ) ) ) {
|
||||
throw new RuntimeException(
|
||||
"Cannot update previous revision for entity " + auditedEntityName + " and id " + id
|
||||
);
|
||||
}
|
||||
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( sessionImplementor.getFactory().getDialect() ).setTableName( updateTableName );
|
||||
// set REVEND = ?
|
||||
update.addColumn( revEndColumnName );
|
||||
// set [, REVEND_TSTMP = ?]
|
||||
if ( isRevisionEndTimestampEnabled ) {
|
||||
update.addColumn(
|
||||
rootAuditedEntityQueryable.toColumns( auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName() )[0]
|
||||
);
|
||||
}
|
||||
|
||||
// where (prod_ent_id) = ?
|
||||
update.addPrimaryKeyColumns( rootProductionEntityQueryable.getIdentifierColumnNames() );
|
||||
// where REV <> ?
|
||||
update.addWhereColumn(
|
||||
rootAuditedEntityQueryable.toColumns( auditCfg.getAuditEntCfg().getRevisionNumberPath() )[0], "<> ?"
|
||||
);
|
||||
// where REVEND is null
|
||||
update.addWhereColumn( revEndColumnName, " is null" );
|
||||
|
||||
// Now lets execute the sql...
|
||||
final String updateSql = update.toStatementString();
|
||||
|
||||
int rowCount = ( (Session) sessionImplementor ).doReturningWork(
|
||||
new ReturningWork<Integer>() {
|
||||
@Override
|
||||
public Integer execute(Connection connection) throws SQLException {
|
||||
PreparedStatement preparedStatement = sessionImplementor.getTransactionCoordinator()
|
||||
.getJdbcCoordinator().getStatementPreparer().prepareStatement( updateSql );
|
||||
|
||||
try {
|
||||
int index = 1;
|
||||
|
||||
// set REVEND = ?
|
||||
final Number revisionNumber = auditCfg.getRevisionInfoNumberReader().getRevisionNumber(
|
||||
revision
|
||||
);
|
||||
revisionInfoIdType.nullSafeSet(
|
||||
preparedStatement, revisionNumber, index, sessionImplementor
|
||||
);
|
||||
index += revisionInfoIdType.getColumnSpan( sessionImplementor.getFactory() );
|
||||
|
||||
// set [, REVEND_TSTMP = ?]
|
||||
if ( isRevisionEndTimestampEnabled ) {
|
||||
final Object revEndTimestampObj = revisionTimestampGetter.get( revision );
|
||||
final Date revisionEndTimestamp = convertRevEndTimestampToDate( revEndTimestampObj );
|
||||
final Type revEndTsType = rootAuditedEntityQueryable.getPropertyType(
|
||||
auditCfg.getAuditEntCfg().getRevisionEndTimestampFieldName()
|
||||
);
|
||||
revEndTsType.nullSafeSet(
|
||||
preparedStatement, revisionEndTimestamp, index, sessionImplementor
|
||||
);
|
||||
index += revEndTsType.getColumnSpan( sessionImplementor.getFactory() );
|
||||
}
|
||||
|
||||
// where (prod_ent_id) = ?
|
||||
final Type idType = rootProductionEntityQueryable.getIdentifierType();
|
||||
idType.nullSafeSet( preparedStatement, id, index, sessionImplementor );
|
||||
index += idType.getColumnSpan( sessionImplementor.getFactory() );
|
||||
|
||||
// where REV <> ?
|
||||
final Type revType = rootAuditedEntityQueryable.getPropertyType(
|
||||
auditCfg.getAuditEntCfg().getRevisionNumberPath()
|
||||
);
|
||||
revType.nullSafeSet( preparedStatement, revisionNumber, index, sessionImplementor );
|
||||
|
||||
// where REVEND is null
|
||||
// nothing to bind....
|
||||
|
||||
return sessionImplementor.getTransactionCoordinator()
|
||||
.getJdbcCoordinator().getResultSetReturn().executeUpdate( preparedStatement );
|
||||
}
|
||||
finally {
|
||||
sessionImplementor.getTransactionCoordinator().getJdbcCoordinator().release(
|
||||
preparedStatement
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if ( rowCount != 1 && ( !reuseEntityIdentifier || ( getRevisionType( auditCfg, data ) != RevisionType.ADD ) ) ) {
|
||||
throw new RuntimeException(
|
||||
"Cannot update previous revision for entity " + auditedEntityName + " and id " + id
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
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 );
|
||||
}
|
||||
|
@ -416,5 +368,4 @@ public class ValidityAuditStrategy implements AuditStrategy {
|
|||
}
|
||||
return new Date( (Long) revEndTimestampObj );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue