There is annotation org.hibernate.annotations.UuidGenerator, which relates to generator: org.hibernate.id.uuid.UuidGenerator.class
I need, due to historic reasons of some app, to declare custom generator, which will allow the value only if it’s not manually set. Not ideal, sure, but should be very trivial to implement.
I didn’t find any reasonable way how to do that, and trying to copy paste hibernate code I’m facing some issues with calling inexisting constructor using reflection. Can someone suggest how to declare such generator?
I created new annotation to create mapping to new generator
@IdGeneratorType(UseExistingOrGenerateNewUuidGenerator.class)
@ValueGenerationType(generatedBy = UseExistingOrGenerateNewUuidGenerator.class)
@Retention(RUNTIME)
@Target({ FIELD, METHOD })
public @interface UuidGenerator {
/**
* Represents a kind of UUID, that is, what RFC 4122 calls a "version".
*/
enum Style {
/**
* Defaults to {@link #RANDOM}.
*/
AUTO,
/**
* Use {@link UUID#randomUUID()} to generate UUIDs, producing a value
* compatible with RFC 4122 version 4.
*/
RANDOM,
/**
* Use a time-based generation strategy consistent with RFC 4122
* version 1, but with IP address instead of MAC address.
*
* @implNote Can be a bottleneck, since synchronization is used when
* incrementing an internal counter as part of the algorithm.
*/
TIME
}
/**
* Specifies which {@linkplain Style style} of UUID generation should be used.
*/
Style style() default Style.AUTO;
}
and trivially extended the existing generator:
public class UseExistingOrGenerateNewUuidGenerator extends UuidGenerator {
public UseExistingOrGenerateNewUuidGenerator(org.hibernate.annotations.UuidGenerator config,
Member idMember,
CustomIdGeneratorCreationContext creationContext) {
super(config, idMember, creationContext);
}
public UseExistingOrGenerateNewUuidGenerator(org.hibernate.annotations.UuidGenerator config,
Member member,
GeneratorCreationContext creationContext) {
super(config, member, creationContext);
}
@Override
public Object generate(SharedSessionContractImplementor session,
Object owner,
Object currentValue,
EventType eventType) {
Object pk = session.getEntityPersister(null, owner).getIdentifier(owner, session);
return pk != null
? pk
: valueTransformer.transform( generator.generateUuid( session ) );
}
}
but for some reason the caller code tries to find inexisting no-arg constructor…
Caused by: java.lang.NoSuchMethodException: net.homecredit.ofs.hibernate.UseExistingOrGenerateNewUuidGenerator.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3761)
at java.base/java.lang.Class.newInstance(Class.java:706)
... 31 common frames omitted
UPDATE: there is something very fishy how this class is instantiated. As shown I have everything the same, but instead of provided constructors no-arg one is called in quite mysterious way. So I cannot initialize value Transfromer based on idMember. So as a workaround, I can make 3 specialized interfaces and uuid generators. This approach accepts that no-arg is called and it can get work done.
public class UseExistingOrGenerateNewUuidGenerator implements BeforeExecutionGenerator {
private final StandardRandomStrategy generator = StandardRandomStrategy.INSTANCE;
private final ValueTransformer valueTransformer;
public UseExistingOrGenerateNewUuidGenerator() {
valueTransformer = UUIDJavaType.PassThroughTransformer.INSTANCE;
}
@Override
public EnumSet<EventType> getEventTypes() {
return INSERT_ONLY;
}
@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
Object pk = session.getEntityPersister(null, owner).getIdentifier(owner, session);
return pk != null
? pk
: valueTransformer.transform( generator.generateUuid( session ) );
}
}