HHH-15564 add @SecondaryRow annotation

and HHH-15565 completely remove the notion of subsequent select fetching for
secondary tables ... and deprecate the member of @Table which turns this on,
since the actual underlying functionality is missing in H6!

+ some misc code cleanup in AbstractEntityPersister
This commit is contained in:
Gavin King 2022-10-01 15:28:41 +02:00
parent 1ff2b4e176
commit 87211e7cd4
16 changed files with 380 additions and 163 deletions

View File

@ -0,0 +1,48 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Specifies how the row of a {@link jakarta.persistence.SecondaryTable} should be managed.
*
* @see jakarta.persistence.SecondaryTable
*
* @author Gavin King
*/
@Target(TYPE)
@Retention(RUNTIME)
@Repeatable(SecondaryRows.class)
public @interface SecondaryRow {
/**
* The name of the secondary table, as specified by
* {@link jakarta.persistence.SecondaryTable#name()}.
*/
String table() default "";
/**
* If enabled, Hibernate will never insert or update the columns of the secondary table.
* <p>
* This setting is useful if data in the secondary table belongs to some other entity,
* or if it is maintained externally to Hibernate.
*/
boolean owned() default true;
/**
* Unless disabled, specifies that no row should be inserted in the secondary table if
* all the columns of the secondary table would be null. Furthermore, an outer join will
* always be used to read the row. Thus, by default, Hibernate avoids creating a row
* containing only null column values.
*/
boolean optional() default true;
}

View File

@ -0,0 +1,24 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A grouping of {@link SecondaryRows}.
*
* @author Gavin King
*/
@Target(TYPE)
@Retention(RUNTIME)
public @interface SecondaryRows {
SecondaryRow[] value();
}

View File

