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:
parent
1ff2b4e176
commit
87211e7cd4
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -1741,7 +1741,6 @@ public class ModelBinder {
|
|||
|
||||
bindCustomSql( secondaryTableSource, secondaryTableJoin );
|
||||
|
||||
secondaryTableJoin.setSequentialSelect( secondaryTableSource.getFetchStyle() == FetchStyle.SELECT );
|
||||
secondaryTableJoin.setInverse( secondaryTableSource.isInverse() );
|
||||
secondaryTableJoin.setOptional( secondaryTableSource.isOptional() );
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue