0% found this document useful (0 votes)
410 views8 pages

Custom Sequence-Based ID Generators

The document discusses implementing a custom id generator in Hibernate that uses a sequence-based value with an additional prefix. It provides examples of prefixes including a fixed string, date values, and a parent entity attribute. The key points are: 1. Extend Hibernate's SequenceStyleGenerator to leverage existing functionality and optimizations. 2. Override configure to read parameters and generate to add the prefix. 3. Parameters allow customizing formats, separators, and resetting sequences. 4. The generator can be used via JPA annotations similarly to built-in generators.

Uploaded by

Adolf
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
410 views8 pages

Custom Sequence-Based ID Generators

The document discusses implementing a custom id generator in Hibernate that uses a sequence-based value with an additional prefix. It provides examples of prefixes including a fixed string, date values, and a parent entity attribute. The key points are: 1. Extend Hibernate's SequenceStyleGenerator to leverage existing functionality and optimizations. 2. Override configure to read parameters and generate to add the prefix. 3. Parameters allow customizing formats, separators, and resetting sequences. 4. The generator can be used via JPA annotations similarly to built-in generators.

Uploaded by

Adolf
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Implement a Custom, Sequence-Based IdGenerator

A lot of applications use primary keys that are based on a sequence


but use an additional prefix that contains semantic information. Here
are some typical examples:
1. A fixed String as a prefix followed by a sequence-based value of
fixed or variable length, e.g., B_00001 or B_1
2. Year and month as a prefix followed by a sequence-based value
of fixed or variable length, e.g., 2018-08_00001 or 2018-08_1
3. An attribute value of a parent entity as a prefix followed by a
sequence-based value of fixed or variable length, e.g.,
MP_00001 or MP_1
You can easily support all 3 of these examples by implementing a
custom generator. If your database supports sequences and at least a
part of your ID consists of an automatically incremented value, the
best way to do that is to extend Hibernate’s SequenceStyleGenerator
class. That enables you to use your generator in the same way as any
other id generator. You can also benefit from all Hibernate-specific
optimizations, like the high-low algorithm which reduces the number
of times Hibernate requests a new value from the database sequence.

A fixed String followed by a sequence-based value


Hibernate’s SequenceStyleGenerator already does most of the heavy
lifting, like the handling of different database dialects or the
implementation of various performance optimizations. If you extend
that class, you only need to add your prefix and format the sequence
value in your preferred way.
public class StringPrefixedSequenceIdGenerator
extends SequenceStyleGenerator {

public static final String VALUE_PREFIX_PARAMETER = "valuePrefix";


public static final String VALUE_PREFIX_DEFAULT = "";
private String valuePrefix;

public static final String NUMBER_FORMAT_PARAMETER = "numberFormat";


public static final String NUMBER_FORMAT_DEFAULT = "%d";
private String numberFormat;

www.thoughts-on-java.org
Implement a Custom, Sequence-Based IdGenerator

public static final String NUMBER_FORMAT_PARAMETER = "numberFormat";


public static final String NUMBER_FORMAT_DEFAULT = "%d";
private String numberFormat;

@Override
public Serializable generate(SharedSessionContractImplementor session,
Object object) throws HibernateException {
return valuePrefix
+ String.format(numberFormat, super.generate(session, object));
}

@Override
public void configure(Type type, Properties params,
ServiceRegistry serviceRegistry) throws MappingException {
super.configure(LongType.INSTANCE, params, serviceRegistry);
valuePrefix = ConfigurationHelper.getString(
VALUE_PREFIX_PARAMETER, params, VALUE_PREFIX_DEFAULT);
numberFormat = ConfigurationHelper.getString(
NUMBER_FORMAT_PARAMETER, params,
NUMBER_FORMAT_DEFAULT);
}
}

Please note that the configure method changes the value of the Type
type parameter, when it calls the configure method of the superclass.
This is necessary because the sequence value will be part of a String,
but Hibernate can’t handle String-based sequences. So, you need to
tell Hibernate to generate a sequence value of type Long and convert
it afterward.

www.thoughts-on-java.org
Implement a Custom, Sequence-Based IdGenerator

Use the StringPrefixedSequenceIdGenerator


That’s all you need to do to implement your custom, sequence-based
generator. You can now add a @GeneratedValue and a
@GenericGenerator annotation to your Book entity to use the
generator.

@Entity
public class Book {

@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "book_seq")
@GenericGenerator(
name = "book_seq",
strategy =
"org.thoughts.on.java.generators.StringPrefixedSequenceIdGenerator",
parameters = {
@Parameter(name =
StringPrefixedSequenceIdGenerator.INCREMENT_PARAM, value = "50"),
@Parameter(name =
StringPrefixedSequenceIdGenerator.VALUE_PREFIX_PARAMETER, value =
"B_"),
@Parameter(name =
StringPrefixedSequenceIdGenerator.NUMBER_FORMAT_PARAMETER, value =
"%05d") })
private String id;

...
}

www.thoughts-on-java.org
Implement a Custom, Sequence-Based IdGenerator

If you want to use a custom generator, you need to define the


generator in a @GenericGenerator annotation and provide the fully-
qualified classname as the strategy. You can also configure a set of
parameters that will be provided to the configure method when
Hibernate instantiates the generator.

Year and month followed by a sequence-based value


The implementation of a custom id generator that uses parts of the
current date as a prefix is pretty similar to the one that uses a
configurable String. The main difference is that you need to
configure a date format that will be used to create the prefix. The
rest of it follows the same principle:
1. You extend Hibernate’s SequenceStyleGenerator class,
2. override the configure method to read the additional
configuration parameters and
3. override the generate method to add the date as a prefix.

public class DatePrefixedSequenceIdGenerator extends


SequenceStyleGenerator {

public static final String DATE_FORMAT_PARAMETER = "dateFormat";


public static final String DATE_FORMAT_DEFAULT = "%tY-%tm";

public static final String NUMBER_FORMAT_PARAMETER = "numberFormat";


public static final String NUMBER_FORMAT_DEFAULT = "%05d";

public static final String DATE_NUMBER_SEPARATOR_PARAMETER =


"dateNumberSeparator";
public static final String DATE_NUMBER_SEPARATOR_DEFAULT = "_";

private String format;

@Override
public Serializable generate(SharedSessionContractImplementor session,
www.thoughts-on-java.org
Object object) throws HibernateException {
Implement a Custom, Sequence-Based IdGenerator

@Override
public Serializable generate(SharedSessionContractImplementor session,
Object object) throws HibernateException {
return String.format(format, LocalDate.now(), super.generate(session,
object));
}

@Override
public void configure(Type type, Properties params,
ServiceRegistry serviceRegistry) throws MappingException {
super.configure(LongType.INSTANCE, params, serviceRegistry);

String dateFormat =
ConfigurationHelper.getString(DATE_FORMAT_PARAMETER, params,
DATE_FORMAT_DEFAULT).replace("%", "%1$");
String numberFormat =
ConfigurationHelper.getString(NUMBER_FORMAT_PARAMETER, params,
NUMBER_FORMAT_DEFAULT).replace("%", "%2$");;
String dateNumberSeparator =
ConfigurationHelper.getString(DATE_NUMBER_SEPARATOR_PARAMETER,
params, DATE_NUMBER_SEPARATOR_DEFAULT);
this.format = dateFormat+dateNumberSeparator+numberFormat;
}
}

A common scenario for this kind of id generator requires you to reset


the sequence number at the end of each day or month or year. This
can’t be handled by the id generator. You need to configure a job that
resets your database sequence in the required intervals and the
DatePrefixedSequenceIdGenerator will automatically get the new
values when it requests the next value from the sequence.

www.thoughts-on-java.org
Implement a Custom, Sequence-Based IdGenerator

Use the DatePrefixedSequenceIdGenerator


If you use the default configuration, the
DatePrefixedSequenceIdGenerator generates ids that follow the
format 2018-08_00001. You can customize the format by setting the
DATE_FORMAT_PARAMETER, NUMBER_FORMAT_PARAMETER
and DATE_NUMBER_SEPARATOR_PARAMETER parameters.

@Entity
public class Book {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"book_seq")
@GenericGenerator(
name = "book_seq",
strategy =
"org.thoughts.on.java.generators.DatePrefixedSequenceIdGenerator",
parameters = {@Parameter(name =
DatePrefixedSequenceIdGenerator.INCREMENT_PARAM, value = "50")})
private String id;

...
}

An attribute value of a parent entity followed by a


sequence-based value
You’re probably not surprised if I tell you that the implementation of
this id generator is pretty similar to the 2 previous ones. The main
difference is that I use the Object object parameter of the generate
method to create the primary key value.

www.thoughts-on-java.org
Implement a Custom, Sequence-Based IdGenerator
That Object contains the entity object for which the
PublisherCodePrefixedSequenceIdGenerator got called. You can use
it to access all attributes of that entity including all initialized
associations. In this example, I use it to get the value of the code
attribute of the associated Publisher entity.

public class PublisherCodePrefixedSequenceIdGenerator


extends SequenceStyleGenerator {

public static final String CODE_NUMBER_SEPARATOR_PARAMETER =


"codeNumberSeparator";
public static final String CODE_NUMBER_SEPARATOR_DEFAULT = "_";

public static final String NUMBER_FORMAT_PARAMETER = "numberFormat";


public static final String NUMBER_FORMAT_DEFAULT = "%05d";

private String format;

@Override
public Serializable generate(SharedSessionContractImplementor session,
Object object) throws HibernateException {
return String.format(format, ((Book)object).getPublisher().getCode(),
super.generate(session, object));
}

@Override
public void configure(Type type, Properties params,
ServiceRegistry serviceRegistry) throws MappingException {
super.configure(LongType.INSTANCE, params, serviceRegistry);
String codeNumberSeparator = ConfigurationHelper.getString(
CODE_NUMBER_SEPARATOR_PARAMETER, params,
CODE_NUMBER_SEPARATOR_DEFAULT);
String numberFormat =
ConfigurationHelper.getString(NUMBER_FORMAT_PARAMETER, params,
NUMBER_FORMAT_DEFAULT).replace("%", "%2$");
this.format = "%1$s"+codeNumberSeparator+numberFormat;
www.thoughts-on-java.org
Implement a Custom, Sequence-Based IdGenerator

String numberFormat = ConfigurationHelper.getString(


NUMBER_FORMAT_PARAMETER, params,
NUMBER_FORMAT_DEFAULT).replace("%", "%2$");
this.format = "%1$s"+codeNumberSeparator+numberFormat;
}

Use the PublisherCodePrefixedSequenceIdGenerator


You can use the PublisherCodePrefixedSequenceIdGenerator in the
same ways as the previously discussed id generators.

@Entity
public class Book {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"book_seq")
@GenericGenerator(
name = "book_seq",
strategy =
"org.thoughts.on.java.generators.PublisherCodePrefixedSequenceIdGenerator",
parameters = {
@Parameter(name =
PublisherCodePrefixedSequenceIdGenerator.INCREMENT_PARAM, value = "50
})
private String id;

...
}

www.thoughts-on-java.org

You might also like