HHH-9106 : Merging multiple representations of the same entity (using StrategySelector)

This commit is contained in:
Gail Badner 2014-06-26 22:10:01 -07:00
parent ceaf2e320f
commit 6203b5da30
14 changed files with 88 additions and 131 deletions

View File

@ -93,6 +93,10 @@ import org.hibernate.engine.transaction.jta.platform.internal.WebSphereJtaPlatfo
import org.hibernate.engine.transaction.jta.platform.internal.WeblogicJtaPlatform;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.engine.transaction.spi.TransactionFactory;
import org.hibernate.event.internal.EntityCopyAllowedLoggedObserver;
import org.hibernate.event.internal.EntityCopyAllowedObserver;
import org.hibernate.event.internal.EntityCopyNotAllowedObserver;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.PersistentTableBulkIdStrategy;
import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy;
@ -162,6 +166,7 @@ public class StrategySelectorBuilder {
addJtaPlatforms( strategySelector );
addTransactionFactories( strategySelector );
addMultiTableBulkIdStrategies( strategySelector );
addEntityCopyObserverStrategies( strategySelector );
// apply auto-discovered registrations
for ( StrategyRegistrationProvider provider : classLoaderService.loadJavaServices( StrategyRegistrationProvider.class ) ) {
@ -372,4 +377,22 @@ public class StrategySelectorBuilder {
TemporaryTableBulkIdStrategy.class
);
}
private void addEntityCopyObserverStrategies(StrategySelectorImpl strategySelector) {
strategySelector.registerStrategyImplementor(
EntityCopyObserver.class,
EntityCopyNotAllowedObserver.SHORT_NAME,
EntityCopyNotAllowedObserver.class
);
strategySelector.registerStrategyImplementor(
EntityCopyObserver.class,
EntityCopyAllowedObserver.SHORT_NAME,
EntityCopyAllowedObserver.class
);
strategySelector.registerStrategyImplementor(
EntityCopyObserver.class,
EntityCopyAllowedLoggedObserver.SHORT_NAME,
EntityCopyAllowedLoggedObserver.class
);
}
}

View File

@ -31,14 +31,18 @@ import org.hibernate.HibernateException;
import org.hibernate.ObjectDeletedException;
import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.internal.Cascade;
import org.hibernate.engine.internal.CascadePoint;
import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.MergeEvent;
import org.hibernate.event.spi.MergeEventListener;
@ -47,6 +51,7 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.TypeHelper;
@ -59,15 +64,13 @@ import org.hibernate.type.TypeHelper;
public class DefaultMergeEventListener extends AbstractSaveEventListener implements MergeEventListener {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultMergeEventListener.class );
private String entityCopyObserverStrategy;
@Override
protected Map getMergeMap(Object anything) {
return ( (MergeContext) anything ).invertMap();
}
protected EntityCopyObserver createEntityCopyObserver() {
return new EntityCopyNotAllowedObserver();
}
/**
* Handle the given merge event.
*
@ -76,7 +79,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
* @throws HibernateException
*/
public void onMerge(MergeEvent event) throws HibernateException {
final EntityCopyObserver entityCopyObserver = createEntityCopyObserver();
final EntityCopyObserver entityCopyObserver = createEntityCopyObserver( event.getSession().getFactory() );
final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver );
try {
onMerge( event, mergeContext );
@ -88,6 +91,27 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
}
}
private EntityCopyObserver createEntityCopyObserver(SessionFactoryImplementor sessionFactory) {
final ServiceRegistry serviceRegistry = sessionFactory.getServiceRegistry();
if ( entityCopyObserverStrategy == null ) {
final ConfigurationService configurationService
= serviceRegistry.getService( ConfigurationService.class );
entityCopyObserverStrategy = configurationService.getSetting(
"hibernate.event.merge.entity_copy_observer",
new ConfigurationService.Converter<String>() {
@Override
public String convert(Object value) {
return value.toString();
}
},
EntityCopyNotAllowedObserver.SHORT_NAME
);
LOG.debugf( "EntityCopyObserver strategy: %s", entityCopyObserverStrategy );
}
final StrategySelector strategySelector = serviceRegistry.getService( StrategySelector.class );
return strategySelector.resolveStrategy( EntityCopyObserver.class, entityCopyObserverStrategy );
}
/**
* Handle the given merge event.
*

View File

@ -35,7 +35,7 @@ import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.pretty.MessageHelper;
/**
* An {@link EntityCopyObserver} implementation that allows multiple representations of
* An {@link org.hibernate.event.spi.EntityCopyObserver} implementation that allows multiple representations of
* the same persistent entity to be merged and provides logging of the entity copies that
* that are detected.
*
@ -44,6 +44,8 @@ import org.hibernate.pretty.MessageHelper;
public class EntityCopyAllowedLoggedObserver extends EntityCopyAllowedObserver {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EntityCopyAllowedLoggedObserver.class );
public static final String SHORT_NAME = "log";
// Tracks the number of entity copies per entity name.
private Map<String, Integer> countsByEntityName;

View File

@ -1,41 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, 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.event.internal;
/**
* A {@link org.hibernate.event.spi.MergeEventListener} that allows merging
* multiple representations of the same persistent entity.
*
* @author Gail Badner
*/
public class EntityCopyAllowedMergeEventListener extends DefaultMergeEventListener {
@Override
protected EntityCopyObserver createEntityCopyObserver() {
return EntityCopyAllowedLoggedObserver.isDebugLoggingEnabled() ?
new EntityCopyAllowedLoggedObserver() :
new EntityCopyAllowedObserver();
}
}

