HHH-3910 - custom dirty flag tracking
This commit is contained in:
parent
aef0e25405
commit
6258df4752
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, 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;
|
||||
|
||||
/**
|
||||
* During a flush cycle, Hibernate needs to determine which of the entities associated with a {@link Session}.
|
||||
* Dirty entities are the ones that get {@literal UPDATE}ed to the database.
|
||||
* <p/>
|
||||
* In some circumstances, that process of determining whether an entity is dirty can take a significant time as
|
||||
* by default Hibernate must check each of the entity's attribute values one-by-one. Oftentimes applications
|
||||
* already have knowledge of an entity's dirtiness and using that information instead would be more performant.
|
||||
* The purpose of this contract then is to allow applications such a plug-in point.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface CustomEntityDirtinessStrategy {
|
||||
/**
|
||||
* Is this strategy capable of telling whether the given entity is dirty? A return of {@code true} means that
|
||||
* {@link #isDirty} will be called next as the definitive means to determine whether the entity is dirty.
|
||||
*
|
||||
* @param entity The entity to be check.
|
||||
* @param session The session from which this check originates.
|
||||
*
|
||||
* @return {@code true} indicates the dirty check can be done; {@code false} indicates it cannot.
|
||||
*/
|
||||
public boolean canDirtyCheck(Object entity, Session session);
|
||||
|
||||
/**
|
||||
* The callback used by Hibernate to determine if the given entity is dirty. Only called if the previous
|
||||
* {@link #canDirtyCheck} returned {@code true}
|
||||
*
|
||||
* @param entity The entity to check.
|
||||
* @param session The session from which this check originates.
|
||||
*
|
||||
* @return {@code true} indicates the entity is dirty; {@link false} indicates the entity is not dirty.
|
||||
*/
|
||||
public boolean isDirty(Object entity, Session session);
|
||||
|
||||
/**
|
||||
* Callback used by Hibernate to signal that the entity dirty flag should be cleared. Generally this
|
||||
* happens after previous dirty changes were written to the database.
|
||||
*
|
||||
* @param entity The entity to reset
|
||||
* @param session The session from which this call originates.
|
||||
*/
|
||||
public void resetDirty(Object entity, Session session);
|
||||
}
|
|
@ -540,4 +540,10 @@ public interface AvailableSettings {
|
|||
* Default to false to keep backward compatibility.
|
||||
*/
|
||||
public static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id.new_generator_mappings";
|
||||
|
||||
/**
|
||||
* Setting to identify a {@link org.hibernate.CustomEntityDirtinessStrategy} to use. May point to
|
||||
* either a class name or instance.
|
||||
*/
|
||||
public static final String CUSTOM_ENTITY_DIRTINESS_STRATEGY = "hibernate.entity_dirtiness_strategy";
|
||||
}
|
||||
|
|
|
@ -28,9 +28,11 @@ import java.io.ObjectInputStream;
|
|||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.persister.entity.UniqueKeyLoadable;
|
||||
|
@ -269,12 +271,37 @@ public final class EntityEntry implements Serializable {
|
|||
return loadedState[propertyIndex];
|
||||
}
|
||||
|
||||
public boolean requiresDirtyCheck(Object entity) {
|
||||
return isModifiableEntity() && (
|
||||
getPersister().hasMutableProperties() ||
|
||||
!getPersister().getInstrumentationMetadata().isInstrumented() ||
|
||||
getPersister().getInstrumentationMetadata().extractInterceptor( entity ).isDirty()
|
||||
);
|
||||
/**
|
||||
* Not sure this is the best method name, but the general idea here is to return {@code true} if the entity can
|
||||
* possibly be dirty. This can only be the case if it is in a modifiable state (not read-only/deleted) and it
|
||||
* either has mutable properties or field-interception is not telling us it is dirty. Clear as mud? :/
|
||||
*
|
||||
* A name like canPossiblyBeDirty might be better
|
||||
*
|
||||
* @param entity The entity to test
|
||||
*
|
||||
* @return {@code true} indicates that the entity could possibly be dirty and that dirty check
|
||||
* should happen; {@code false} indicates there is no way the entity can be dirty
|
||||
*/
|
||||
public boolean requiresDirtyCheck(Object entity) {
|
||||
return isModifiableEntity()
|
||||
&& ( getPersister().hasMutableProperties() || ! isUnequivocallyNonDirty( entity ) );
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"SimplifiableIfStatement"})
|
||||
private boolean isUnequivocallyNonDirty(Object entity) {
|
||||
if ( getPersister().getInstrumentationMetadata().isInstrumented() ) {
|
||||
// the entity must be instrumented (otherwise we cant check dirty flag) and the dirty flag is false
|
||||
return ! getPersister().getInstrumentationMetadata().extractInterceptor( entity ).isDirty();
|
||||
}
|
||||
|
||||
final CustomEntityDirtinessStrategy customEntityDirtinessStrategy =
|
||||
persistenceContext.getSession().getFactory().getCustomEntityDirtinessStrategy();
|
||||
if ( customEntityDirtinessStrategy.canDirtyCheck( entity, (Session) persistenceContext.getSession() ) ) {
|
||||
return ! customEntityDirtinessStrategy.isDirty( entity, (Session) persistenceContext.getSession() );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -289,9 +316,9 @@ public final class EntityEntry implements Serializable {
|
|||
* @return true, if the entity is modifiable; false, otherwise,
|
||||
*/
|
||||
public boolean isModifiableEntity() {
|
||||
return ( status != Status.READ_ONLY ) &&
|
||||
! ( status == Status.DELETED && previousStatus == Status.READ_ONLY ) &&
|
||||
getPersister().isMutable();
|
||||
return getPersister().isMutable()
|
||||
&& status != Status.READ_ONLY
|
||||
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
|
||||
}
|
||||
|
||||
public void forceLocked(Object entity, Object nextVersion) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Map;
|
|||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Interceptor;
|
||||
import org.hibernate.MappingException;
|
||||
|
@ -238,4 +239,6 @@ public interface SessionFactoryImplementor extends Mapping, SessionFactory {
|
|||
public ServiceRegistryImplementor getServiceRegistry();
|
||||
|
||||
public void addObserver(SessionFactoryObserver observer);
|
||||
|
||||
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy();
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.jboss.logging.Logger;
|
|||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.Cache;
|
||||
import org.hibernate.ConnectionReleaseMode;
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.EmptyInterceptor;
|
||||
import org.hibernate.EntityNameResolver;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -72,6 +73,7 @@ import org.hibernate.cache.spi.access.AccessType;
|
|||
import org.hibernate.cache.spi.access.CollectionRegionAccessStrategy;
|
||||
import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
|
||||
import org.hibernate.cache.spi.access.RegionAccessStrategy;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.cfg.Settings;
|
||||
|
@ -122,6 +124,7 @@ import org.hibernate.persister.spi.PersisterFactory;
|
|||
import org.hibernate.pretty.MessageHelper;
|
||||
import org.hibernate.proxy.EntityNotFoundDelegate;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
import org.hibernate.service.classloading.spi.ClassLoaderService;
|
||||
import org.hibernate.service.config.spi.ConfigurationService;
|
||||
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
|
||||
import org.hibernate.service.jndi.spi.JndiService;
|
||||
|
@ -206,6 +209,7 @@ public final class SessionFactoryImpl
|
|||
private final transient TypeHelper typeHelper;
|
||||
private final transient TransactionEnvironment transactionEnvironment;
|
||||
private final transient SessionFactoryOptions sessionFactoryOptions;
|
||||
private final transient CustomEntityDirtinessStrategy customEntityDirtinessStrategy;
|
||||
|
||||
@SuppressWarnings( {"unchecked", "ThrowableResultOfMethodCallIgnored"})
|
||||
public SessionFactoryImpl(
|
||||
|
@ -531,10 +535,64 @@ public final class SessionFactoryImpl
|
|||
fetchProfiles.put( fetchProfile.getName(), fetchProfile );
|
||||
}
|
||||
|
||||
this.customEntityDirtinessStrategy = determineCustomEntityDirtinessStrategy( properties );
|
||||
this.transactionEnvironment = new TransactionEnvironmentImpl( this );
|
||||
this.observer.sessionFactoryCreated( this );
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
private CustomEntityDirtinessStrategy determineCustomEntityDirtinessStrategy(Properties properties) {
|
||||
final Object value = properties.get( AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY );
|
||||
if ( value != null ) {
|
||||
if ( CustomEntityDirtinessStrategy.class.isInstance( value ) ) {
|
||||
return CustomEntityDirtinessStrategy.class.cast( value );
|
||||
}
|
||||
Class<CustomEntityDirtinessStrategy> customEntityDirtinessStrategyClass;
|
||||
if ( Class.class.isInstance( value ) ) {
|
||||
customEntityDirtinessStrategyClass = Class.class.cast( customEntityDirtinessStrategy );
|
||||
}
|
||||
else {
|
||||
try {
|
||||
customEntityDirtinessStrategyClass = serviceRegistry.getService( ClassLoaderService.class )
|
||||
.classForName( value.toString() );
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.debugf(
|
||||
"Unable to locate CustomEntityDirtinessStrategy implementation class %s",
|
||||
value.toString()
|
||||
);
|
||||
customEntityDirtinessStrategyClass = null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return customEntityDirtinessStrategyClass.newInstance();
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.debugf(
|
||||
"Unable to instantiate CustomEntityDirtinessStrategy class %s",
|
||||
customEntityDirtinessStrategyClass.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// last resort
|
||||
return new CustomEntityDirtinessStrategy() {
|
||||
@Override
|
||||
public boolean canDirtyCheck(Object entity, Session session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object entity, Session session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetDirty(Object entity, Session session) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"ThrowableResultOfMethodCallIgnored"})
|
||||
public SessionFactoryImpl(
|
||||
MetadataImplementor metadata,
|
||||
|
@ -853,6 +911,7 @@ public final class SessionFactoryImpl
|
|||
fetchProfiles.put( fetchProfile.getName(), fetchProfile );
|
||||
}
|
||||
|
||||
this.customEntityDirtinessStrategy = determineCustomEntityDirtinessStrategy( properties );
|
||||
this.transactionEnvironment = new TransactionEnvironmentImpl( this );
|
||||
this.observer.sessionFactoryCreated( this );
|
||||
}
|
||||
|
@ -1723,6 +1782,10 @@ public final class SessionFactoryImpl
|
|||
}
|
||||
}
|
||||
|
||||
public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() {
|
||||
return customEntityDirtinessStrategy;
|
||||
}
|
||||
|
||||
|
||||
// Serialization handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, 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.test.dirtiness;
|
||||
|
||||
import org.hibernate.CustomEntityDirtinessStrategy;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CustomDirtinessStrategyTest extends BaseCoreFunctionalTestCase {
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
super.configure( configuration );
|
||||
configuration.getProperties().put( AvailableSettings.CUSTOM_ENTITY_DIRTINESS_STRATEGY, Strategy.INSTANCE );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { Thing.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomStrategy() throws Exception {
|
||||
final String INITIAL_NAME = "thing 1";
|
||||
final String SUBSEQUENT_NAME = "thing 2";
|
||||
|
||||
Session session = openSession();
|
||||
session.beginTransaction();
|
||||
session.save( new Thing( INITIAL_NAME ) );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
|
||||
session = openSession();
|
||||
session.beginTransaction();
|
||||
Thing thing = (Thing) session.get( Thing.class, 1L );
|
||||
thing.setName( SUBSEQUENT_NAME );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
|
||||
session = openSession();
|
||||
session.beginTransaction();
|
||||
thing = (Thing) session.get( Thing.class, 1L );
|
||||
assertEquals( INITIAL_NAME, thing.getName() );
|
||||
session.delete( thing );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
public static class Strategy implements CustomEntityDirtinessStrategy {
|
||||
public static final Strategy INSTANCE = new Strategy();
|
||||
|
||||
@Override
|
||||
public boolean canDirtyCheck(Object entity, Session session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object entity, Session session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetDirty(Object entity, Session session) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, 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.test.dirtiness;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
public class Thing {
|
||||
@Id
|
||||
@GeneratedValue( generator = "increment" )
|
||||
@GenericGenerator( strategy = "increment", name = "increment" )
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public Thing() {
|
||||
}
|
||||
|
||||
public Thing(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue