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

(cherry picked from commit 6203b5da30)
This commit is contained in:
Gail Badner 2014-06-26 22:10:01 -07:00
parent b0a2ae9d66
commit 1472681dc2
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.internal.WeblogicJtaPlatform;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.engine.transaction.spi.TransactionFactory; 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.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.PersistentTableBulkIdStrategy; import org.hibernate.hql.spi.PersistentTableBulkIdStrategy;
import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy;
@ -163,6 +167,7 @@ public class StrategySelectorBuilder {
addJtaPlatforms( strategySelector ); addJtaPlatforms( strategySelector );
addTransactionFactories( strategySelector ); addTransactionFactories( strategySelector );
addMultiTableBulkIdStrategies( strategySelector ); addMultiTableBulkIdStrategies( strategySelector );
addEntityCopyObserverStrategies( strategySelector );
// apply auto-discovered registrations // apply auto-discovered registrations
for ( StrategyRegistrationProvider provider : classLoaderService.loadJavaServices( StrategyRegistrationProvider.class ) ) { for ( StrategyRegistrationProvider provider : classLoaderService.loadJavaServices( StrategyRegistrationProvider.class ) ) {
@ -373,4 +378,22 @@ public class StrategySelectorBuilder {
TemporaryTableBulkIdStrategy.class 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.ObjectDeletedException;
import org.hibernate.StaleObjectStateException; import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException; import org.hibernate.WrongClassException;
import org.hibernate.boot.registry.selector.spi.StrategySelector;
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor; 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.Cascade;
import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.CascadePoint;
import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.MergeEvent; import org.hibernate.event.spi.MergeEvent;
import org.hibernate.event.spi.MergeEventListener; import org.hibernate.event.spi.MergeEventListener;
@ -47,6 +51,7 @@ import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.LazyInitializer;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.ForeignKeyDirection; import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.TypeHelper; import org.hibernate.type.TypeHelper;
@ -59,15 +64,13 @@ import org.hibernate.type.TypeHelper;
public class DefaultMergeEventListener extends AbstractSaveEventListener implements MergeEventListener { public class DefaultMergeEventListener extends AbstractSaveEventListener implements MergeEventListener {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultMergeEventListener.class ); private static final CoreMessageLogger LOG = CoreLogging.messageLogger( DefaultMergeEventListener.class );
private String entityCopyObserverStrategy;
@Override @Override
protected Map getMergeMap(Object anything) { protected Map getMergeMap(Object anything) {
return ( (MergeContext) anything ).invertMap(); return ( (MergeContext) anything ).invertMap();
} }
protected EntityCopyObserver createEntityCopyObserver() {
return new EntityCopyNotAllowedObserver();
}
/** /**
* Handle the given merge event. * Handle the given merge event.
* *
@ -76,7 +79,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
* @throws HibernateException * @throws HibernateException
*/ */
public void onMerge(MergeEvent event) 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 ); final MergeContext mergeContext = new MergeContext( event.getSession(), entityCopyObserver );
try { try {
onMerge( event, mergeContext ); 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. * Handle the given merge event.
* *

View File

@ -35,7 +35,7 @@ import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.pretty.MessageHelper; 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 * the same persistent entity to be merged and provides logging of the entity copies that
* that are detected. * that are detected.
* *
@ -44,6 +44,8 @@ import org.hibernate.pretty.MessageHelper;
public class EntityCopyAllowedLoggedObserver extends EntityCopyAllowedObserver { public class EntityCopyAllowedLoggedObserver extends EntityCopyAllowedObserver {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EntityCopyAllowedLoggedObserver.class ); 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. // Tracks the number of entity copies per entity name.
private Map<String, Integer> countsByEntityName; 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; package org.hibernate.event.internal;
import java.util.IdentityHashMap; import org.hibernate.event.spi.EntityCopyObserver;
import java.util.Map;
import java.util.Set;
import org.hibernate.event.spi.EventSource; 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. * the same persistent entity to be merged.
* *
* @author Gail Badner * @author Gail Badner
*/ */
public class EntityCopyAllowedObserver implements EntityCopyObserver { public class EntityCopyAllowedObserver implements EntityCopyObserver {
public static final String SHORT_NAME = "allow";
@Override @Override
public void entityCopyDetected( public void entityCopyDetected(
Object managedEntity, Object managedEntity,

View File

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

View File

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

View File

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

View File

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

View File

@ -29,9 +29,6 @@ import org.junit.Test;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; 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.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;

View File

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

View File

@ -31,9 +31,7 @@ import org.hibernate.Hibernate;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.StaleObjectStateException; import org.hibernate.StaleObjectStateException;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.event.internal.EntityCopyAllowedMergeEventListener; import org.hibernate.cfg.Configuration;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.testing.FailureExpected; import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.FailureExpectedWithNewMetamodel; import org.hibernate.testing.FailureExpectedWithNewMetamodel;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
@ -58,9 +56,12 @@ public class MergeMultipleEntityRepresentationsTest extends BaseCoreFunctionalTe
}; };
} }
protected void afterSessionFactoryBuilt() { public void configure(Configuration cfg) {
EventListenerRegistry registry = sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class ); super.configure( cfg );
registry.setListeners( EventType.MERGE, new EntityCopyAllowedMergeEventListener() ); cfg.setProperty(
"hibernate.event.merge.entity_copy_observer",
"allow"
);
} }
@Test @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; package org.hibernate.jpa.test.emops;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.junit.Test; 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.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
@ -41,21 +38,19 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
/** /**
* Tests merging multiple detached representations of the same entity using * Tests merging multiple detached representations of the same entity.
* {@link org.hibernate.jpa.event.internal.core.JpaEntityCopyAllowedMergeEventListener}.
* *
* @author Gail Badner * @author Gail Badner
*/ */
@TestForIssue( jiraKey = "HHH-9106") @TestForIssue( jiraKey = "HHH-9106")
public class MergeMultipleEntityRepresentationsAllowedTest extends BaseEntityManagerFunctionalTestCase { public class MergeMultipleEntityRepresentationsAllowedTest extends BaseEntityManagerFunctionalTestCase {
@Override protected void addConfigOptions(Map options) {
protected void afterEntityManagerFactoryBuilt() {
super.afterEntityManagerFactoryBuilt();
SessionFactoryImplementor sfi = entityManagerFactory().unwrap( SessionFactoryImplementor.class ); options.put(
EventListenerRegistry registry = sfi.getServiceRegistry().getService( EventListenerRegistry.class ); "hibernate.event.merge.entity_copy_observer",
registry.setListeners( EventType.MERGE, new JpaEntityCopyAllowedMergeEventListener() ); "allow"
);
} }
@Test @Test