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-losequence 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
@Tsidor the following@TsidGeneratorclass since they are already provided by the Hypersistence Utils project.However, in order to understand how the
@TsidGeneratormechanism 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
@IdGeneratorTypeand the@ValueGenerationTypeto allow the@Tsidannotation 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:
- The
@SequenceOptimizermeta annotation - The
SequenceOptimizerGeneratorprovides the configuration to the HibernateSequenceStyleGeneratorthat is used by the sequence-based entity identifiers.
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.






