/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, 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.action.internal;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.action.spi.Executable;
import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy;
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;

An ActionQueue Executable for ensuring shared cache cleanup in relation to performed bulk HQL queries.

NOTE: currently this executes for INSERT queries as well as UPDATE and DELETE queries. For INSERT it is really not needed as we'd have no invalid entity/collection data to cleanup (we'd still nee to invalidate the appropriate update-timestamps regions) as a result of this query.
Author:Steve Ebersole
/** * An {@link org.hibernate.engine.spi.ActionQueue} {@link org.hibernate.action.spi.Executable} for ensuring * shared cache cleanup in relation to performed bulk HQL queries. * <p/> * NOTE: currently this executes for <tt>INSERT</tt> queries as well as * <tt>UPDATE</tt> and <tt>DELETE</tt> queries. For <tt>INSERT</tt> it is * really not needed as we'd have no invalid entity/collection data to * cleanup (we'd still nee to invalidate the appropriate update-timestamps * regions) as a result of this query. * * @author Steve Ebersole */
public class BulkOperationCleanupAction implements Executable, Serializable { private final Serializable[] affectedTableSpaces; private final Set<EntityCleanup> entityCleanups = new HashSet<EntityCleanup>(); private final Set<CollectionCleanup> collectionCleanups = new HashSet<CollectionCleanup>(); private final Set<NaturalIdCleanup> naturalIdCleanups = new HashSet<NaturalIdCleanup>();
Constructs an action to cleanup "affected cache regions" based on the affected entity persisters. The affected regions are defined as the region (if any) of the entity persisters themselves, plus the collection regions for any collection in which those entity persisters participate as elements/keys/etc.
Params:
  • session – The session to which this request is tied.
  • affectedQueryables – The affected entity persisters.
/** * Constructs an action to cleanup "affected cache regions" based on the * affected entity persisters. The affected regions are defined as the * region (if any) of the entity persisters themselves, plus the * collection regions for any collection in which those entity * persisters participate as elements/keys/etc. * * @param session The session to which this request is tied. * @param affectedQueryables The affected entity persisters. */
public BulkOperationCleanupAction(SessionImplementor session, Queryable... affectedQueryables) { final SessionFactoryImplementor factory = session.getFactory(); final LinkedHashSet<String> spacesList = new LinkedHashSet<String>(); for ( Queryable persister : affectedQueryables ) { spacesList.addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); if ( persister.hasCache() ) { entityCleanups.add( new EntityCleanup( persister.getCacheAccessStrategy() ) ); } if ( persister.hasNaturalIdentifier() && persister.hasNaturalIdCache() ) { naturalIdCleanups.add( new NaturalIdCleanup( persister.getNaturalIdCacheAccessStrategy() ) ); } final Set<String> roles = factory.getCollectionRolesByEntityParticipant( persister.getEntityName() ); if ( roles != null ) { for ( String role : roles ) { final CollectionPersister collectionPersister = factory.getCollectionPersister( role ); if ( collectionPersister.hasCache() ) { collectionCleanups.add( new CollectionCleanup( collectionPersister.getCacheAccessStrategy() ) ); } } } } this.affectedTableSpaces = spacesList.toArray( new String[ spacesList.size() ] ); }
Constructs an action to cleanup "affected cache regions" based on a set of affected table spaces. This differs from BulkOperationCleanupAction(SessionImplementor, Queryable[]) in that here we have the affected table names. From those we deduce the entity persisters which are affected based on the defined table spaces; and from there, we determine the affected collection regions based on any collections in which those entity persisters participate as elements/keys/etc.
Params:
  • session – The session to which this request is tied.
  • tableSpaces – The table spaces.
