The best way to replace the deprecated GenericGenerator

Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?

What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?

Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.

So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!

Introduction

In this article, I’m going to show you the best way to replace the deprecated @GenericGenerator when upgrading your application to Hibernate 7.

Hibernate @GenericGenerator annotation

The @GenericGenerator annotation has been available since Hibernate 3.5, as an alternative to the <generator> XML element from HBM mappings.

As illustrated by the Hibernate 6.5 @GenericGenerator Javadoc, this annotation was deprecated in favor of the @IdGeneratorType meta-annotation, which we’ve been using before for the TSID identifier.

Traditionally, there were two main use cases where you’d use the @GenericGenerator annotation:

  • to provide a custom identifier generator
  • to supply a pooled-lo sequence optimizer to the @SequenceGenerator

So, to replace the @GenericGenerator annotation, we need a way to address both these two use cases using the @IdGeneratorType annotation.

How to map a custom entity identifier generator

Let’s say that I want to use the TSID (time-sorted id) generator to assign Long entity identifiers in the application.

Generating a Long identifier using the TSID generator can be done as follows:

TSID.Factory tsidFactory = TSID.Factory
    .builder()
    .withRandomFunction(TSID.Factory.THREAD_LOCAL_RANDOM_FUNCTION)
    .build();

long id = tsidFactory.generate().toLong();

While I could encapsulate this logic in a utility method and assign the id directly on the entity, the @IdGeneratorType allows us to define a custom annotation (e.g., @Tsid) that we can use on the id attribute and let Hibernate generate the identifier values during persist:

@Entity
@Table(name = "post")
public class Post {
⠀
    @Id
    @Tsid
    private Long id;
}

Note that you don’t need to create the @Tsid or the following @TsidGenerator class since they are already provided by the Hypersistence Utils project.

However, in order to understand how the @TsidGenerator mechanism works, let’s see how you can leverage this new meta annotation to simplify your entity identifier mappings.

The @Tsid annotation can be created like this:

@IdGeneratorType(TsidGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD, METHOD})
public @interface Tsid {

    Class<? extends Supplier<TSID.Factory>> value()
        default FactorySupplier.class;

    class FactorySupplier implements Supplier<TSID.Factory> {

        public static final FactorySupplier INSTANCE = new FactorySupplier();

        private TSID.Factory tsidFactory = TSID.Factory
            .builder()
            .withRandomFunction(TSID.Factory.THREAD_LOCAL_RANDOM_FUNCTION)
            .build();

        @Override
        public TSID.Factory get() {
            return tsidFactory;
        }
    }
}

Via the @IdGeneratorType meta annotation, we have instructed Hibernate to use the TsidGenerator class to generate the entity identifier values for the attributes annotated with the @Tsid annotation.

The TsidGenerator implements the IdentifierGenerator interface as follows:

public class TsidGenerator implements IdentifierGenerator {

    private final TSID.Factory tsidFactory;

    private AttributeType idType;

    public TsidGenerator(
            Tsid config,
            Member idMember,
            GeneratorCreationContext creationContext) {
        //Get the entity attribute Type to know what to cast the TSID value to
        idType = AttributeType.valueOf(ReflectionUtils.getMemberType(idMember));

        //Get the provided TSID.Factory or, otherwise, the default one 
        Class<? extends Supplier<TSID.Factory>> tsidSupplierClass = config.value();

        if(tsidSupplierClass.equals(Tsid.FactorySupplier.class)) {
            //Use the default TSID.Factory
            tsidFactory = Tsid.FactorySupplier.INSTANCE.get();
        } else {
            Supplier<TSID.Factory> factorySupplier = ReflectionUtils.newInstance(
                tsidSupplierClass
            );
            //Use the custom TSID.Factory
            tsidFactory = factorySupplier.get();
        }
    }

    @Override
    public Object generate(SharedSessionContractImplementor session, Object object) 
            throws HibernateException {
        //Generate the TSID and cast it to the associated entity attribute Type
        return idType.cast(tsidFactory.generate());
    }

    enum AttributeType {
        LONG {
            @Override
            public Object cast(TSID tsid) {
                return tsid.toLong();
            }
        },
        STRING {
            @Override
            public Object cast(TSID tsid) {
                return tsid.toString();
            }
        },
        TSID {
            @Override
            public Object cast(TSID tsid) {
                return tsid;
            }
        };

        public abstract Object cast(TSID tsid);

        static AttributeType valueOf(Class clazz) {
            if(Long.class.isAssignableFrom(clazz)) {
                return LONG;
            } else if (String.class.isAssignableFrom(clazz)) {
                return STRING;
            } else if (TSID.class.isAssignableFrom(clazz)) {
                return TSID;
            } else {
                throw new HibernateException(
                    String.format(
                        "The @Tsid annotation on [%s] can only be placed " +
                        "on a Long, String, or TSID entity attribute!",
                        clazz
                    )
                );
            }
        }
    }
}

That’s it!

Previously, you’d had to create the TsidGenerator class and provide the fully-qualified class name to the @GenericGenerator annotation. However, with the @IdGeneratorType, you can use custom annotations and simplify your entity mappings.

There’s worth noting that this new approach allows us to reuse the Generator for both identifier and non-identifier values.

For example, as illustrated by this example from the Hypersistence Utils project, you can use both the @IdGeneratorType and the @ValueGenerationType to allow the @Tsid annotation to be used on both identifier and non-identifier entity attributes.

How to configure the `pooled-lo` optimizer without the @GenericGenerator

Another very common use case for the @GenericGenerator annotation was to configure the pooled-lo sequence optimizer:

@Entity
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "pooled-lo"
    )
    @GenericGenerator(
            name = "pooled-lo",
            strategy = "sequence",
            parameters = {
                @Parameter(name = "sequence_name", value = "post_sequence"),
                @Parameter(name = "initial_value", value = "1"),
                @Parameter(name = "increment_size", value = "3"),
                @Parameter(name = "optimizer", value = "pooled-lo")
            }
    )
    private Long id;
}

That’s a lot of code for customizing the pooled-lo sequence optimizer. Luckily, using the @IdGeneratorType, we can change the previous entity mapping to this:

@Entity
@Table(name = "post")
public class Post {

    @Id
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceOptimizer(
        sequenceName = "post_sequence",
        initialValue = 1,
        incrementSize = 3,
        optimizer = "pooled-lo"
    )
    private Long id;
}

The @SequenceOptimizer is available via the Hypersistence Utils project, so if you want to take a look, check out the following two classes:

That’s it!

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Having been available since 2010, there’s a very good chance that the @GenericGenerator has been widely used, meaning that you’d need to migrate your mappings to the new @IdGeneratorType approach.

While you can create your own @IdGeneratorType solutions, for many common use cases, you are going to find the solution already in the Hypersistence Utils, which supports multiple versions of Hibernate.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.