Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-4309 Test and fix overriding generators in subclasses works #9590

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
:core-project-dir: {root-project-dir}/hibernate-core
:core-test-base: {core-project-dir}/src/test/java/org/hibernate/orm/test
:example-dir-identifier: {core-test-base}/mapping/identifier
:example-dir-generated: {core-test-base}/mapping/generated
:example-dir-associations: {core-test-base}/associations
:jpaJavadocUrl: https://javadoc.io/doc/jakarta.persistence/jakarta.persistence-api/latest/jakarta.persistence
:extrasdir: extras
Expand Down Expand Up @@ -392,6 +393,24 @@ include::{example-dir-identifier}/SequenceGeneratorConfiguredTest.java[tag=ident
Again the mapping specifies `explicit_product_sequence` as the physical sequence name, but it also specifies an
explicit allocation-size ("increment by").

The scope of the generator name can be controlled with the <<settings-hibernate.jpa.compliance.global_id_generators,`hibernate.jpa.compliance.global_id_generators`>>
configuration setting. With <<settings-compliance,JPA compliance>> enabled, the name scope is global i.e.
there may not be two generator definitions with the same name. Historically, Hibernate ORM used a local scope i.e.
every managed type may have a generator with the same name, preferring the "local" definition over a more distant one.
This behavior allows to model e.g. a mapped superclass with a generator that should apply to subclasses by default,
yet allow an entity subclass to choose a custom generator by simply specifying the same generator name.

[[identifiers-generators-sequence-override]]
.Sequence override
====
[source,java]
----
include::{example-dir-generated}/override/SequenceGeneratorOverrideTest.java[tag=identifiers-generators-sequence-override-example, indent=0]
----
====

In this case, `base_sequence` will be used when a `Entity1` is persisted,
whereas for persists of a `Entity2`, Hibernate ORM will use `sub_sequence`.

[[identifiers-generators-identity]]
==== Using IDENTITY columns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,24 @@ public AbstractEntityIdGeneratorResolver(
@Override
public final void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
switch ( generatedValue.strategy() ) {
case UUID -> GeneratorAnnotationHelper.handleUuidStrategy( idValue, idMember, buildingContext );
case UUID -> handleUuidStrategy();
case IDENTITY -> GeneratorAnnotationHelper.handleIdentityStrategy( idValue );
case SEQUENCE -> handleSequenceStrategy();
case TABLE -> handleTableStrategy();
case AUTO -> handleAutoStrategy();
}
}

private void handleUuidStrategy() {
GeneratorAnnotationHelper.handleUuidStrategy(
idValue,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
buildingContext
);
}

private void handleSequenceStrategy() {
if ( generatedValue.generator().isBlank() ) {
handleUnnamedSequenceGenerator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.function.Consumer;
import java.util.function.Function;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.annotations.Parameter;
Expand Down Expand Up @@ -61,8 +62,9 @@ public class GeneratorAnnotationHelper {
public static <A extends Annotation> A findLocalizedMatch(
AnnotationDescriptor<A> generatorAnnotationType,
MemberDetails idMember,
Function<A,String> nameExtractor,
String matchName,
ClassDetails entityType,
@Nullable Function<A,String> nameExtractor,
@Nullable String matchName,
MetadataBuildingContext context) {
final SourceModelBuildingContext sourceModelContext =
context.getMetadataCollector().getSourceModelBuildingContext();
Expand All @@ -88,7 +90,26 @@ public static <A extends Annotation> A findLocalizedMatch(
}
}

// next, on the class
// next, on the entity class
for ( A generatorAnnotation :
entityType.getRepeatedAnnotationUsages( generatorAnnotationType, sourceModelContext ) ) {
if ( nameExtractor != null ) {
final String registrationName = nameExtractor.apply( generatorAnnotation );
if ( registrationName.isEmpty() ) {
if ( possibleMatch == null ) {
possibleMatch = generatorAnnotation;
}
}
else if ( registrationName.equals( matchName ) ) {
return generatorAnnotation;
}
}
else {
return generatorAnnotation;
}
}

// next, on the declaring class
for ( A generatorAnnotation:
idMember.getDeclaringType().getRepeatedAnnotationUsages( generatorAnnotationType, sourceModelContext ) ) {
if ( nameExtractor != null ) {
Expand Down Expand Up @@ -296,10 +317,12 @@ public static <A extends Annotation> void prepareForUse(
public static void handleUuidStrategy(
SimpleValue idValue,
MemberDetails idMember,
ClassDetails entityClass,
MetadataBuildingContext context) {
final org.hibernate.annotations.UuidGenerator generatorConfig = findLocalizedMatch(
HibernateAnnotations.UUID_GENERATOR,
idMember,
entityClass,
null,
null,
context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ public IdBagIdGeneratorResolverSecondPass(
public void doSecondPass(Map<String, PersistentClass> idGeneratorDefinitionMap) throws MappingException {
final GeneratedValue generatedValue = idBagMember.getDirectAnnotationUsage( GeneratedValue.class );
switch ( generatedValue.strategy() ) {
case UUID -> handleUuidStrategy( idValue, idBagMember, buildingContext );
case UUID -> handleUuidStrategy(
idValue,
idBagMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
buildingContext
);
case IDENTITY -> handleIdentityStrategy( idValue );
case SEQUENCE -> handleSequenceStrategy(
generatorName,
Expand Down Expand Up @@ -121,6 +127,8 @@ private void handleTableStrategy(
final TableGenerator localizedTableMatch = findLocalizedMatch(
JpaAnnotations.TABLE_GENERATOR,
idBagMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
TableGenerator::name,
generatorName,
buildingContext
Expand Down Expand Up @@ -164,6 +172,8 @@ private void handleSequenceStrategy(
final SequenceGenerator localizedSequencedMatch = findLocalizedMatch(
JpaAnnotations.SEQUENCE_GENERATOR,
idBagMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
SequenceGenerator::name,
generatorName,
buildingContext
Expand Down Expand Up @@ -235,6 +245,8 @@ private void handleAutoStrategy(
final SequenceGenerator localizedSequencedMatch = findLocalizedMatch(
JpaAnnotations.SEQUENCE_GENERATOR,
idBagMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
SequenceGenerator::name,
generatorName,
buildingContext
Expand All @@ -247,6 +259,8 @@ private void handleAutoStrategy(
final TableGenerator localizedTableMatch = findLocalizedMatch(
JpaAnnotations.TABLE_GENERATOR,
idBagMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
TableGenerator::name,
generatorName,
buildingContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ protected void handleUnnamedSequenceGenerator() {
final SequenceGenerator localizedMatch = findLocalizedMatch(
JpaAnnotations.SEQUENCE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
null,
null,
buildingContext
Expand All @@ -77,6 +79,8 @@ protected void handleNamedSequenceGenerator() {
final SequenceGenerator localizedMatch = findLocalizedMatch(
JpaAnnotations.SEQUENCE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
SequenceGenerator::name,
generator,
buildingContext
Expand Down Expand Up @@ -147,6 +151,8 @@ protected void handleUnnamedTableGenerator() {
final TableGenerator localizedMatch = findLocalizedMatch(
JpaAnnotations.TABLE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
null,
null,
buildingContext
Expand All @@ -161,6 +167,8 @@ protected void handleNamedTableGenerator() {
final TableGenerator localizedTableMatch = findLocalizedMatch(
JpaAnnotations.TABLE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
TableGenerator::name,
generator,
buildingContext
Expand Down Expand Up @@ -229,6 +237,8 @@ protected void handleUnnamedAutoGenerator() {
final SequenceGenerator localizedSequenceMatch = findLocalizedMatch(
JpaAnnotations.SEQUENCE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
null,
null,
buildingContext
Expand All @@ -241,6 +251,8 @@ protected void handleUnnamedAutoGenerator() {
final TableGenerator localizedTableMatch = findLocalizedMatch(
JpaAnnotations.TABLE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
null,
null,
buildingContext
Expand All @@ -253,6 +265,8 @@ protected void handleUnnamedAutoGenerator() {
final GenericGenerator localizedGenericMatch = findLocalizedMatch(
HibernateAnnotations.GENERIC_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
null,
null,
buildingContext
Expand All @@ -274,7 +288,13 @@ protected void handleUnnamedAutoGenerator() {

if ( idMember.getType().isImplementor( UUID.class )
|| idMember.getType().isImplementor( String.class ) ) {
GeneratorAnnotationHelper.handleUuidStrategy( idValue, idMember, buildingContext );
GeneratorAnnotationHelper.handleUuidStrategy(
idValue,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
buildingContext
);
return;
}

Expand Down Expand Up @@ -313,7 +333,13 @@ protected void handleNamedAutoGenerator() {

if ( idMember.getType().isImplementor( UUID.class )
|| idMember.getType().isImplementor( String.class ) ) {
GeneratorAnnotationHelper.handleUuidStrategy( idValue, idMember, buildingContext );
GeneratorAnnotationHelper.handleUuidStrategy(
idValue,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
buildingContext
);
return;
}

Expand All @@ -331,6 +357,8 @@ private boolean handleAsLocalAutoGenerator() {
final SequenceGenerator localizedSequenceMatch = findLocalizedMatch(
JpaAnnotations.SEQUENCE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
SequenceGenerator::name,
generator,
buildingContext
Expand All @@ -343,6 +371,8 @@ private boolean handleAsLocalAutoGenerator() {
final TableGenerator localizedTableMatch = findLocalizedMatch(
JpaAnnotations.TABLE_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
TableGenerator::name,
generator,
buildingContext
Expand All @@ -355,6 +385,8 @@ private boolean handleAsLocalAutoGenerator() {
final GenericGenerator localizedGenericMatch = findLocalizedMatch(
HibernateAnnotations.GENERIC_GENERATOR,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
GenericGenerator::name,
generator,
buildingContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,13 @@ private void handleAutoGenerator(String globalRegistrationName) {
// Implicit handling of UUID generation
if ( idMember.getType().isImplementor( UUID.class )
|| idMember.getType().isImplementor( String.class ) ) {
handleUuidStrategy( idValue, idMember, buildingContext );
handleUuidStrategy(
idValue,
idMember,
buildingContext.getMetadataCollector().getClassDetailsRegistry()
.getClassDetails( entityMapping.getClassName() ),
buildingContext
);
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.mapping.generated.override;

import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.generator.Generator;
import org.hibernate.id.UUIDGenerator;
import org.hibernate.id.UUIDHexGenerator;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;


@DomainModel(
annotatedClasses = {
GenericGeneratorOverrideTest.Entity1.class,
GenericGeneratorOverrideTest.Entity2.class,
}
)
@SessionFactory
@Jira("https://hibernate.atlassian.net/browse/HHH-4309")
public class GenericGeneratorOverrideTest {

@Test
public void test(SessionFactoryScope scope) {
SessionFactoryImplementor sf = scope.getSessionFactory();
EntityPersister p1 = sf.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( Entity1.class.getName() );
EntityPersister p2 = sf.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( Entity2.class.getName() );

Generator generator1 = p1.getGenerator();
Generator generator2 = p2.getGenerator();
assertThat( generator1 ).isInstanceOf( UUIDGenerator.class );
assertThat( generator2 ).isInstanceOf( UUIDHexGenerator.class );
}

@MappedSuperclass
@GenericGenerator(name = "my-generator", strategy = "uuid2")
public static abstract class BaseEntity {
@Id
@GeneratedValue(generator = "my-generator")
private String id;

public String getId() {
return id;
}

public void setId(final String id) {
this.id = id;
}
}

@jakarta.persistence.Entity(name = "Entity1")
public static class Entity1 extends BaseEntity {
public Entity1() {
}
}

@jakarta.persistence.Entity(name = "Entity2")
@GenericGenerator(name = "my-generator", strategy = "uuid.hex")
public static class Entity2 extends BaseEntity {
public Entity2() {
}
}

}
Loading
Loading