View File

@ -23,23 +23,19 @@
*/
package org.hibernate.event.internal;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.event.spi.EventSource;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.pretty.MessageHelper;
/**
* An {@link EntityCopyObserver} implementation that allows multiple representations of
* An {@link org.hibernate.event.spi.EntityCopyObserver} implementation that allows multiple representations of
* the same persistent entity to be merged.
*
* @author Gail Badner
*/
public class EntityCopyAllowedObserver implements EntityCopyObserver {
public static final String SHORT_NAME = "allow";
@Override
public void entityCopyDetected(
Object managedEntity,

View File

@ -24,6 +24,7 @@
package org.hibernate.event.internal;
import org.hibernate.AssertionFailure;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.event.spi.EventSource;
import org.hibernate.pretty.MessageHelper;
@ -32,6 +33,8 @@ import org.hibernate.pretty.MessageHelper;
*/
public class EntityCopyNotAllowedObserver implements EntityCopyObserver {
public static final String SHORT_NAME = "disallow";
@Override
public void entityCopyDetected(
Object managedEntity,

View File

@ -31,6 +31,7 @@ import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.event.spi.EventSource;
import org.hibernate.pretty.MessageHelper;
@ -53,7 +54,7 @@ import org.hibernate.pretty.MessageHelper;
* MergeContext already contains an entry with a different entity as the key, but
* with the same (managedEntity) value, this means that multiple entity representations
* for the same persistent entity are being merged. If this happens,
* {@link org.hibernate.event.internal.EntityCopyObserver#entityCopyDetected(
* {@link org.hibernate.event.spi.EntityCopyObserver#entityCopyDetected(
* Object managedEntity, Object mergeEntity1, Object mergeEntity2, org.hibernate.event.spi.EventSource)}
* will be called. It is up to that method to determine the property course of
* action for this situation.

View File

@ -21,7 +21,7 @@
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.event.internal;
package org.hibernate.event.spi;
import org.hibernate.event.spi.EventSource;

View File

@ -36,6 +36,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.event.spi.EventSource;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;

View File

@ -29,9 +29,6 @@ import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;

View File

@ -30,11 +30,8 @@ import org.junit.Test;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
@ -57,9 +54,12 @@ public class MergeMultipleEntityRepresentationsOrphanDeleteTest extends BaseCore
};
}
protected void afterSessionFactoryBuilt() {
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
registry.setListeners( EventType.MERGE, new EntityCopyAllowedMergeEventListener() );
public void configure(Configuration cfg) {
super.configure( cfg );
cfg.setProperty(
"hibernate.event.merge.entity_copy_observer",
"allow"
);
}
@Test

View File

@ -31,9 +31,7 @@ import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.StaleObjectStateException;
import org.hibernate.Transaction;
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.cfg.Configuration;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
@ -57,9 +55,12 @@ public class MergeMultipleEntityRepresentationsTest extends BaseCoreFunctionalTe
};
}
protected void afterSessionFactoryBuilt() {
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
registry.setListeners( EventType.MERGE, new EntityCopyAllowedMergeEventListener() );
public void configure(Configuration cfg) {
super.configure( cfg );
cfg.setProperty(
"hibernate.event.merge.entity_copy_observer",
"allow"
);
}
@Test

View File

@ -1,45 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, 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.jpa.event.internal.core;
import org.hibernate.event.internal.EntityCopyAllowedLoggedObserver;
import org.hibernate.event.internal.EntityCopyAllowedObserver;
import org.hibernate.event.internal.EntityCopyObserver;
/**
* Overrides {@link JpaMergeEventListener} that allows merging multiple representations
* of the same persistent entity.
*
* @author Gail Badner
*/
public class JpaEntityCopyAllowedMergeEventListener extends JpaMergeEventListener {
@Override
protected EntityCopyObserver createEntityCopyObserver() {
return EntityCopyAllowedLoggedObserver.isDebugLoggingEnabled() ?
new EntityCopyAllowedLoggedObserver() :
new EntityCopyAllowedObserver();
}
}

View File

@ -24,14 +24,11 @@
package org.hibernate.jpa.test.emops;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import org.junit.Test;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.jpa.event.internal.core.JpaEntityCopyAllowedMergeEventListener;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
@ -41,21 +38,19 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
/**
* Tests merging multiple detached representations of the same entity using
* {@link org.hibernate.jpa.event.internal.core.JpaEntityCopyAllowedMergeEventListener}.
* Tests merging multiple detached representations of the same entity.
*
* @author Gail Badner
*/
@TestForIssue( jiraKey = "HHH-9106")
public class MergeMultipleEntityRepresentationsAllowedTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected void afterEntityManagerFactoryBuilt() {
super.afterEntityManagerFactoryBuilt();
protected void addConfigOptions(Map options) {
SessionFactoryImplementor sfi = entityManagerFactory().unwrap( SessionFactoryImplementor.class );
EventListenerRegistry registry = sfi.getServiceRegistry().getService( EventListenerRegistry.class );
registry.setListeners( EventType.MERGE, new JpaEntityCopyAllowedMergeEventListener() );
options.put(
"hibernate.event.merge.entity_copy_observer",
"allow"
);
}
@Test