In my App I'm modelling an Invoice. In my country (Italy) every invoice must have a unique sequential number without holes, that every year have to restart from 1.
I thought long and hard about the best way to implement it but I have not found a good guide about this. For now I have a JpaRepository
where I've my custom synchronized save()
method in which I get the last id used:
SELECT MAX(numero) FROM Invoice WHERE YEAR(date) = :year
The problem of this approach is that is not very safe because the developer should know that the save should be done only with that particular service.
Instead I'd like more an approach that is hidden to the developer.
I thought to use a @Prepersist
method in a @EntityListeners
. This sounds good but do get entity manager inside this class is not so simple....so maybe is not the optimal place...
Finally I thought about Hibernate Interceptor....
Please give me some hints. The problem seems a quite generic problem; so maybe there is yet a good practice to follow.
Thanks
SEQUENCE is the generation type recommended by the Hibernate documentation. The generated values are unique per sequence. If we don't specify a sequence name, Hibernate will reuse the same hibernate_sequence for different types.
GenerationType.It relies on an auto-incremented database column and lets the database generate a new value with each insert operation. From a database point of view, this is very efficient because the auto-increment columns are highly optimized, and it doesn't require any additional statements.
Annotation Type Id @Target(value={METHOD,FIELD}) @Retention(value=RUNTIME) public @interface Id. Specifies the primary key of an entity. The field or property to which the Id annotation is applied should be one of the following types: any Java primitive type; any primitive wrapper type; String ; java. util. Date ; java ...
The SequenceGenerator annotation defines a primary key generator that may be referenced by name when a generator element is specified for the GeneratedValue annotation. A sequence generator may be specified on the entity class or on the primary key field or property.
This problem can be broken down into the following requirements:
1000001
) and then always incrementing by a fixed value (say, 1
).1000001
, the increment is 1
and 200 numbers have been generated so far, the latest number should be 1000201
.Any solution can only comply with 4 out of these 5 requirements. For example, if you want to guarantee 1-4, each process will need to take locks so that no other process can generate and use the same number that it has generated. Therefore, imposing 1-4 as requirements will mean that 5 will have to be let gone of. Similarly, if you want to guarantee 1, 2, 4 and 5, you need to make sure that only one process (thread) generates a number at a time because uniqueness cannot be guaranteed in a concurrent environment without locking. Continue this logic and you will see why it is impossible to guarantee all of these requirements at the same time.
Now, the solution depends on which one out of 1-5 you are willing to sacrifice. If you are willing to sacrifice #4 but not #5, you can run a batch process during idle hours to generate the numbers. However, if you put this list in front of a business user (or a finance guy), they will ask you to comply with 1-4 as #5 is a purely technical issue (to them) and therefore they would not want to be bothered with it. If that is the case, a possible strategy is:
DOCUMENT_SEQUENCE
) to keep a track of the last generated number.SERIALIZABLE
), find the required sequence value to use and save the invoice immediately. This should not take too much time because reading a row, incrementing its value and saving a record should be a short enough operation. If possible, make this short transaction a nested transaction to the main one.SERIALIZABLE
lock do not time out too fast.In addition to this, if possible and allowed by your database design guidelines, put a unique constraint on the invoice number column so that duplicate values are not stored at any cost.
Spring gives you all the tools to implement this.
@Transactional
annotation.isolation
attribute of the @Transactional
annotation.I have a sample app that demonstrates using all these pieces together.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With