Replacement of @GenericGenerator "native"

Hi,
I have upgraded to hibernate 6.6 from 6.2 and found, that @GenericGenerator is deprecated. I am using native strategy, so some databases are using sequences and others identity. I need this for backward compatibility.

    @Id
    @GeneratedValue(generator = "native")
    @GenericGenerator(name = "native", strategy = "native")
    private Long id;

GenerationType.IDENTITY is always generating identity and disabling identity in dialect throws, GenerationType.AUTO is selecting sequences for SQL Server. Is there option to alter behaviour of AUTO strategy in dialect or some supported way to use native generator?

1 Like

The supported way to specify a custom generator is using @IdGeneratorType meta-annotation, see the user guide for more details and examples on how to rework your mappings.

Only way is to create custom generator? I was thinking about that, but this is complicated - delegate either to identity or sequence generator - how should I know current dialect?

Dialect has nothing to do with this. If you wish to use identity or sequence generators, just annotated either @GeneratedValue( strategy = IDENTITY ) or @GeneratedValue( strategy = SEQUENCE ).

I need different generator for SQL Server (identity) and for Oracle/PostgreSQL (sequence) - so I need to differentiate this with dialect.

1 Like

I’m afraid that’s not possible with native Hibernate functionality, you’re just going to need to create a custom generator that implements your specific business logic.

Hi I also have the same problem with @GenericGenerator(name = “native_generator”, strategy = “native”), I need to support PostgesSql and Ms SQL together but as hibernate 6.5 deprecated GenericGenerator annotation, which annotation could be replacement of GenericGenerator with strategy = “native”? PostgesSql need ID with sequence and Ms SQL needs Auto increment ID.
@jezbera Have you found solution for it?

Unfortunately custom GenerationTypeStrategyRegistration was not working.

import jakarta.persistence.GenerationType;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.id.factory.internal.IdentityGenerationTypeStrategy;
import org.hibernate.id.factory.internal.SequenceGenerationTypeStrategy;
import org.hibernate.id.factory.spi.GenerationTypeStrategy;
import org.hibernate.id.factory.spi.GenerationTypeStrategyRegistration;
import org.hibernate.service.ServiceRegistry;

import java.util.function.BiConsumer;

public class CustomIdentifierGeneratorStrategyRegistration implements GenerationTypeStrategyRegistration {
    @Override
    public void registerStrategies(BiConsumer<GenerationType, GenerationTypeStrategy> registry, ServiceRegistry serviceRegistry) {
        Dialect dialect = serviceRegistry.requireService(JdbcEnvironment.class).getDialect();
        registry.accept(GenerationType.AUTO, dialect.getNativeIdentifierGeneratorStrategy().equals("identity") ? IdentityGenerationTypeStrategy.INSTANCE :  SequenceGenerationTypeStrategy.INSTANCE);
    }
}

I have used dirty way - custom IdentifierGeneratorFactory, that replaces sequences with identity based on dialect (it will break with future hibernate):

import jakarta.persistence.GenerationType;
import org.hibernate.MappingException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.generator.Generator;
import org.hibernate.id.Assigned;
import org.hibernate.id.Configurable;
import org.hibernate.id.ForeignGenerator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.id.factory.spi.GeneratorDefinitionResolver;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.JavaType;

import java.util.Optional;
import java.util.Properties;

public class CustomIdentifierGeneratorFactory implements IdentifierGeneratorFactory {
    private final ServiceRegistry serviceRegistry;

    private Dialect dialect;

