natural-id + not-found
https://hibernate.atlassian.net/browse/HHH-17197 - Add check for illegal combo of to-one + natural-id + not-found
https://hibernate.atlassian.net/browse/HHH-17196 - Documentation for @NaturalId should be more explicit about non-nullability
(cherry picked from commit 6c2e04381d
)
This commit is contained in:
parent
e14c7f8740
commit
8f4afa625f
|
@ -5,7 +5,15 @@
|
|||
|
||||
Natural ids represent domain model unique identifiers that have a meaning in the real world too.
|
||||
Even if a natural id does not make a good primary key (surrogate keys being usually preferred), it's still useful to tell Hibernate about it.
|
||||
As we will see later, Hibernate provides a dedicated, efficient API for loading an entity by its natural id much like it offers for loading by its identifier (PK).
|
||||
As we will see later, Hibernate provides a dedicated, efficient API for loading an entity by its natural id much like it offers for loading by identifier (PK).
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
All values used in a natural id must be non-nullable.
|
||||
|
||||
For natural id mappings using a to-one association, this precludes the use of not-found
|
||||
mappings which effectively define a nullable mapping.
|
||||
====
|
||||
|
||||
[[naturalid-mapping]]
|
||||
==== Natural Id Mapping
|
||||
|
|
|
@ -19,6 +19,8 @@ import java.util.Set;
|
|||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
|
||||
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
|
||||
import org.hibernate.cfg.NotYetImplementedException;
|
||||
|
@ -30,8 +32,15 @@ import org.hibernate.internal.CoreMessageLogger;
|
|||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||
import org.hibernate.mapping.Component;
|
||||
import org.hibernate.mapping.GeneratorCreator;
|
||||
import org.hibernate.mapping.ManyToOne;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.Subclass;
|
||||
import org.hibernate.mapping.ToOne;
|
||||
import org.hibernate.mapping.Value;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.tuple.GenerationTiming;
|
||||
import org.hibernate.tuple.IdentifierProperty;
|
||||
|
@ -231,6 +240,7 @@ public class EntityMetamodel implements Serializable {
|
|||
}
|
||||
|
||||
if ( prop.isNaturalIdentifier() ) {
|
||||
verifyNaturalIdProperty( property );
|
||||
naturalIdNumbers.add( i );
|
||||
if ( prop.isUpdateable() ) {
|
||||
foundUpdateableNaturalIdProperty = true;
|
||||
|
@ -658,6 +668,29 @@ public class EntityMetamodel implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
private void verifyNaturalIdProperty(Property property) {
|
||||
final Value value = property.getValue();
|
||||
if ( value instanceof ManyToOne ) {
|
||||
final ManyToOne toOne = (ManyToOne) value;
|
||||
if ( toOne.getNotFoundAction() == NotFoundAction.IGNORE ) {
|
||||
throw new MappingException(
|
||||
"Attribute marked as natural-id can not also be a not-found association - "
|
||||
+ propertyName( property )
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ( value instanceof Component ) {
|
||||
final Component component = (Component) value;
|
||||
for ( Property componentProperty : component.getProperties() ) {
|
||||
verifyNaturalIdProperty( componentProperty );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String propertyName(Property property) {
|
||||
return getName() + "." + property.getName();
|
||||
}
|
||||
|
||||
private static class NoInMemoryValueGenerationStrategy implements InMemoryValueGenerationStrategy {
|
||||
/**
|
||||
* Singleton access
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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.orm.test.mapping.naturalid;
|
||||
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
import org.hibernate.boot.MetadataSources;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@ServiceRegistry
|
||||
public class ValidationTests {
|
||||
@Test
|
||||
void checkManyToOne(ServiceRegistryScope registryScope) {
|
||||
final StandardServiceRegistry registry = registryScope.getRegistry();
|
||||
final MetadataSources metadataSources = new MetadataSources( registry )
|
||||
.addAnnotatedClass( Thing1.class )
|
||||
.addAnnotatedClass( Thing2.class );
|
||||
try (final SessionFactory sessionFactory = metadataSources.buildMetadata().buildSessionFactory(); ) {
|
||||
fail( "Expecting an exception" );
|
||||
}
|
||||
catch (MappingException expected) {
|
||||
assertThat( expected.getMessage() )
|
||||
.startsWith( "Attribute marked as natural-id can not also be a not-found association - " );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkEmbeddable(ServiceRegistryScope registryScope) {
|
||||
final StandardServiceRegistry registry = registryScope.getRegistry();
|
||||
final MetadataSources metadataSources = new MetadataSources( registry )
|
||||
.addAnnotatedClass( Thing1.class )
|
||||
.addAnnotatedClass( Thing3.class )
|
||||
.addAnnotatedClass( Container.class );
|
||||
try (final SessionFactory sessionFactory = metadataSources.buildMetadata().buildSessionFactory(); ) {
|
||||
fail( "Expecting an exception" );
|
||||
}
|
||||
catch (MappingException expected) {
|
||||
assertThat( expected.getMessage() )
|
||||
.startsWith( "Attribute marked as natural-id can not also be a not-found association - " );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name="Thing1")
|
||||
@Table(name="thing_1")
|
||||
public static class Thing1 {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Entity(name="Thing2")
|
||||
@Table(name="thing_2")
|
||||
public static class Thing2 {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@NaturalId
|
||||
@ManyToOne
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private Thing1 thing1;
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
public static class Container {
|
||||
@NaturalId
|
||||
@ManyToOne
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private Thing1 thing1;
|
||||
}
|
||||
|
||||
@Entity(name="Thing2")
|
||||
@Table(name="thing_2")
|
||||
public static class Thing3 {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
@NaturalId
|
||||
@Embedded
|
||||
private Container container;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue