HHH-14526 validate table repetition in JOINED hierarchies

and automatically force the discriminator when necessary
This commit is contained in:
Gavin 2022-12-26 01:02:39 +01:00 committed by Gavin King
parent 0f29c15461
commit 01d608ca84
8 changed files with 93 additions and 66 deletions

View File

@ -32,8 +32,8 @@ public class JoinedSubclass extends Subclass implements TableOwner {
} }
public void setTable(Table table) { public void setTable(Table table) {
this.table=table; this.table = table;
getSuperclass().addSubclassTable(table); getSuperclass().addSubclassTable( table );
} }
public KeyValue getKey() { public KeyValue getKey() {

View File

@ -37,6 +37,10 @@ import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.Alias; import org.hibernate.sql.Alias;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static org.hibernate.internal.util.StringHelper.root;
/** /**
* A mapping model object that represents an {@linkplain jakarta.persistence.Entity entity class}. * A mapping model object that represents an {@linkplain jakarta.persistence.Entity entity class}.
* *
@ -239,8 +243,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
List<Subclass>[] subclassLists = new List[subclasses.size() + 1]; List<Subclass>[] subclassLists = new List[subclasses.size() + 1];
int j; int j;
for (j = 0; j < subclasses.size(); j++) { for (j = 0; j < subclasses.size(); j++) {
Subclass subclass = subclasses.get(j); subclassLists[j] = subclasses.get(j).getSubclasses();
subclassLists[j] = subclass.getSubclasses();
} }
subclassLists[j] = subclasses; subclassLists[j] = subclasses;
return new JoinedList<>( subclassLists ); return new JoinedList<>( subclassLists );
@ -254,14 +257,14 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
@Deprecated(since = "6.0") @Remove @Deprecated(since = "6.0") @Remove
public Iterator<Subclass> getSubclassIterator() { public Iterator<Subclass> getSubclassIterator() {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Iterator<Subclass>[] iters = new Iterator[subclasses.size() + 1]; final Iterator<Subclass>[] iterators = new Iterator[subclasses.size() + 1];
final Iterator<Subclass> iter = subclasses.iterator(); final Iterator<Subclass> iterator = subclasses.iterator();
int i = 0; int i = 0;
while ( iter.hasNext() ) { while ( iterator.hasNext() ) {
iters[i++] = iter.next().getSubclassIterator(); iterators[i++] = iterator.next().getSubclassIterator();
} }
iters[i] = subclasses.iterator(); iterators[i] = subclasses.iterator();
return new JoinedIterator<>( iters ); return new JoinedIterator<>( iterators );
} }
public List<PersistentClass> getSubclassClosure() { public List<PersistentClass> getSubclassClosure() {
@ -275,12 +278,12 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
@Deprecated(since = "6.0") @Remove @Deprecated(since = "6.0") @Remove
public Iterator<PersistentClass> getSubclassClosureIterator() { public Iterator<PersistentClass> getSubclassClosureIterator() {
final ArrayList<Iterator<PersistentClass>> iters = new ArrayList<>(); final ArrayList<Iterator<PersistentClass>> iterators = new ArrayList<>();
iters.add( new SingletonIterator<>( this ) ); iterators.add( new SingletonIterator<>( this ) );
for ( Subclass subclass : getSubclasses() ) { for ( Subclass subclass : getSubclasses() ) {
iters.add( subclass.getSubclassClosureIterator() ); iterators.add( subclass.getSubclassClosureIterator() );
} }
return new JoinedIterator<>( iters ); return new JoinedIterator<>( iterators );
} }
public Table getIdentityTable() { public Table getIdentityTable() {
@ -403,7 +406,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
final ArrayList<List<Property>> lists = new ArrayList<>(); final ArrayList<List<Property>> lists = new ArrayList<>();
lists.add( getPropertyClosure() ); lists.add( getPropertyClosure() );
lists.add( subclassProperties ); lists.add( subclassProperties );
for (Join join : subclassJoins) { for ( Join join : subclassJoins ) {
lists.add( join.getProperties() ); lists.add( join.getProperties() );
} }
return new JoinedList<>( lists ); return new JoinedList<>( lists );
@ -537,7 +540,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
try { try {
return getRecursiveProperty( propertyPath, getReferenceableProperties() ); return getRecursiveProperty( propertyPath, getReferenceableProperties() );
} }
catch (MappingException e) { catch ( MappingException e ) {
throw new MappingException( throw new MappingException(
"property-ref [" + propertyPath + "] not found on entity [" + getEntityName() + "]", e "property-ref [" + propertyPath + "] not found on entity [" + getEntityName() + "]", e
); );
@ -548,7 +551,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
try { try {
return getRecursiveProperty( propertyPath, getPropertyClosure() ); return getRecursiveProperty( propertyPath, getPropertyClosure() );
} }
catch (MappingException e) { catch ( MappingException e ) {
throw new MappingException( throw new MappingException(
"property [" + propertyPath + "] not found on entity [" + getEntityName() + "]", e "property [" + propertyPath + "] not found on entity [" + getEntityName() + "]", e
); );
@ -577,7 +580,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
// of the embedded composite identifier properties // of the embedded composite identifier properties
property = identifierProperty; property = identifierProperty;
} }
catch (MappingException ignore) { catch ( MappingException ignore ) {
// ignore it... // ignore it...
} }
} }
@ -592,7 +595,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
} }
} }
} }
catch (MappingException e) { catch ( MappingException e ) {
throw new MappingException( "property [" + propertyPath + "] not found on entity [" + getEntityName() + "]" ); throw new MappingException( "property [" + propertyPath + "] not found on entity [" + getEntityName() + "]" );
} }
@ -600,7 +603,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
} }
private Property getProperty(String propertyName, List<Property> properties) throws MappingException { private Property getProperty(String propertyName, List<Property> properties) throws MappingException {
String root = StringHelper.root( propertyName ); String root = root( propertyName );
for ( Property prop : properties ) { for ( Property prop : properties ) {
if ( prop.getName().equals( root ) if ( prop.getName().equals( root )
|| ( prop instanceof Backref || prop instanceof IndexBackref ) || ( prop instanceof Backref || prop instanceof IndexBackref )
@ -614,7 +617,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
public Property getProperty(String propertyName) throws MappingException { public Property getProperty(String propertyName) throws MappingException {
Property identifierProperty = getIdentifierProperty(); Property identifierProperty = getIdentifierProperty();
if ( identifierProperty != null if ( identifierProperty != null
&& identifierProperty.getName().equals( StringHelper.root( propertyName ) ) ) { && identifierProperty.getName().equals( root( propertyName ) ) ) {
return identifierProperty; return identifierProperty;
} }
else { else {
@ -628,17 +631,16 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
} }
public Property getSubclassProperty(String propertyName) throws MappingException { public Property getSubclassProperty(String propertyName) throws MappingException {
Property identifierProperty = getIdentifierProperty(); final Property identifierProperty = getIdentifierProperty();
if ( identifierProperty != null if ( identifierProperty != null
&& identifierProperty.getName().equals( StringHelper.root( propertyName ) ) ) { && identifierProperty.getName().equals( root( propertyName ) ) ) {
return identifierProperty; return identifierProperty;
} }
else { else {
List<Property> closure = getSubclassPropertyClosure(); final Component identifierMapper = getIdentifierMapper();
Component identifierMapper = getIdentifierMapper(); final List<Property> closure = identifierMapper != null
if ( identifierMapper != null ) { ? new JoinedList<>( identifierMapper.getProperties(), getSubclassPropertyClosure() )
closure = new JoinedList<>( identifierMapper.getProperties(), closure ); : getSubclassPropertyClosure();
}
return getProperty( propertyName, closure ); return getProperty( propertyName, closure );
} }
} }
@ -657,7 +659,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
} }
for ( Property property : getPropertyClosure() ) { for ( Property property : getPropertyClosure() ) {
if (property.getName().equals(name)) { if ( property.getName().equals(name) ) {
return true; return true;
} }
} }
@ -691,7 +693,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
public boolean isPropertyDefinedInHierarchy(String name) { public boolean isPropertyDefinedInHierarchy(String name) {
return hasProperty( name ) return hasProperty( name )
|| getSuperMappedSuperclass() != null && getSuperMappedSuperclass().isPropertyDefinedInHierarchy( name ) || getSuperMappedSuperclass() != null && getSuperMappedSuperclass().isPropertyDefinedInHierarchy( name )
||getSuperclass() != null && getSuperclass().isPropertyDefinedInHierarchy(name); || getSuperclass() != null && getSuperclass().isPropertyDefinedInHierarchy( name );
} }
/** /**
@ -721,9 +723,9 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
public void validate(Metadata mapping) throws MappingException { public void validate(Metadata mapping) throws MappingException {
for ( Property prop : getProperties() ) { for ( Property prop : getProperties() ) {
if ( !prop.isValid( mapping ) ) { if ( !prop.isValid( mapping ) ) {
Type type = prop.getType(); final Type type = prop.getType();
int actualColumns = prop.getColumnSpan(); final int actualColumns = prop.getColumnSpan();
int requiredColumns = type.getColumnSpan(mapping); final int requiredColumns = type.getColumnSpan(mapping);
throw new MappingException( throw new MappingException(
"Property '" + StringHelper.qualify( getEntityName(), prop.getName() ) "Property '" + StringHelper.qualify( getEntityName(), prop.getName() )
+ "' maps to " + actualColumns + " columns but " + requiredColumns + "' maps to " + actualColumns + " columns but " + requiredColumns
@ -835,7 +837,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
public Iterator<Property> getPropertyIterator() { public Iterator<Property> getPropertyIterator() {
final ArrayList<Iterator<Property>> iterators = new ArrayList<>(); final ArrayList<Iterator<Property>> iterators = new ArrayList<>();
iterators.add( properties.iterator() ); iterators.add( properties.iterator() );
for (Join join : joins) { for ( Join join : joins ) {
iterators.add( join.getPropertyIterator() ); iterators.add( join.getPropertyIterator() );
} }
return new JoinedIterator<>( iterators ); return new JoinedIterator<>( iterators );
@ -857,7 +859,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
public List<Property> getProperties() { public List<Property> getProperties() {
final ArrayList<List<Property>> list = new ArrayList<>(); final ArrayList<List<Property>> list = new ArrayList<>();
list.add( properties ); list.add( properties );
for (Join join : joins) { for ( Join join : joins ) {
list.add( join.getProperties() ); list.add( join.getProperties() );
} }
return new JoinedList<>( list ); return new JoinedList<>( list );
@ -1043,10 +1045,10 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
protected void checkPropertyColumnDuplication(Set<String> distinctColumns, List<Property> properties) protected void checkPropertyColumnDuplication(Set<String> distinctColumns, List<Property> properties)
throws MappingException { throws MappingException {
for ( Property prop : properties ) { for ( Property prop : properties ) {
Value value = prop.getValue(); final Value value = prop.getValue();
if ( value instanceof Component ) { if ( value instanceof Component ) {
Component component = (Component) value; final Component component = (Component) value;
AggregateColumn aggregateColumn = component.getAggregateColumn(); final AggregateColumn aggregateColumn = component.getAggregateColumn();
if ( aggregateColumn == null ) { if ( aggregateColumn == null ) {
checkPropertyColumnDuplication( distinctColumns, component.getProperties() ); checkPropertyColumnDuplication( distinctColumns, component.getProperties() );
} }
@ -1155,10 +1157,7 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
} }
public java.util.List<CallbackDefinition> getCallbackDefinitions() { public java.util.List<CallbackDefinition> getCallbackDefinitions() {
if ( callbackDefinitions == null ) { return callbackDefinitions == null ? emptyList() : unmodifiableList( callbackDefinitions );
return Collections.emptyList();
}
return Collections.unmodifiableList( callbackDefinitions );
} }
public void setIdentifierMapper(Component handle) { public void setIdentifierMapper(Component handle) {
@ -1186,9 +1185,9 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
// The following methods are added to support @MappedSuperclass in the metamodel // The following methods are added to support @MappedSuperclass in the metamodel
public List<Property> getDeclaredProperties() { public List<Property> getDeclaredProperties() {
ArrayList<List<Property>> lists = new ArrayList<>(); final ArrayList<List<Property>> lists = new ArrayList<>();
lists.add( declaredProperties ); lists.add( declaredProperties );
for (Join join : joins) { for ( Join join : joins ) {
lists.add( join.getDeclaredProperties() ); lists.add( join.getDeclaredProperties() );
} }
return new JoinedList<>( lists ); return new JoinedList<>( lists );
@ -1196,9 +1195,9 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
@Deprecated(since = "6.0") @Deprecated(since = "6.0")
public Iterator<Property> getDeclaredPropertyIterator() { public Iterator<Property> getDeclaredPropertyIterator() {
ArrayList<Iterator<Property>> iterators = new ArrayList<>(); final ArrayList<Iterator<Property>> iterators = new ArrayList<>();
iterators.add( declaredProperties.iterator() ); iterators.add( declaredProperties.iterator() );
for (Join join : joins) { for ( Join join : joins ) {
iterators.add( join.getDeclaredPropertyIterator() ); iterators.add( join.getDeclaredPropertyIterator() );
} }
return new JoinedIterator<>( iterators ); return new JoinedIterator<>( iterators );

View File

@ -18,10 +18,11 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.SingletonIterator; import org.hibernate.internal.util.collections.SingletonIterator;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
/** /**
* A mapping model object that represents the root class in an entity class * A mapping model object that represents the root class in an entity class
* {@linkplain jakarta.persistence.Inheritance inheritance} hierarchy. * {@linkplain jakarta.persistence.Inheritance inheritance} hierarchy.
@ -294,11 +295,38 @@ public class RootClass extends PersistentClass implements TableOwner {
); );
} }
checkCompositeIdentifier(); checkCompositeIdentifier();
checkTableDuplication();
}
private void checkTableDuplication() {
if ( hasSubclasses() ) {
final Set<Table> tables = new HashSet<>();
tables.add( getTable() );
for ( Subclass subclass : getSubclasses() ) {
if ( !(subclass instanceof SingleTableSubclass) ) {
final Table table = subclass.getTable();
if ( !tables.add( table ) ) {
if ( getDiscriminator() == null ) {
throw new MappingException( "Two different subclasses of '" + getEntityName()
+ "' map to the table '" + table.getName()
+ "' and the hierarchy has no discriminator column" );
}
else {
// This is arguably not the right place to do this.
// Perhaps it's an issue better dealt with later on
// by the persisters.
forceDiscriminator = true;
}
break;
}
}
}
}
} }
private void checkCompositeIdentifier() { private void checkCompositeIdentifier() {
if ( getIdentifier() instanceof Component ) { if ( getIdentifier() instanceof Component ) {
Component id = (Component) getIdentifier(); final Component id = (Component) getIdentifier();
if ( !id.isDynamic() ) { if ( !id.isDynamic() ) {
final Class<?> idClass = id.getComponentClass(); final Class<?> idClass = id.getComponentClass();
if ( idClass != null ) { if ( idClass != null ) {
@ -328,7 +356,7 @@ public class RootClass extends PersistentClass implements TableOwner {
} }
public void setCacheRegionName(String cacheRegionName) { public void setCacheRegionName(String cacheRegionName) {
this.cacheRegionName = StringHelper.nullIfEmpty( cacheRegionName ); this.cacheRegionName = nullIfEmpty( cacheRegionName );
} }
public boolean isLazyPropertiesCacheable() { public boolean isLazyPropertiesCacheable() {
@ -359,7 +387,7 @@ public class RootClass extends PersistentClass implements TableOwner {
} }
public Set<Table> getIdentityTables() { public Set<Table> getIdentityTables() {
Set<Table> tables = new HashSet<>(); final Set<Table> tables = new HashSet<>();
for ( PersistentClass clazz : getSubclassClosure() ) { for ( PersistentClass clazz : getSubclassClosure() ) {
if ( clazz.isAbstract() == null || !clazz.isAbstract() ) { if ( clazz.isAbstract() == null || !clazz.isAbstract() ) {
tables.add( clazz.getIdentityTable() ); tables.add( clazz.getIdentityTable() );

View File

@ -282,12 +282,14 @@ public class Subclass extends PersistentClass {
@Override @Override
public boolean isClassOrSuperclassJoin(Join join) { public boolean isClassOrSuperclassJoin(Join join) {
return super.isClassOrSuperclassJoin(join) || getSuperclass().isClassOrSuperclassJoin(join); return super.isClassOrSuperclassJoin( join )
|| getSuperclass().isClassOrSuperclassJoin( join );
} }
@Override @Override
public boolean isClassOrSuperclassTable(Table table) { public boolean isClassOrSuperclassTable(Table table) {
return super.isClassOrSuperclassTable(table) || getSuperclass().isClassOrSuperclassTable(table); return super.isClassOrSuperclassTable( table )
|| getSuperclass().isClassOrSuperclassTable( table );
} }
@Override @Override
@ -307,8 +309,8 @@ public class Subclass extends PersistentClass {
@Override @Override
public java.util.Set<String> getSynchronizedTables() { public java.util.Set<String> getSynchronizedTables() {
HashSet<String> result = new HashSet<>(); final HashSet<String> result = new HashSet<>();
result.addAll(synchronizedTables); result.addAll( synchronizedTables );
result.addAll( getSuperclass().getSynchronizedTables() ); result.addAll( getSuperclass().getSynchronizedTables() );
return result; return result;
} }
@ -320,15 +322,15 @@ public class Subclass extends PersistentClass {
@Override @Override
public java.util.List<FilterConfiguration> getFilters() { public java.util.List<FilterConfiguration> getFilters() {
java.util.List<FilterConfiguration> filters = new ArrayList<>(super.getFilters()); final ArrayList<FilterConfiguration> filters = new ArrayList<>( super.getFilters() );
filters.addAll(getSuperclass().getFilters()); filters.addAll( getSuperclass().getFilters() );
return filters; return filters;
} }
@Override @Override
public boolean hasSubselectLoadableCollections() { public boolean hasSubselectLoadableCollections() {
return super.hasSubselectLoadableCollections() || return super.hasSubselectLoadableCollections()
getSuperclass().hasSubselectLoadableCollections(); || getSuperclass().hasSubselectLoadableCollections();
} }
@Override @Override

View File

@ -7,9 +7,11 @@
package org.hibernate.mapping; package org.hibernate.mapping;
/** /**
* Additional, optional contract as part pf the {@link PersistentClass} * Optional contract implemented by some subtypes of {@link PersistentClass}.
* hierarchy used to differentiate entity bindings for entities that map to their own table * <p>
* (root, union-subclass, joined-subclass) versus those that do not (discriminator-subclass). * Differentiates entity types that map to their own table ({@link RootClass},
* {@link UnionSubclass}, and {@link JoinedSubclass}) from those which do not
* ({@link SingleTableSubclass}).
* *
* @author Emmanuel Bernard * @author Emmanuel Bernard
* @author Steve Ebersole * @author Steve Ebersole

View File

@ -30,7 +30,7 @@ public class UnionSubclass extends Subclass implements TableOwner {
public void setTable(Table table) { public void setTable(Table table) {
this.table = table; this.table = table;
getSuperclass().addSubclassTable(table); getSuperclass().addSubclassTable( table );
} }
public java.util.Set<String> getSynchronizedTables() { public java.util.Set<String> getSynchronizedTables() {

View File

@ -14,7 +14,6 @@ import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.annotations.DiscriminatorOptions;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test; import org.junit.Test;
@ -110,7 +109,6 @@ public class RepeatedSubclassTableTest extends BaseCoreFunctionalTestCase {
@Table(name = "DATA_TYPE") @Table(name = "DATA_TYPE")
@Inheritance(strategy = JOINED) @Inheritance(strategy = JOINED)
@DiscriminatorColumn(name = "supertype_id") @DiscriminatorColumn(name = "supertype_id")
@DiscriminatorOptions(force = true)
public static abstract class DataType { public static abstract class DataType {
private Long id; private Long id;

View File

@ -2,7 +2,6 @@ package org.hibernate.orm.test.inheritance.repeatedtable;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.annotations.DiscriminatorOptions;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
@ -101,7 +100,6 @@ public class RepeatedTableTest extends BaseCoreFunctionalTestCase {
@Table(name = "DATA_TYPE") @Table(name = "DATA_TYPE")
@Inheritance(strategy = JOINED) @Inheritance(strategy = JOINED)
@DiscriminatorColumn(name = "supertype_id") @DiscriminatorColumn(name = "supertype_id")
@DiscriminatorOptions(force = true)
public static abstract class DataType { public static abstract class DataType {
private Long id; private Long id;