@ -68,26 +68,19 @@ public @interface Table {
ForeignKey foreignKey() default @ForeignKey( name="" );
/**
* Defines a fetching strategy for the secondary table.
* <ul>
* <li>If set to {@link FetchMode#JOIN}, the default, Hibernate will use an inner join to
* retrieve a secondary table defined by a class or its superclasses and an outer join for
* a secondary table defined by a subclass.
* <li>If set to {@link FetchMode#SELECT} then Hibernate will use a sequential select for
* a secondary table defined on a subclass, which will be issued only if a row turns out
* to represent an instance of the subclass. Inner joins will still be used to retrieve a
* secondary table defined by the class and its superclasses.
* </ul>
* <p>
* <em>Only applies to secondary tables.</em>
* @deprecated This setting has no effect in Hibernate 6
*/
@Deprecated(since = "6.2")
FetchMode fetch() default FetchMode.JOIN;
/**
* If enabled, Hibernate will never insert or update the columns of the secondary table.
* <p>
* <em>Only applies to secondary tables.</em>
*
* @deprecated use {@link SecondaryRow#owned()}
*/
@Deprecated(since = "6.2")
boolean inverse() default false;
/**
@ -96,7 +89,10 @@ public @interface Table {
* by default, Hibernate avoids creating a row of null values.
* <p>
* <em>Only applies to secondary tables.</em>
*
* @deprecated use {@link SecondaryRow#optional()}
*/
@Deprecated(since = "6.2")
boolean optional() default true;
/**

View File

@ -13,11 +13,11 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A grouping of tables.
* A grouping of {@link Table}s.
*
* @author Emmanuel Bernard
*/
@Target({TYPE})
@Target(TYPE)
@Retention(RUNTIME)
public @interface Tables {
/**

View File

@ -1741,7 +1741,6 @@ public class ModelBinder {
bindCustomSql( secondaryTableSource, secondaryTableJoin );
secondaryTableJoin.setSequentialSelect( secondaryTableSource.getFetchStyle() == FetchStyle.SELECT );
secondaryTableJoin.setInverse( secondaryTableSource.isInverse() );
secondaryTableJoin.setOptional( secondaryTableSource.isOptional() );

View File

@ -279,7 +279,6 @@ public class OneToOneSecondPass implements SecondPass {
//TODO support @ForeignKey
join.setKey( key );
join.setSequentialSelect( false );
//TODO support for inverse and optional
join.setOptional( true ); //perhaps not quite per-spec, but a Good Thing anyway
key.setCascadeDeleteEnabled( false );

View File

@ -50,7 +50,6 @@ import org.hibernate.annotations.DiscriminatorFormula;
import org.hibernate.annotations.DiscriminatorOptions;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Filters;
import org.hibernate.annotations.ForeignKey;
@ -70,6 +69,8 @@ import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.SecondaryRow;
import org.hibernate.annotations.SecondaryRows;
import org.hibernate.annotations.SelectBeforeUpdate;
import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize;
@ -1857,24 +1858,41 @@ public class EntityBinder {
}
private org.hibernate.annotations.Table findMatchingComplementaryTableAnnotation(Join join) {
String tableName = join.getTable().getQuotedName();
final String tableName = join.getTable().getQuotedName();
org.hibernate.annotations.Table table = annotatedClass.getAnnotation( org.hibernate.annotations.Table.class );
org.hibernate.annotations.Table matchingTable = null;
if ( table != null && tableName.equals( table.appliesTo() ) ) {
matchingTable = table;
return table;
}
else {
Tables tables = annotatedClass.getAnnotation( Tables.class );
if ( tables != null ) {
for (org.hibernate.annotations.Table current : tables.value()) {
if ( tableName.equals( current.appliesTo() ) ) {
matchingTable = current;
break;
return current;
}
}
}
return null;
}
}
private SecondaryRow findMatchingComplementarySecondaryRowAnnotation(Join join) {
final String tableName = join.getTable().getQuotedName();
SecondaryRow row = annotatedClass.getAnnotation( SecondaryRow.class );
if ( row != null && ( row.table().isEmpty() || tableName.equals( row.table() ) ) ) {
return row;
}
else {
SecondaryRows tables = annotatedClass.getAnnotation( SecondaryRows.class );
if ( tables != null ) {
for ( SecondaryRow current : tables.value() ) {
if ( tableName.equals( current.table() ) ) {
return current;
}
}
}
return null;
}
return matchingTable;
}
//Used for @*ToMany @JoinTable
@ -1964,9 +1982,13 @@ public class EntityBinder {
//Has to do the work later because it needs persistentClass id!
LOG.debugf( "Adding secondary table to entity %s -> %s",
persistentClass.getEntityName(), join.getTable().getName() );
SecondaryRow matchingRow = findMatchingComplementarySecondaryRowAnnotation( join );
org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( join );
if ( matchingTable != null ) {
join.setSequentialSelect( FetchMode.JOIN != matchingTable.fetch() );
if ( matchingRow != null ) {
join.setInverse( !matchingRow.owned() );
join.setOptional( matchingRow.optional() );
}
else if ( matchingTable != null ) {
join.setInverse( matchingTable.inverse() );
join.setOptional( matchingTable.optional() );
String insertSql = matchingTable.sqlInsert().sql();
@ -1996,7 +2018,6 @@ public class EntityBinder {
}
else {
//default
join.setSequentialSelect( false );
join.setInverse( false );
join.setOptional( true ); //perhaps not quite per-spec, but a Good Thing anyway
}

View File

@ -26,7 +26,6 @@ public class Join implements AttributeContainer, Serializable {
private Table table;
private KeyValue key;
private PersistentClass persistentClass;
private boolean sequentialSelect;
private boolean inverse;
private boolean optional;
private boolean disableForeignKeyCreation;
@ -176,13 +175,6 @@ public class Join implements AttributeContainer, Serializable {
return deleteCheckStyle;
}
public boolean isSequentialSelect() {
return sequentialSelect;
}
public void setSequentialSelect(boolean deferred) {
this.sequentialSelect = deferred;
}
public boolean isInverse() {
return inverse;
}

View File

@ -243,6 +243,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode;
import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl;
@ -611,9 +612,9 @@ public abstract class AbstractEntityPersister
return deleteCallable[j];
}
protected boolean isSubclassTableSequentialSelect(int j) {
return false;
}
// protected boolean isSubclassTableSequentialSelect(int j) {
// return false;
// }
/**
* Decide which tables need to be updated.
@ -627,7 +628,6 @@ public abstract class AbstractEntityPersister
* @return Array of booleans indicating which table require updating.
*/
public boolean[] getTableUpdateNeeded(final int[] dirtyProperties, boolean hasDirtyCollection) {
if ( dirtyProperties == null ) {
return getTableHasColumns(); // for objects that came in via update()
}
@ -697,31 +697,31 @@ public abstract class AbstractEntityPersister
final NaturalIdDataAccess naturalIdRegionAccessStrategy,
final RuntimeModelCreationContext creationContext) throws HibernateException {
this.factory = creationContext.getSessionFactory();
this.sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromEntityName( bootDescriptor.getEntityName() );
factory = creationContext.getSessionFactory();
sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromEntityName( bootDescriptor.getEntityName() );
this.navigableRole = new NavigableRole( bootDescriptor.getEntityName() );
navigableRole = new NavigableRole( bootDescriptor.getEntityName() );
SessionFactoryOptions sessionFactoryOptions = creationContext.getSessionFactory().getSessionFactoryOptions();
final SessionFactoryOptions sessionFactoryOptions = creationContext.getSessionFactory().getSessionFactoryOptions();
if ( sessionFactoryOptions.isSecondLevelCacheEnabled() ) {
this.canWriteToCache = determineCanWriteToCache( bootDescriptor, cacheAccessStrategy );
this.canReadFromCache = determineCanReadFromCache( bootDescriptor, cacheAccessStrategy );
this.cacheAccessStrategy = cacheAccessStrategy;
this.isLazyPropertiesCacheable = bootDescriptor.getRootClass().isLazyPropertiesCacheable();
this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy;
canWriteToCache = determineCanWriteToCache( bootDescriptor, cacheAccessStrategy );
canReadFromCache = determineCanReadFromCache( bootDescriptor, cacheAccessStrategy );
isLazyPropertiesCacheable = bootDescriptor.getRootClass().isLazyPropertiesCacheable();
}
else {
this.canWriteToCache = false;
this.canReadFromCache = false;
this.cacheAccessStrategy = null;
this.isLazyPropertiesCacheable = true;
this.naturalIdRegionAccessStrategy = null;
canWriteToCache = false;
canReadFromCache = false;
isLazyPropertiesCacheable = true;
}
this.entityMetamodel = new EntityMetamodel( bootDescriptor, this, creationContext );
entityMetamodel = new EntityMetamodel( bootDescriptor, this, creationContext );
this.entityEntryFactory = entityMetamodel.isMutable()
entityEntryFactory = entityMetamodel.isMutable()
? MutableEntityEntryFactory.INSTANCE
: ImmutableEntityEntryFactory.INSTANCE;
@ -1855,74 +1855,12 @@ public abstract class AbstractEntityPersister
@Override
public String selectFragment(String alias, String suffix) {
final QuerySpec rootQuerySpec = new QuerySpec( true );
final String rootTableName = getRootTableName();
final LoaderSqlAstCreationState sqlAstCreationState = new LoaderSqlAstCreationState(
rootQuerySpec,
new SqlAliasBaseManager(),
new SimpleFromClauseAccessImpl(),
LockOptions.NONE,
(fetchParent, querySpec, creationState) -> {
final List<Fetch> fetches = new ArrayList<>();
fetchParent.getReferencedMappingContainer().visitFetchables(
fetchable -> {
// Ignore plural attributes
if ( fetchable instanceof PluralAttributeMapping ) {
return;
}
FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming();
final boolean selectable;
if ( fetchable instanceof AttributeMapping ) {
if ( fetchParent instanceof EmbeddableResultGraphNode && ( (EmbeddableResultGraphNode) fetchParent ).getReferencedMappingContainer() == getIdentifierMapping() ) {
selectable = true;
}
else {
final int propertyNumber = ( (AttributeMapping) fetchable ).getStateArrayPosition();
final int tableNumber = getSubclassPropertyTableNumber( propertyNumber );
selectable = !isSubclassTableSequentialSelect( tableNumber )
&& propertySelectable[propertyNumber];
}
}
else {
selectable = true;
}
if ( fetchable instanceof BasicValuedModelPart ) {
// Ignore lazy basic columns
if ( fetchTiming == FetchTiming.DELAYED ) {
return;
}
}
else if ( fetchable instanceof Association ) {
final Association association = (Association) fetchable;
// Ignore the fetchable if the FK is on the other side
if ( association.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) {
return;
}
// Ensure the FK comes from the root table
if ( !rootTableName.equals( association.getForeignKeyDescriptor().getKeyTable() ) ) {
return;
}
fetchTiming = FetchTiming.DELAYED;
}
if ( selectable ) {
final NavigablePath navigablePath = fetchParent.resolveNavigablePath( fetchable );
final Fetch fetch = fetchParent.generateFetchableFetch(
fetchable,
navigablePath,
fetchTiming,
true,
null,
creationState
);
fetches.add( fetch );
}
},
null
);
return fetches;
},
this::fetchProcessor,
true,
getFactory()
);
@ -2029,6 +1967,77 @@ public abstract class AbstractEntityPersister
return expression;
}
private List<Fetch> fetchProcessor(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState) {
final List<Fetch> fetches = new ArrayList<>();
final String rootTableName = getRootTableName();
fetchParent.getReferencedMappingContainer().visitFetchables(
fetchable -> {
// Ignore plural attributes
if ( !(fetchable instanceof PluralAttributeMapping ) ) {
final FetchTiming fetchTiming;
if ( fetchable instanceof BasicValuedModelPart ) {
// Ignore lazy basic columns
fetchTiming = fetchable.getMappedFetchOptions().getTiming();
if ( fetchTiming == FetchTiming.DELAYED ) {
return;
}
}
else if ( fetchable instanceof Association ) {
final Association association = (Association) fetchable;
// Ignore the fetchable if the FK is on the other side
if ( association.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) {
return;
}
// Ensure the FK comes from the root table
if ( !rootTableName.equals( association.getForeignKeyDescriptor().getKeyTable() ) ) {
return;
}
fetchTiming = FetchTiming.DELAYED;
}
else {
fetchTiming = fetchable.getMappedFetchOptions().getTiming();
}
if ( fetchTiming == null ) {
throw new AssertionFailure("fetchTiming was null");
}
if ( isSelectable( fetchParent, fetchable ) ) {
final Fetch fetch = fetchParent.generateFetchableFetch(
fetchable,
fetchParent.resolveNavigablePath( fetchable ),
fetchTiming,
true,
null,
creationState
);
fetches.add( fetch );
}
}
},
null
);
return fetches;
}
private boolean isSelectable(FetchParent fetchParent, Fetchable fetchable) {
if ( fetchable instanceof AttributeMapping ) {
if ( fetchParent instanceof EmbeddableResultGraphNode
&& ( (EmbeddableResultGraphNode) fetchParent).getReferencedMappingContainer() == getIdentifierMapping() ) {
return true;
}
else {
final int propertyNumber = ( (AttributeMapping) fetchable).getStateArrayPosition();
// final int tableNumber = getSubclassPropertyTableNumber( propertyNumber );
// return !isSubclassTableSequentialSelect( tableNumber ) && propertySelectable[propertyNumber];
return propertySelectable[propertyNumber];
}
}
else {
return true;
}
}
@Override
public String[] getIdentifierAliases(String suffix) {
// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
@ -4285,11 +4294,8 @@ public abstract class AbstractEntityPersister
@Override
public final void postInstantiate() throws MappingException {
doLateInit();
prepareLoader( singleIdEntityLoader );
prepareLoader( multiIdEntityLoader );
doPostInstantiate();
}
private void prepareLoader(Loader loader) {
@ -4297,10 +4303,6 @@ public abstract class AbstractEntityPersister
( (Preparable) loader ).prepare();
}
}
protected void doPostInstantiate() {
}
/**
* Load an instance using either the {@code forUpdateLoader} or the outer joining {@code loader},
* depending upon the value of the {@code lock} parameter
@ -4767,14 +4769,10 @@ public abstract class AbstractEntityPersister
// for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the
// database, because we have no other way to know if the state changed while detached.
final Object naturalIdSnapshot;
final Object[] entitySnapshot = persistenceContext.getDatabaseSnapshot( id, this );
if ( entitySnapshot == StatefulPersistenceContext.NO_ROW ) {
naturalIdSnapshot = null;
}
else {
naturalIdSnapshot = naturalIdMapping.extractNaturalIdFromEntityState( entitySnapshot, session );
}
final Object naturalIdSnapshot = entitySnapshot == StatefulPersistenceContext.NO_ROW
? null
: naturalIdMapping.extractNaturalIdFromEntityState(entitySnapshot, session);
naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this );
naturalIdResolutions.manageLocalResolution(
@ -4787,17 +4785,11 @@ public abstract class AbstractEntityPersister
@Override
public Boolean isTransient(Object entity, SharedSessionContractImplementor session) throws HibernateException {
final Object id;
if ( canExtractIdOutOfEntity() ) {
id = getIdentifier( entity, session );
}
else {
id = null;
}
final Object id = canExtractIdOutOfEntity() ? getIdentifier(entity, session) : null;
// we *always* assume an instance with a null
// identifier or no identifier property is unsaved!
if ( id == null ) {
return Boolean.TRUE;
return true;
}
// check the version unsaved-value, if appropriate
@ -4823,7 +4815,7 @@ public abstract class AbstractEntityPersister
final Object ck = cache.generateCacheKey( id, this, session.getFactory(), session.getTenantIdentifier() );
final Object ce = CacheHelper.fromSharedCache( session, ck, getCacheAccessStrategy() );
if ( ce != null ) {
return Boolean.FALSE;
return false;
}
}

View File

@ -121,7 +121,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
// private final int[] subclassFormulaTableNumberClosure;
private final String[] subclassColumnClosure;
private final boolean[] subclassTableSequentialSelect;
// private final boolean[] subclassTableSequentialSelect;
// private final boolean[] subclassTableIsLazyClosure;
private final boolean[] isInverseSubclassTable;
private final boolean[] isNullableSubclassTable;
@ -311,7 +311,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
ArrayList<String> subclassTableNames = new ArrayList<>();
ArrayList<Boolean> isConcretes = new ArrayList<>();
ArrayList<Boolean> isDeferreds = new ArrayList<>();
// ArrayList<Boolean> isDeferreds = new ArrayList<>();
// ArrayList<Boolean> isLazies = new ArrayList<>();
ArrayList<Boolean> isInverses = new ArrayList<>();
ArrayList<Boolean> isNullables = new ArrayList<>();
@ -319,10 +319,10 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
keyColumns = new ArrayList<>();
for ( Table table : persistentClass.getSubclassTableClosure() ) {
isConcretes.add( persistentClass.isClassOrSuperclassTable( table ) );
isDeferreds.add( Boolean.FALSE );
// isLazies.add( Boolean.FALSE );
isInverses.add( Boolean.FALSE );
isNullables.add( Boolean.FALSE );
// isDeferreds.add( false );
// isLazies.add( false );
isInverses.add( false );
isNullables.add( false );
final String tableName = determineTableName( table );
subclassTableNames.add( tableName );
String[] key = new String[idColumnSpan];
@ -338,7 +338,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
final Table joinTable = join.getTable();
isConcretes.add( persistentClass.isClassOrSuperclassTable( joinTable ) );
isDeferreds.add( join.isSequentialSelect() );
// isDeferreds.add( join.isSequentialSelect() );
isInverses.add( join.isInverse() );
isNullables.add( join.isOptional() );
// isLazies.add( join.isLazy() );
@ -356,7 +356,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
String[] naturalOrderSubclassTableNameClosure = ArrayHelper.toStringArray( subclassTableNames );
String[][] naturalOrderSubclassTableKeyColumnClosure = ArrayHelper.to2DStringArray( keyColumns );
isClassOrSuperclassTable = ArrayHelper.toBooleanArray( isConcretes );
subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
// subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
// subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );
isInverseSubclassTable = ArrayHelper.toBooleanArray( isInverses );
isNullableSubclassTable = ArrayHelper.toBooleanArray( isNullables );
@ -729,10 +729,10 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
return isInverseTable[j];
}
@Override
protected boolean isSubclassTableSequentialSelect(int j) {
return subclassTableSequentialSelect[j] && !isClassOrSuperclassTable[j];
}
// @Override
// protected boolean isSubclassTableSequentialSelect(int j) {
// return subclassTableSequentialSelect[j] && !isClassOrSuperclassTable[j];
// }
/*public void postInstantiate() throws MappingException {
super.postInstantiate();

View File

@ -93,7 +93,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
// private final boolean[] subclassTableIsLazyClosure;
private final boolean[] isInverseSubclassTable;
private final boolean[] isNullableSubclassTable;
private final boolean[] subclassTableSequentialSelect;
// private final boolean[] subclassTableSequentialSelect;
private final String[][] subclassTableKeyColumnClosure;
private final boolean[] isClassOrSuperclassTable;
private final boolean[] isClassOrSuperclassJoin;
@ -252,7 +252,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
ArrayList<String[]> joinKeyColumns = new ArrayList<>();
ArrayList<Boolean> isConcretes = new ArrayList<>();
ArrayList<Boolean> isClassOrSuperclassJoins = new ArrayList<>();
ArrayList<Boolean> isDeferreds = new ArrayList<>();
// ArrayList<Boolean> isDeferreds = new ArrayList<>();
ArrayList<Boolean> isInverses = new ArrayList<>();
ArrayList<Boolean> isNullables = new ArrayList<>();
// ArrayList<Boolean> isLazies = new ArrayList<>();
@ -260,7 +260,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
joinKeyColumns.add( getIdentifierColumnNames() );
isConcretes.add( Boolean.TRUE );
isClassOrSuperclassJoins.add( Boolean.TRUE );
isDeferreds.add( Boolean.FALSE );
// isDeferreds.add( Boolean.FALSE );
isInverses.add( Boolean.FALSE );
isNullables.add( Boolean.FALSE );
// isLazies.add( Boolean.FALSE );
@ -271,8 +271,8 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
isNullables.add( join.isOptional() );
// isLazies.add( lazyAvailable && join.isLazy() );
boolean isDeferred = join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ;
isDeferreds.add( isDeferred );
// boolean isDeferred = join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ;
// isDeferreds.add( isDeferred );
String joinTableName = determineTableName( join.getTable() );
subclassTables.add( joinTableName );
@ -286,7 +286,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
joinKeyColumns.add( keyCols );
}
subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
// subclassTableSequentialSelect = ArrayHelper.toBooleanArray( isDeferreds );
subclassTableNameClosure = ArrayHelper.toStringArray( subclassTables );
// subclassTableIsLazyClosure = ArrayHelper.toBooleanArray( isLazies );
subclassTableKeyColumnClosure = ArrayHelper.to2DStringArray( joinKeyColumns );
@ -568,10 +568,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
return propertyTableNumbers[property] == j;
}
@Override
protected boolean isSubclassTableSequentialSelect(int j) {
return subclassTableSequentialSelect[j] && !isClassOrSuperclassTable[j];
}
// @Override
// protected boolean isSubclassTableSequentialSelect(int j) {
// return subclassTableSequentialSelect[j] && !isClassOrSuperclassTable[j];
// }
// Execute the SQL:

View File

@ -20,8 +20,9 @@ import java.io.Serializable;
*/
public interface StatementInspector extends Serializable {
/**
* Inspect the given SQL, possibly returning a different SQL to be used instead. Note that returning {@code null}
* is interpreted as returning the same SQL as was passed.
* Inspect the given SQL, possibly returning a different SQL to be used instead.
* Note that returning {@code null} is interpreted as returning the same SQL as
* was passed.
*
* @param sql The SQL to inspect
*

View File

@ -129,7 +129,7 @@ public class EntityMetamodel implements Serializable {
private final boolean inherited;
private final boolean hasSubclasses;
private final Set<String> subclassEntityNames;
private final Map<Class<?>,String> entityNameByInheritenceClassMap;
private final Map<Class<?>,String> entityNameByInheritanceClassMap;
private final BytecodeEnhancementMetadata bytecodeEnhancementMetadata;
@ -439,7 +439,7 @@ public class EntityMetamodel implements Serializable {
entityNameByInheritanceClassMapLocal.put( subclass.getMappedClass(), subclass.getEntityName() );
}
}
entityNameByInheritenceClassMap = CollectionHelper.toSmallMap( entityNameByInheritanceClassMapLocal );
entityNameByInheritanceClassMap = CollectionHelper.toSmallMap( entityNameByInheritanceClassMapLocal );
}
private static GenerationStrategyPair buildGenerationStrategyPair(
@ -1002,7 +1002,7 @@ public class EntityMetamodel implements Serializable {
* @return The mapped entity-name, or null if no such mapping was found.
*/
public String findEntityNameByEntityClass(Class<?> inheritanceClass) {
return entityNameByInheritenceClassMap.get( inheritanceClass );
return entityNameByInheritanceClassMap.get( inheritanceClass );
}
@Override

View File

@ -0,0 +1,26 @@
package org.hibernate.orm.test.secondarytable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import org.hibernate.annotations.SecondaryRow;
@Entity
@Table(name = "Overview")
@SecondaryTable(name = "Details")
@SecondaryTable(name = "Extras")
@SecondaryRow(table = "Details", optional = false)
@SecondaryRow(table = "Extras", optional = true)
@SequenceGenerator(name="RecordSeq", sequenceName = "RecordId", allocationSize = 1)
public class Record {
@Id @GeneratedValue(generator = "RecordSeq") long id;
String name;
@Column(table = "Details") String text;
@Column(table = "Details") boolean enabled;
@Column(table = "Extras", name="`comment`") String comment;
}

View File

@ -0,0 +1,100 @@
package org.hibernate.orm.test.secondarytable;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DomainModel(annotatedClasses = {Record.class,SpecialRecord.class})
@SessionFactory
public class SecondaryRowTest {
@Test
public void testSecondaryRow(SessionFactoryScope scope) {
int seq = scope.getSessionFactory().getJdbcServices().getDialect().getSequenceSupport().supportsSequences()
? 1 : 0;
Record record = new Record();
record.enabled = true;
record.text = "Hello World!";
scope.inTransaction(s -> s.persist(record));
scope.getCollectingStatementInspector().assertExecutedCount(2+seq);
scope.getCollectingStatementInspector().clear();
Record record2 = new Record();
record2.enabled = true;
record2.text = "Hello World!";
record2.comment = "Goodbye";
scope.inTransaction(s -> s.persist(record2));
scope.getCollectingStatementInspector().assertExecutedCount(3+seq);
scope.getCollectingStatementInspector().clear();
SpecialRecord specialRecord = new SpecialRecord();
specialRecord.enabled = true;
specialRecord.text = "Hello World!";
specialRecord.validated = LocalDateTime.now();
scope.inTransaction(s -> s.persist(specialRecord));
scope.getCollectingStatementInspector().assertExecutedCount(3+seq);
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> assertNotNull(s.find(Record.class, record.id)));
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> assertNotNull(s.find(Record.class, record2.id)));
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> assertNotNull(s.find(Record.class, specialRecord.id)));
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> assertEquals(3, s.createQuery("from Record").getResultList().size()));
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> assertEquals(1, s.createQuery("from SpecialRecord").getResultList().size()));
scope.getCollectingStatementInspector().assertExecutedCount(1);
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> {
Record r = s.find(Record.class, record.id);
r.text = "new text";
r.comment = "the comment";
});
scope.getCollectingStatementInspector().assertExecutedCount(3);
assertTrue( scope.getCollectingStatementInspector().getSqlQueries().get(1).startsWith("update ") );
assertTrue( scope.getCollectingStatementInspector().getSqlQueries().get(2).startsWith("insert ") );
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> {
Record r = s.find(Record.class, record.id);
r.comment = "new comment";
});
scope.getCollectingStatementInspector().assertExecutedCount(2);
assertTrue( scope.getCollectingStatementInspector().getSqlQueries().get(1).startsWith("update ") );
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> {
Record r = s.find(Record.class, record2.id);
r.comment = null;
});
scope.getCollectingStatementInspector().assertExecutedCount(2);
assertTrue( scope.getCollectingStatementInspector().getSqlQueries().get(1).startsWith("delete ") );
scope.getCollectingStatementInspector().clear();
scope.inTransaction(s -> {
SpecialRecord r = s.find(SpecialRecord.class, specialRecord.id);
r.validated = null;
r.timestamp = System.currentTimeMillis();
});
scope.getCollectingStatementInspector().assertExecutedCount(2);
assertTrue( scope.getCollectingStatementInspector().getSqlQueries().get(1).startsWith("delete ") );
scope.getCollectingStatementInspector().clear();
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.orm.test.secondarytable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.SecondaryTable;
import org.hibernate.annotations.SecondaryRow;
import java.time.LocalDateTime;
@Entity
@SecondaryTable(name = "Options")
@SecondaryTable(name = "`View`")
@SecondaryRow(table = "`View`", owned = false)
public class SpecialRecord extends Record {
@Column(table = "Options")
LocalDateTime validated;
@Column(table = "`View`", name="`timestamp`")
Long timestamp;
}