refactor handling of NaturalId unique keys

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-06-15 17:39:21 +02:00 committed by Steve Ebersole
parent 3d686a3b97
commit cc272f704e
8 changed files with 151 additions and 231 deletions

View File

@ -1300,9 +1300,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
} }
if ( logicalName == null ) { if ( logicalName == null ) {
throw new MappingException( throw new MappingException( "Unable to find column with physical name '"
"Unable to find column with physical name " + physicalNameString + " in table " + table.getName() + physicalNameString + "' in table '" + table.getName() + "'" );
);
} }
return logicalName.render(); return logicalName.render();
} }

View File

@ -1011,17 +1011,6 @@ public class AnnotatedColumn {
return columns; return columns;
} }
void addUniqueKey(String uniqueKeyName, boolean inSecondPass) {
final UniqueKeySecondPass secondPass =
new UniqueKeySecondPass( uniqueKeyName, this, getBuildingContext() );
if ( inSecondPass ) {
secondPass.doSecondPass( getBuildingContext().getMetadataCollector().getEntityBindingMap() );
}
else {
getBuildingContext().getMetadataCollector().addSecondPass( secondPass );
}
}
@Override @Override
public String toString() { public String toString() {
StringBuilder string = new StringBuilder(); StringBuilder string = new StringBuilder();

View File

@ -14,6 +14,7 @@ import java.util.Map;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Join; import org.hibernate.mapping.Join;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table; import org.hibernate.mapping.Table;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
@ -65,6 +66,12 @@ public class AnnotatedColumns {
this.propertyName = propertyName; this.propertyName = propertyName;
} }
Property resolveProperty() {
return buildingContext.getMetadataCollector().getEntityBindingMap()
.get( propertyHolder.getPersistentClass().getEntityName() )
.getReferencedProperty( propertyName );
}
public void setBuildingContext(MetadataBuildingContext buildingContext) { public void setBuildingContext(MetadataBuildingContext buildingContext) {
this.buildingContext = buildingContext; this.buildingContext = buildingContext;
} }

View File

@ -66,7 +66,6 @@ import static org.hibernate.internal.util.StringHelper.qualify;
public class AnnotatedJoinColumns extends AnnotatedColumns { public class AnnotatedJoinColumns extends AnnotatedColumns {
private final List<AnnotatedJoinColumn> columns = new ArrayList<>(); private final List<AnnotatedJoinColumn> columns = new ArrayList<>();
private String propertyName; // this is really a .-separated property path
private String referencedProperty; private String referencedProperty;
@ -105,15 +104,12 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
} }
} }
handlePropertyRef( inferredData.getAttributeMember(), parent, context ); handlePropertyRef( inferredData.getAttributeMember(), parent );
return parent; return parent;
} }
private static void handlePropertyRef( private static void handlePropertyRef(MemberDetails attributeMember, AnnotatedJoinColumns parent) {
MemberDetails attributeMember,
AnnotatedJoinColumns parent,
MetadataBuildingContext context) {
final PropertyRef propertyRefUsage = attributeMember.getDirectAnnotationUsage( PropertyRef.class ); final PropertyRef propertyRefUsage = attributeMember.getDirectAnnotationUsage( PropertyRef.class );
if ( propertyRefUsage == null ) { if ( propertyRefUsage == null ) {
return; return;
@ -138,7 +134,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
joinColumns.setPropertyHolder( propertyHolder ); joinColumns.setPropertyHolder( propertyHolder );
joinColumns.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) ); joinColumns.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) );
AnnotatedJoinColumn.buildJoinFormula( joinFormula, joinColumns ); AnnotatedJoinColumn.buildJoinFormula( joinFormula, joinColumns );
handlePropertyRef( inferredData.getAttributeMember(), joinColumns, context ); handlePropertyRef( inferredData.getAttributeMember(), joinColumns );
return joinColumns; return joinColumns;
} }
@ -208,7 +204,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
); );
} }
} }
handlePropertyRef( memberDetails, parent, context ); handlePropertyRef( memberDetails, parent );
return parent; return parent;
} }
@ -236,7 +232,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn( parent, propertyHolder, inferredData, joinColumn ); AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn( parent, propertyHolder, inferredData, joinColumn );
} }
} }
handlePropertyRef( inferredData.getAttributeMember(), parent, context ); handlePropertyRef( inferredData.getAttributeMember(), parent );
return parent; return parent;
} }
@ -508,19 +504,6 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
|| getMappedByPropertyName() != null; || getMappedByPropertyName() != null;
} }
/**
* A property path relative to the {@link #getPropertyHolder() PropertyHolder}.
*/
@Override
public String getPropertyName() {
return propertyName;
}
@Override
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
private ImplicitJoinColumnNameSource.Nature getImplicitNature() { private ImplicitJoinColumnNameSource.Nature getImplicitNature() {
if ( getPropertyHolder().isEntity() ) { if ( getPropertyHolder().isEntity() ) {
return ImplicitJoinColumnNameSource.Nature.ENTITY; return ImplicitJoinColumnNameSource.Nature.ENTITY;

View File

@ -0,0 +1,119 @@
/*
* 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.boot.model.internal;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.NaturalId;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitUniqueKeyNameSource;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.models.spi.MemberDetails;
import java.util.List;
import static java.util.Collections.singletonList;
import static org.hibernate.boot.model.naming.Identifier.toIdentifier;
/**
* @author Gavin King
*/
class NaturalIdBinder {
static void addNaturalIds(
boolean inSecondPass,
MemberDetails property,
AnnotatedColumns columns,
AnnotatedJoinColumns joinColumns,
MetadataBuildingContext context) {
// Natural ID columns must reside in one single UniqueKey within the Table.
// For now, simply ensure consistent naming.
final NaturalId naturalId = property.getDirectAnnotationUsage( NaturalId.class );
if ( naturalId != null ) {
final AnnotatedColumns annotatedColumns = joinColumns != null ? joinColumns : columns;
final Identifier name = uniqueKeyName( context, annotatedColumns );
if ( inSecondPass ) {
addColumnsToUniqueKey( annotatedColumns, name );
}
else {
context.getMetadataCollector()
.addSecondPass( persistentClasses -> addColumnsToUniqueKey( annotatedColumns, name ) );
}
}
}
private static Identifier uniqueKeyName(MetadataBuildingContext context, AnnotatedColumns annotatedColumns) {
return context.getBuildingOptions().getImplicitNamingStrategy()
.determineUniqueKeyName( new NaturalIdNameSource( annotatedColumns.getTable(), context) );
}
private static void addColumnsToUniqueKey(AnnotatedColumns columns, Identifier name) {
final InFlightMetadataCollector collector = columns.getBuildingContext().getMetadataCollector();
final Table table = columns.getTable();
final UniqueKey uniqueKey = table.getOrCreateUniqueKey( name.render( collector.getDatabase().getDialect() ) );
final Property property = columns.resolveProperty();
if ( property.isComposite() ) {
for ( Selectable selectable : property.getValue().getSelectables() ) {
if ( selectable instanceof org.hibernate.mapping.Column) {
uniqueKey.addColumn( tableColumn( (org.hibernate.mapping.Column) selectable, table, collector ) );
}
}
}
else {
for ( AnnotatedColumn column : columns.getColumns() ) {
uniqueKey.addColumn( tableColumn( column.getMappingColumn(), table, collector ) );
}
}
}
private static org.hibernate.mapping.Column tableColumn(
org.hibernate.mapping.Column column, Table table, InFlightMetadataCollector collector) {
final String columnName = collector.getLogicalColumnName( table, column.getQuotedName() );
final org.hibernate.mapping.Column tableColumn = table.getColumn( collector, columnName );
if ( tableColumn == null ) {
throw new AnnotationException(
"Table '" + table.getName() + "' has no column named '" + columnName
+ "' matching the column specified in '@Index'"
);
}
return tableColumn;
}
private static class NaturalIdNameSource implements ImplicitUniqueKeyNameSource {
private final Table table;
private final MetadataBuildingContext context;
NaturalIdNameSource(Table table, MetadataBuildingContext context) {
this.table = table;
this.context = context;
}
@Override
public Identifier getTableName() {
return table.getNameIdentifier();
}
@Override
public List<Identifier> getColumnNames() {
return singletonList( toIdentifier("_NaturalID") );
}
@Override
public Identifier getUserProvidedIdentifier() {
return null;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return context;
}
}
}

View File

@ -25,10 +25,6 @@ import org.hibernate.annotations.OptimisticLock;
import org.hibernate.annotations.Parent; import org.hibernate.annotations.Parent;
import org.hibernate.binder.AttributeBinder; import org.hibernate.binder.AttributeBinder;
import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
import org.hibernate.boot.model.naming.ImplicitUniqueKeyNameSource;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.models.JpaAnnotations; import org.hibernate.boot.models.JpaAnnotations;
import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector;
@ -79,7 +75,6 @@ import jakarta.persistence.OneToOne;
import jakarta.persistence.Version; import jakarta.persistence.Version;
import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.FetchType.LAZY;
import static java.util.Collections.singletonList;
import static org.hibernate.boot.model.internal.AnyBinder.bindAny; import static org.hibernate.boot.model.internal.AnyBinder.bindAny;
import static org.hibernate.boot.model.internal.BinderHelper.getMappedSuperclassOrNull; import static org.hibernate.boot.model.internal.BinderHelper.getMappedSuperclassOrNull;
import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.getPath;
@ -90,13 +85,14 @@ import static org.hibernate.boot.model.internal.ClassPropertyHolder.prepareActua
import static org.hibernate.boot.model.internal.CollectionBinder.bindCollection; import static org.hibernate.boot.model.internal.CollectionBinder.bindCollection;
import static org.hibernate.boot.model.internal.EmbeddableBinder.createCompositeBinder; import static org.hibernate.boot.model.internal.EmbeddableBinder.createCompositeBinder;
import static org.hibernate.boot.model.internal.EmbeddableBinder.createEmbeddable; import static org.hibernate.boot.model.internal.EmbeddableBinder.createEmbeddable;
import static org.hibernate.boot.model.internal.EmbeddableBinder.determineCustomInstantiator;
import static org.hibernate.boot.model.internal.EmbeddableBinder.isEmbedded; import static org.hibernate.boot.model.internal.EmbeddableBinder.isEmbedded;
import static org.hibernate.boot.model.internal.GeneratorBinder.createIdGeneratorsFromGeneratorAnnotations; import static org.hibernate.boot.model.internal.GeneratorBinder.createIdGeneratorsFromGeneratorAnnotations;
import static org.hibernate.boot.model.internal.GeneratorBinder.createValueGeneratorFromAnnotations; import static org.hibernate.boot.model.internal.GeneratorBinder.createValueGeneratorFromAnnotations;
import static org.hibernate.boot.model.internal.NaturalIdBinder.addNaturalIds;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.resolveTimeZoneStorageCompositeUserType; import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.resolveTimeZoneStorageCompositeUserType;
import static org.hibernate.boot.model.internal.ToOneBinder.bindManyToOne; import static org.hibernate.boot.model.internal.ToOneBinder.bindManyToOne;
import static org.hibernate.boot.model.internal.ToOneBinder.bindOneToOne; import static org.hibernate.boot.model.internal.ToOneBinder.bindOneToOne;
import static org.hibernate.boot.model.naming.Identifier.toIdentifier;
import static org.hibernate.id.IdentifierGeneratorHelper.getForeignId; import static org.hibernate.id.IdentifierGeneratorHelper.getForeignId;
import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualify;
@ -764,18 +760,14 @@ public class PropertyBinder {
MetadataBuildingContext context, MetadataBuildingContext context,
Map<ClassDetails, InheritanceState> inheritanceStatePerClass, Map<ClassDetails, InheritanceState> inheritanceStatePerClass,
MemberDetails property) { MemberDetails property) {
final TypeDetails attributeTypeDetails = inferredData.getAttributeMember().isPlural() final TypeDetails attributeTypeDetails =
inferredData.getAttributeMember().isPlural()
? inferredData.getAttributeMember().getType() ? inferredData.getAttributeMember().getType()
: inferredData.getClassOrElementType(); : inferredData.getClassOrElementType();
final ClassDetails attributeClassDetails = attributeTypeDetails.determineRawClass(); final ClassDetails attributeClassDetails = attributeTypeDetails.determineRawClass();
final ColumnsBuilder columnsBuilder = new ColumnsBuilder( final ColumnsBuilder columnsBuilder =
propertyHolder, new ColumnsBuilder( propertyHolder, nullability, property, inferredData, entityBinder, context )
nullability, .extractMetadata();
property,
inferredData,
entityBinder,
context
).extractMetadata();
final PropertyBinder propertyBinder = new PropertyBinder(); final PropertyBinder propertyBinder = new PropertyBinder();
propertyBinder.setName( inferredData.getPropertyName() ); propertyBinder.setName( inferredData.getPropertyName() );
@ -941,11 +933,6 @@ public class PropertyBinder {
|| property.hasDirectAnnotationUsage( ManyToAny.class ); || property.hasDirectAnnotationUsage( ManyToAny.class );
} }
private static boolean isForcePersist(MemberDetails property) {
return property.hasDirectAnnotationUsage( MapsId.class )
|| property.hasDirectAnnotationUsage( Id.class );
}
private static void bindVersionProperty( private static void bindVersionProperty(
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
PropertyData inferredData, PropertyData inferredData,
@ -1073,7 +1060,7 @@ public class PropertyBinder {
inheritanceStatePerClass, inheritanceStatePerClass,
null, null,
null, null,
EmbeddableBinder.determineCustomInstantiator( property, returnedClass, context ), determineCustomInstantiator( property, returnedClass, context ),
compositeUserType, compositeUserType,
null, null,
columns columns
@ -1117,7 +1104,7 @@ public class PropertyBinder {
inheritanceStatePerClass, inheritanceStatePerClass,
null, null,
null, null,
EmbeddableBinder.determineCustomInstantiator( property, property.getElementType().determineRawClass(), context ), determineCustomInstantiator( property, property.getElementType().determineRawClass(), context ),
compositeUserType, compositeUserType,
null, null,
columns columns
@ -1304,91 +1291,23 @@ public class PropertyBinder {
return annotationUsage != null && annotationUsage.fetch() == LAZY; return annotationUsage != null && annotationUsage.fetch() == LAZY;
} }
private static void addNaturalIds(
boolean inSecondPass,
MemberDetails property,
AnnotatedColumns columns,
AnnotatedJoinColumns joinColumns,
MetadataBuildingContext context) {
// Natural ID columns must reside in one single UniqueKey within the Table.
// For now, simply ensure consistent naming.
// TODO: AFAIK, there really isn't a reason for these UKs to be created
// on the SecondPass. This whole area should go away...
final NaturalId naturalId = property.getDirectAnnotationUsage( NaturalId.class );
if ( naturalId != null ) {
final Database database = context.getMetadataCollector().getDatabase();
final ImplicitNamingStrategy implicitNamingStrategy = context.getBuildingOptions().getImplicitNamingStrategy();
if ( joinColumns != null ) {
final Identifier name = implicitNamingStrategy.determineUniqueKeyName( new ImplicitUniqueKeyNameSource() {
@Override
public Identifier getTableName() {
return joinColumns.getTable().getNameIdentifier();
}
@Override
public List<Identifier> getColumnNames() {
return singletonList(toIdentifier("_NaturalID"));
}
@Override
public Identifier getUserProvidedIdentifier() {
return null;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return context;
}
});
final String keyName = name.render( database.getDialect() );
for ( AnnotatedColumn column : joinColumns.getColumns() ) {
column.addUniqueKey( keyName, inSecondPass );
}
}
else {
final Identifier name = implicitNamingStrategy.determineUniqueKeyName(new ImplicitUniqueKeyNameSource() {
@Override
public Identifier getTableName() {
return columns.getTable().getNameIdentifier();
}
@Override
public List<Identifier> getColumnNames() {
return singletonList(toIdentifier("_NaturalID"));
}
@Override
public Identifier getUserProvidedIdentifier() {
return null;
}
@Override
public MetadataBuildingContext getBuildingContext() {
return context;
}
});
final String keyName = name.render( database.getDialect() );
for ( AnnotatedColumn column : columns.getColumns() ) {
column.addUniqueKey( keyName, inSecondPass );
}
}
}
}
private static Class<? extends CompositeUserType<?>> resolveCompositeUserType( private static Class<? extends CompositeUserType<?>> resolveCompositeUserType(
PropertyData inferredData, PropertyData inferredData,
MetadataBuildingContext context) { MetadataBuildingContext context) {
final SourceModelBuildingContext sourceModelContext = context.getMetadataCollector().getSourceModelBuildingContext(); final SourceModelBuildingContext sourceModelContext =
context.getMetadataCollector().getSourceModelBuildingContext();
final MemberDetails attributeMember = inferredData.getAttributeMember(); final MemberDetails attributeMember = inferredData.getAttributeMember();
final TypeDetails classOrElementType = inferredData.getClassOrElementType(); final TypeDetails classOrElementType = inferredData.getClassOrElementType();
final ClassDetails returnedClass = classOrElementType.determineRawClass(); final ClassDetails returnedClass = classOrElementType.determineRawClass();
if ( attributeMember != null ) { if ( attributeMember != null ) {
final CompositeType compositeType = attributeMember.locateAnnotationUsage( CompositeType.class, sourceModelContext ); final CompositeType compositeType =
attributeMember.locateAnnotationUsage( CompositeType.class, sourceModelContext );
if ( compositeType != null ) { if ( compositeType != null ) {
return compositeType.value(); return compositeType.value();
} }
final Class<? extends CompositeUserType<?>> compositeUserType = resolveTimeZoneStorageCompositeUserType( attributeMember, returnedClass, context ); final Class<? extends CompositeUserType<?>> compositeUserType =
resolveTimeZoneStorageCompositeUserType( attributeMember, returnedClass, context );
if ( compositeUserType != null ) { if ( compositeUserType != null ) {
return compositeUserType; return compositeUserType;
} }

View File

@ -1,94 +0,0 @@
/*
* 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.boot.model.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
/**
* @author Emmanuel Bernard
*/
public class UniqueKeySecondPass implements SecondPass {
private final String indexName;
private final MetadataBuildingContext buildingContext;
private final AnnotatedColumn column;
/**
* Build an index if unique is false or a Unique Key if unique is true
*/
public UniqueKeySecondPass(String indexName, AnnotatedColumn column, MetadataBuildingContext buildingContext) {
this.indexName = indexName;
this.column = column;
this.buildingContext = buildingContext;
}
@Override
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
if ( column != null ) {
final AnnotatedColumns annotatedColumns = column.getParent();
final Table table = annotatedColumns.getTable();
final PropertyHolder propertyHolder = annotatedColumns.getPropertyHolder();
final String entityName =
propertyHolder.isComponent()
? propertyHolder.getPersistentClass().getEntityName()
: propertyHolder.getEntityName();
final String propertyName = annotatedColumns.getPropertyName();
final Property property = persistentClasses.get( entityName ).getProperty( propertyName );
addConstraintToProperty( property, table );
}
}
private void addConstraintToProperty(Property property, Table table) {
if ( property.getValue() instanceof Component ) {
final Component component = (Component) property.getValue();
final List<Column> columns = new ArrayList<>();
for ( Selectable selectable: component.getSelectables() ) {
if ( selectable instanceof Column ) {
columns.add( (Column) selectable );
}
}
addConstraintToColumns( columns, table );
}
else {
addConstraintToColumn( column.getMappingColumn(), table );
}
}
private void addConstraintToColumn(Column mappingColumn, Table table) {
final String columnName =
buildingContext.getMetadataCollector()
.getLogicalColumnName( table, mappingColumn.getQuotedName() );
final Column column = table.getColumn( buildingContext.getMetadataCollector(), columnName );
if ( column == null ) {
throw new AnnotationException(
"Table '" + table.getName() + "' has no column named '" + columnName
+ "' matching the column specified in '@Index'"
);
}
table.getOrCreateUniqueKey( indexName ).addColumn( column );
}
private void addConstraintToColumns(List<Column> columns, Table table) {
final UniqueKey uniqueKey = table.getOrCreateUniqueKey( indexName );
for ( Column column : columns ) {
uniqueKey.addColumn( column );
}
}
}

View File

@ -20,7 +20,6 @@ import org.hibernate.query.Query;
import org.hibernate.stat.Statistics; import org.hibernate.stat.Statistics;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@ -37,7 +36,6 @@ import static org.junit.jupiter.api.Assertions.assertNull;
* @author Emmanuel Bernard * @author Emmanuel Bernard
* @author Hardy Ferentschik * @author Hardy Ferentschik
*/ */
@SuppressWarnings("unchecked")
public class NaturalIdTest extends BaseCoreFunctionalTestCase { public class NaturalIdTest extends BaseCoreFunctionalTestCase {
@After @After
public void cleanupData() { public void cleanupData() {