    public CustomIdentifierGeneratorFactory(ServiceRegistryImplementor serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    @Override
    public Generator createIdentifierGenerator(GenerationType generationType, String generatedValueGeneratorName, String generatorName, JavaType<?> javaType, Properties config, GeneratorDefinitionResolver definitionResolver) {
        throw new UnsupportedOperationException("Not implemented yet");
    }

    @Override
    @Deprecated(since = "6.0")
    public Generator createIdentifierGenerator(String strategy, Type type, Properties parameters) {
        try {
            final Class<? extends Generator> clazz = generatorClassForName(strategy);
            final Generator identifierGenerator = clazz.newInstance();
            if (identifierGenerator instanceof Configurable configurable) {
                configurable.configure(type, parameters, serviceRegistry);
            }
            return identifierGenerator;
        } catch (Exception e) {
            final String entityName = parameters.getProperty(IdentifierGenerator.ENTITY_NAME);
            throw new MappingException(String.format("Could not instantiate id generator [entity-name=%s]", entityName), e);
        }
    }

    private Dialect getDialect() {
        if (dialect == null) {
            dialect = serviceRegistry.requireService(JdbcEnvironment.class).getDialect();
        }
        return dialect;
    }

    private Class<? extends Generator> generatorClassForName(String strategy) {
        if (strategy.equals("org.hibernate.id.enhanced.SequenceStyleGenerator") && getDialect().getNativeIdentifierGeneratorStrategy().equals("identity")) {
            return IdentityGenerator.class;
        }
        try {
            return getLegacyGenerator(strategy).orElseGet(() -> serviceRegistry.requireService(ClassLoaderService.class).classForName(strategy));
        } catch (ClassLoadingException e) {
            throw new MappingException(String.format("Could not interpret id generator strategy [%s]", strategy));
        }
    }

    private static Optional<Class<? extends Generator>> getLegacyGenerator(String name) {
        return switch (name) {
            case "assigned" -> Optional.of(Assigned.class);
            case "foreign" -> Optional.of(ForeignGenerator.class);
            default -> Optional.empty();
        };
    }
}

Here a possible solution with @IdGeneratorType (case when ID is of type Long). Use identity for mssql and sequence for the others. Change configure() if you want another behaviour

public class SequenceOrIdentityIdentifierGenerator extends IdentityGenerator implements IdentifierGenerator, OnExecutionGenerator {
	private static final long serialVersionUID = -6394994123485461850L;

	private String sequenceName;

	private String sequenceCallSyntax;
	private boolean useIdentity = false;
	
	public SequenceOrIdentityIdentifierGenerator(SequenceOrIdentity config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
		this.sequenceName = config.name();
	}

	@Override
	public void configure( Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {

		final JdbcEnvironment jdbcEnvironment = serviceRegistry .getService(JdbcEnvironment.class);

		final Dialect dialect = jdbcEnvironment.getDialect();
		
		if(dialect instanceof AbstractTransactSQLDialect) { // An abstract base class for Sybase and MS SQL Server dialects.
			useIdentity = true;
		} else {
			useIdentity = false;
			sequenceCallSyntax = dialect.getSequenceSupport().getSequenceNextValString(sequenceName);
		}
		
	}

	@Override
	public Serializable generate( SharedSessionContractImplementor session, Object obj) {
		return Session.class.cast(session)
				.createNativeQuery(sequenceCallSyntax, Long.class)
				.uniqueResult()
				.longValue();
	}

	@Override
	public boolean generatedOnExecution() { // true if the value is generated by the database, or false if it is generated in Java
		return useIdentity ? true : false; 
	}

	@Override
	public boolean referenceColumnsInSql(Dialect dialect) {
		return useIdentity ? super.referenceColumnsInSql(dialect) : false;  // maybe  useful only for OnExecutionGenerator,,,
	}

	@Override
	public boolean writePropertyValue() {
		return useIdentity ? false : true; // maybe  not useful for sequences
	}

	@Override
	public EnumSet<EventType> getEventTypes() {
		return EventTypeSets.INSERT_ONLY;
	}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hibernate.annotations.IdGeneratorType;

@IdGeneratorType( SequenceOrIdentityIdentifierGenerator.class )
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SequenceOrIdentity {
	 String name();
}

Now you can use it in this way:

	@Id
	@SequenceOrIdentity(name = "THE_SEQUENCE_NAME")
	@Column (name="ID")
	private Long id;

(let me know if it works properly…)