HHH-16089 allow @Synchronize for a collection

This commit is contained in:
Gavin 2023-01-24 22:07:11 +01:00 committed by Gavin King
parent 803a9a0ffb
commit 0bb04b1021
4 changed files with 47 additions and 13 deletions

View File

@ -9,23 +9,28 @@ package org.hibernate.annotations;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
/** /**
* Specifies the tables that hold state mapped by the annotated entity. * Specifies a table or tables that hold state mapped by the annotated
* entity or collection.
* <p> * <p>
* If Hibernate is not aware that a certain table holds state mapped * If Hibernate is not aware that a certain table holds state mapped
* by an entity class, then {@linkplain org.hibernate.FlushMode#AUTO * by an entity class or collection, then modifications might not be
* auto-flush} might not occur when it should, and queries against the * {@linkplain org.hibernate.FlushMode#AUTO automatically synchronized}
* entity might return stale data. * with the database before a query is executed against that table, and
* the query might return stale data.
* <p> * <p>
* This annotation might be necessary if: * Ordinarily, Hibernate knows the tables containing the state of an
* entity or collection. This annotation might be necessary if:
* <ul> * <ul>
* <li>the entity maps a database view, * <li>an entity or collection maps a database view,
* <li>the entity is persisted using handwritten SQL, that is, using * <li>an entity or collection is persisted using handwritten SQL,
* {@link SQLSelect @SQLSelect} and friends, or * that is, using {@link SQLSelect @SQLSelect} and friends, or
* <li>the entity is mapped using {@link Subselect @Subselect}. * <li>an entity is mapped using {@link Subselect @Subselect}.
* </ul> * </ul>
* <p> * <p>
* By default, the table names specified by this annotation are interpreted * By default, the table names specified by this annotation are interpreted
@ -39,7 +44,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* *
* @see org.hibernate.query.SynchronizeableQuery * @see org.hibernate.query.SynchronizeableQuery
*/ */
@Target(TYPE) @Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME) @Retention(RUNTIME)
public @interface Synchronize { public @interface Synchronize {
/** /**

View File

@ -68,6 +68,7 @@ import org.hibernate.annotations.SQLSelect;
import org.hibernate.annotations.SQLUpdate; import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.SortComparator; import org.hibernate.annotations.SortComparator;
import org.hibernate.annotations.SortNatural; import org.hibernate.annotations.SortNatural;
import org.hibernate.annotations.Synchronize;
import org.hibernate.annotations.Where; import org.hibernate.annotations.Where;
import org.hibernate.annotations.WhereJoinTable; import org.hibernate.annotations.WhereJoinTable;
import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.ReflectionManager;
@ -83,6 +84,7 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData; import org.hibernate.boot.spi.PropertyData;
import org.hibernate.boot.spi.SecondPass; import org.hibernate.boot.spi.SecondPass;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
@ -1642,6 +1644,7 @@ public abstract class CollectionBinder {
LOG.debugf( "Mapping collection: %s -> %s", collection.getRole(), collection.getCollectionTable().getName() ); LOG.debugf( "Mapping collection: %s -> %s", collection.getRole(), collection.getCollectionTable().getName() );
} }
bindSynchronize();
bindFilters( false ); bindFilters( false );
handleWhere( false ); handleWhere( false );
@ -1681,6 +1684,26 @@ public abstract class CollectionBinder {
} }
} }
private void bindSynchronize() {
if ( property.isAnnotationPresent( Synchronize.class ) ) {
final JdbcEnvironment jdbcEnvironment = buildingContext.getMetadataCollector().getDatabase().getJdbcEnvironment();
final Synchronize synchronize = property.getAnnotation(Synchronize.class);
for ( String table : synchronize.value() ) {
String physicalName = synchronize.logical() ? toPhysicalName( jdbcEnvironment, table ) : table;
collection.addSynchronizedTable( physicalName );
}
}
}
private String toPhysicalName(JdbcEnvironment jdbcEnvironment, String logicalName) {
return buildingContext.getBuildingOptions().getPhysicalNamingStrategy()
.toPhysicalTableName(
jdbcEnvironment.getIdentifierHelper().toIdentifier( logicalName ),
jdbcEnvironment
)
.render( jdbcEnvironment.getDialect() );
}
private void bindFilters(boolean hasAssociationTable) { private void bindFilters(boolean hasAssociationTable) {
final Filter simpleFilter = property.getAnnotation( Filter.class ); final Filter simpleFilter = property.getAnnotation( Filter.class );
//set filtering //set filtering
@ -2084,6 +2107,8 @@ public abstract class CollectionBinder {
else { else {
handleOwnedManyToMany( targetEntity, isCollectionOfEntities ); handleOwnedManyToMany( targetEntity, isCollectionOfEntities );
} }
bindSynchronize();
bindFilters( isCollectionOfEntities ); bindFilters( isCollectionOfEntities );
handleWhere( isCollectionOfEntities ); handleWhere( isCollectionOfEntities );

View File

@ -727,6 +727,10 @@ public abstract class Collection implements Fetchable, Value, Filterable {
return synchronizedTables; return synchronizedTables;
} }
public void addSynchronizedTable(String table) {
synchronizedTables.add( table );
}
public String getLoaderName() { public String getLoaderName() {
return loaderName; return loaderName;
} }

View File

@ -16,9 +16,9 @@ import org.hibernate.MappingException;
* in a given set of named <em>query spaces</em>. A query space is usually, but not always, * in a given set of named <em>query spaces</em>. A query space is usually, but not always,
* a relational database table, in which case the name of the space is simply the table name. * a relational database table, in which case the name of the space is simply the table name.
* <p> * <p>
* Each {@linkplain jakarta.persistence.Entity entity type} is understood to store its state * Each {@linkplain jakarta.persistence.Entity entity type} or collection is understood to
* in one or more query spaces. Usually, the query spaces are automatically determined by * store its state in one or more query spaces. Usually, the query spaces are automatically
* the mapping, but sometimes they must be specified explicitly using * determined by the mapping, but sometimes they must be specified explicitly using
* {@link org.hibernate.annotations.Synchronize @Synchronize}. * {@link org.hibernate.annotations.Synchronize @Synchronize}.
* <p> * <p>
* Query spaces mediate the interaction between query execution and synchronization of * Query spaces mediate the interaction between query execution and synchronization of