/** * Constructs an action to cleanup "affected cache regions" based on a * set of affected table spaces. This differs from {@link #BulkOperationCleanupAction(SessionImplementor, Queryable[])} * in that here we have the affected <strong>table names</strong>. From those * we deduce the entity persisters which are affected based on the defined * {@link EntityPersister#getQuerySpaces() table spaces}; and from there, we * determine the affected collection regions based on any collections * in which those entity persisters participate as elements/keys/etc. * * @param session The session to which this request is tied. * @param tableSpaces The table spaces. */
@SuppressWarnings({ "unchecked" }) public BulkOperationCleanupAction(SessionImplementor session, Set tableSpaces) { final LinkedHashSet<String> spacesList = new LinkedHashSet<String>(); spacesList.addAll( tableSpaces ); final SessionFactoryImplementor factory = session.getFactory(); for ( String entityName : factory.getAllClassMetadata().keySet() ) { final EntityPersister persister = factory.getEntityPersister( entityName ); final String[] entitySpaces = (String[]) persister.getQuerySpaces(); if ( affectedEntity( tableSpaces, entitySpaces ) ) { spacesList.addAll( Arrays.asList( entitySpaces ) ); if ( persister.hasCache() ) { entityCleanups.add( new EntityCleanup( persister.getCacheAccessStrategy() ) ); } if ( persister.hasNaturalIdentifier() && persister.hasNaturalIdCache() ) { naturalIdCleanups.add( new NaturalIdCleanup( persister.getNaturalIdCacheAccessStrategy() ) ); } final Set<String> roles = session.getFactory().getCollectionRolesByEntityParticipant( persister.getEntityName() ); if ( roles != null ) { for ( String role : roles ) { final CollectionPersister collectionPersister = factory.getCollectionPersister( role ); if ( collectionPersister.hasCache() ) { collectionCleanups.add( new CollectionCleanup( collectionPersister.getCacheAccessStrategy() ) ); } } } } } this.affectedTableSpaces = spacesList.toArray( new String[ spacesList.size() ] ); }
Check to determine whether the table spaces reported by an entity persister match against the defined affected table spaces.
Params:
  • affectedTableSpaces – The table spaces reported to be affected by the query.
  • checkTableSpaces – The table spaces (from the entity persister) to check against the affected table spaces.
Returns:True if there are affected table spaces and any of the incoming check table spaces occur in that set.
/** * Check to determine whether the table spaces reported by an entity * persister match against the defined affected table spaces. * * @param affectedTableSpaces The table spaces reported to be affected by * the query. * @param checkTableSpaces The table spaces (from the entity persister) * to check against the affected table spaces. * * @return True if there are affected table spaces and any of the incoming * check table spaces occur in that set. */
private boolean affectedEntity(Set affectedTableSpaces, Serializable[] checkTableSpaces) { if ( affectedTableSpaces == null || affectedTableSpaces.isEmpty() ) { return true; } for ( Serializable checkTableSpace : checkTableSpaces ) { if ( affectedTableSpaces.contains( checkTableSpace ) ) { return true; } } return false; } @Override public Serializable[] getPropertySpaces() { return affectedTableSpaces; } @Override public BeforeTransactionCompletionProcess getBeforeTransactionCompletionProcess() { return null; } @Override public AfterTransactionCompletionProcess getAfterTransactionCompletionProcess() { return new AfterTransactionCompletionProcess() { @Override public void doAfterTransactionCompletion(boolean success, SessionImplementor session) { for ( EntityCleanup cleanup : entityCleanups ) { cleanup.release(); } entityCleanups.clear(); for ( NaturalIdCleanup cleanup : naturalIdCleanups ) { cleanup.release(); } entityCleanups.clear(); for ( CollectionCleanup cleanup : collectionCleanups ) { cleanup.release(); } collectionCleanups.clear(); } }; } @Override public void beforeExecutions() throws HibernateException { // nothing to do } @Override public void execute() throws HibernateException { // nothing to do } private static class EntityCleanup implements Serializable { private final EntityRegionAccessStrategy cacheAccess; private final SoftLock cacheLock; private EntityCleanup(EntityRegionAccessStrategy cacheAccess) { this.cacheAccess = cacheAccess; this.cacheLock = cacheAccess.lockRegion(); cacheAccess.removeAll(); } private void release() { cacheAccess.unlockRegion( cacheLock ); } } private static class CollectionCleanup implements Serializable { private final CollectionRegionAccessStrategy cacheAccess; private final SoftLock cacheLock; private CollectionCleanup(CollectionRegionAccessStrategy cacheAccess) { this.cacheAccess = cacheAccess; this.cacheLock = cacheAccess.lockRegion(); cacheAccess.removeAll(); } private void release() { cacheAccess.unlockRegion( cacheLock ); } } private static class NaturalIdCleanup implements Serializable { private final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy; private final SoftLock cacheLock; public NaturalIdCleanup(NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy) { this.naturalIdCacheAccessStrategy = naturalIdCacheAccessStrategy; this.cacheLock = naturalIdCacheAccessStrategy.lockRegion(); naturalIdCacheAccessStrategy.removeAll(); } private void release() { naturalIdCacheAccessStrategy.unlockRegion( cacheLock ); } } @Override public void afterDeserialize(SessionImplementor session) { // nop } }