How to create custom id generator for primary key in springboot

very clear question in title
132 Replies
JavaBot
JavaBot4w ago
This post has been reserved for your question.
Hey @userexit! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically marked as dormant after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
dan1st
dan1st4w ago
Stack Overflow
How to generate Custom Id in JPA
i want generate Custom Id in JPA it must be primary key of table. there are many examples to create Custom Id using hibernate like this i want same implementation but in JPA.The id must be alphanu...
dan1st
dan1st4w ago
very clear answer on Stack Overflow ;) though you might also check the second answer with some updated code Also I would use try-with-resources for closing JPA things
userexit
userexitOP4w ago
@dan1st genericgenerator is deprecated
dan1st
dan1st4w ago
Ah right, there's IdGeneratorType
No description
dan1st
dan1st4w ago
https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/annotations/IdGeneratorType.html You can create your own annotation and annotate it with IdGeneratorType
userexit
userexitOP4w ago
so thats the way now ? make my own annotation
dan1st
dan1st4w ago
yes see the Javadoc here it has examples for pretty much exactly that use case there
userexit
userexitOP4w ago
public CustomSequenceGenerator(CustomSequence config, Member annotatedMember, CustomIdGeneratorCreationContext context) kinda dumb to pass teh CustomIdGeneratorCreationContext since we pass the hibernate session in the generate method
dan1st
dan1st4w ago
I think that gives you information on what entity it is about or something like that
userexit
userexitOP4w ago
I cant seem to find the doc for it I know the first config contains the info passed in our custom annotation
userexit
userexitOP4w ago
and that annotatedMember has info about the attribute or method but that last one idk
dan1st
dan1st4w ago
maybe you can use it to get information on the entity not just the field name
userexit
userexitOP4w ago
also these custom annotations are they only good for generation of id or can they be used for other stuff
dan1st
dan1st4w ago
also for other things You can do all sorts of custom stuff with it Also just because it's passed, it doesn't mean you have to use it
userexit
userexitOP4w ago
it gives good enough information
No description
userexit
userexitOP4w ago
about the database the mapping of the entity etc context.getDatabase().getPersistentClass()
dan1st
dan1st4w ago
@IdGeneratorType(YourCustomGenerator.class)
@Retention(RUNTIME) @Target({METHOD,FIELD})
public @interface YourCustomGeneration {
//you can
}
@IdGeneratorType(YourCustomGenerator.class)
@Retention(RUNTIME) @Target({METHOD,FIELD})
public @interface YourCustomGeneration {
//you can
}
//in your entity
@Id
@YourCustomGeneration
private YourIdType yourId;
//in your entity
@Id
@YourCustomGeneration
private YourIdType yourId;
public class CustomSequenceGenerator implements BeforeExecutionGenerator {
public CustomSequenceGenerator(YourCustomGeneration annotation, Member annotatedMember,
CustomIdGeneratorCreationContext context) {
//you can do initialization logic here
}
@Override
public Object generate​(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType){
//logic to generate your key
return yourNewKey;
}
}
public class CustomSequenceGenerator implements BeforeExecutionGenerator {
public CustomSequenceGenerator(YourCustomGeneration annotation, Member annotatedMember,
CustomIdGeneratorCreationContext context) {
//you can do initialization logic here
}
@Override
public Object generate​(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType){
//logic to generate your key
return yourNewKey;
}
}
userexit
userexitOP4w ago
good use case i can see for this is making a generator that uses prefix and subfix
dan1st
dan1st4w ago
for example
userexit
userexitOP4w ago
and since u can choose both of them through the annotation it can be general
dan1st
dan1st4w ago
or literally anything else ;)
userexit
userexitOP4w ago
so u think its fien to get nextval from sequence with this:
con = session.getJdbcConnectionAccess().obtainConnection();
stmt = con.createStatement();


String sql_query = "SELECT MATRICULE_SEQ.NEXTVAL FROM DUAL";
ResultSet result = stmt.executeQuery(sql_query);

if (result.next()) {
key = result.getInt(1);
}
con = session.getJdbcConnectionAccess().obtainConnection();
stmt = con.createStatement();


String sql_query = "SELECT MATRICULE_SEQ.NEXTVAL FROM DUAL";
ResultSet result = stmt.executeQuery(sql_query);

if (result.next()) {
key = result.getInt(1);
}
dan1st
dan1st4w ago
Please close the objects that should be closed
userexit
userexitOP4w ago
doesnt garbage collector do that ?
dan1st
dan1st4w ago
I edited the second answer to that question to show how The GC collects unused memory. But some things (marked Closeable/AutoCloseable should be closed by you this is if it has to do some extra logic when it isn't used any more for example telling the DB "I don't need that any more"/disconnecting from the DB
userexit
userexitOP4w ago
okay well I guess closing stmt and con then maybe add a finally clause
dan1st
dan1st4w ago
You can do it with a try-with-resources statement which is what's done in the second answer
userexit
userexitOP4w ago
try-with-resources ? what happened to try-catch-finally 😭
dan1st
dan1st4w ago
try(SomeAutoCloseable objectYouWantToClose = ...) {

}//objectYouWantToClose will be closed automatically here
try(SomeAutoCloseable objectYouWantToClose = ...) {

}//objectYouWantToClose will be closed automatically here
userexit
userexitOP4w ago
ive seen that many times i thought it was just different syntax lol
dan1st
dan1st4w ago
that's roughly equivalent to
SomeAutoCloseable objectYouWantToClose = ...
try {

} finally {
if(objectYouWantToClose != null) {
try {
objectYouWantToClose.close();
} catch(Exception e) {
if (EXCEPTION HAS BEEN THROWN IN TRY BLOCK) {
thatException.addSuppressed(e);
throw thatException;
}
throw e;
}
}
}
SomeAutoCloseable objectYouWantToClose = ...
try {

} finally {
if(objectYouWantToClose != null) {
try {
objectYouWantToClose.close();
} catch(Exception e) {
if (EXCEPTION HAS BEEN THROWN IN TRY BLOCK) {
thatException.addSuppressed(e);
throw thatException;
}
throw e;
}
}
}
meaning it makes sure the closing is handled correctly and if you have multiple acquired resources in a try block, it closes all of them properly There are a few pitfalls when using try-finally for closing resources and try-with-resources makes it easy to do that correctly
userexit
userexitOP4w ago
so the try (resource) will close other resources inside the try that are not listed between parenthsis too right ?
dan1st
dan1st4w ago
inside the () of the try not the things inside the {}
userexit
userexitOP4w ago
u @Retention and @Target are not found by vscdoium
dan1st
dan1st4w ago
But you can put multiple things in the ()
userexit
userexitOP4w ago
do I need another dependency ? separated by semi colon nice
dan1st
dan1st4w ago
These are always included in Java But you might need to import it These are in java.lang.annotation
userexit
userexitOP4w ago
try (int a = 3; int b = 3 * a) is this possible or nah
dan1st
dan1st4w ago
Retention (Java SE 21 & JDK 21)
declaration: module: java.base, package: java.lang.annotation, annotation type: Retention
dan1st
dan1st4w ago
not with int since you cannot close ints
userexit
userexitOP4w ago
well with resources u can close my point is
dan1st
dan1st4w ago
Stack Overflow
How to generate Custom Id in JPA
i want generate Custom Id in JPA it must be primary key of table. there are many examples to create Custom Id using hibernate like this i want same implementation but in JPA.The id must be alphanu...
userexit
userexitOP4w ago
can u make the second one depend on the first one
dan1st
dan1st4w ago
multiple things closed there
userexit
userexitOP4w ago
al;r ty i came wantign help for @IdGeneratorType I went out learning few things about try catch too
dan1st
dan1st4w ago
that's how stuff goes ;) always keep learning
userexit
userexitOP4w ago
@dan1st maybe last question for today
john
john4w ago
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) {

}
public Serializable generate(SharedSessionContractImplementor session, Object object) {

}
This message has been formatted automatically. You can disable this using /preferences.
userexit
userexitOP4w ago
why return Serializable ?
dan1st
dan1st4w ago
Because the old versions of Hibernate used Serializable
userexit
userexitOP4w ago
how will springboot format this into a varchar ?
dan1st
dan1st4w ago
ANd Serializable was commonly used for entities in the past ?
userexit
userexitOP4w ago
im returning the next key to be used in my database it cant just put Serializable in my database it has to be same type as the key in my database will it just cast it ?
dan1st
dan1st4w ago
You can just return whatever type you want You don't need Serializable here
userexit
userexitOP4w ago
in the method im overriding says return type is serializable
dan1st
dan1st4w ago
Can you show your code? I think it shouldn't be public Serializable generate(SharedSessionContractImplementor session, Object object) but public Object generate​(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType)
userexit
userexitOP4w ago
public class PrefixIdGenerator implements IdentifierGenerator {
private String prefix;
private String sequenceName;

public PrefixIdGenerator(PrefixGenerator config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
prefix = config.prefix();
sequenceName = config.sequenceName();
}

@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) {
try (session.getJdbcConnectionAccess().obtainConnection()) {

}
}
}
public class PrefixIdGenerator implements IdentifierGenerator {
private String prefix;
private String sequenceName;

public PrefixIdGenerator(PrefixGenerator config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
prefix = config.prefix();
sequenceName = config.sequenceName();
}

@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) {
try (session.getJdbcConnectionAccess().obtainConnection()) {

}
}
}
dan1st
dan1st4w ago
Oh you are using IdentifierGenerator? Ah I see that one is possible as wel just change public Serializable generate(...) to public Object generate(...)
userexit
userexitOP4w ago
well which one should I use its the only one u can use either that one or sequencestylegenerator
dan1st
dan1st4w ago
I think IdentifierGenerator is perfectly fine
userexit
userexitOP4w ago
but sequencestylegenerator is just a implementation of identifiergenreator
dan1st
dan1st4w ago
use what works best for you
userexit
userexitOP4w ago
kk got it
public class PrefixIdGenerator implements IdentifierGenerator {
private String prefix;
private String sequenceName;

public PrefixIdGenerator(PrefixGenerator config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
prefix = config.prefix();
sequenceName = config.sequenceName();
}

@Override
public Object generate(SharedSessionContractImplementor session, Object object) {
String key = prefix;
String query = "SELECT ?.NEXTVAL FROM dual;";
try (Connection con = session.getJdbcConnectionAccess().obtainConnection() ; PreparedStatement pstm = con.prepareStatement(query)) {
pstm.setString(0, sequenceName);
ResultSet result = pstm.executeQuery();
if (result.next()) {
key = key + result.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return key;
}
}
public class PrefixIdGenerator implements IdentifierGenerator {
private String prefix;
private String sequenceName;

public PrefixIdGenerator(PrefixGenerator config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
prefix = config.prefix();
sequenceName = config.sequenceName();
}

@Override
public Object generate(SharedSessionContractImplementor session, Object object) {
String key = prefix;
String query = "SELECT ?.NEXTVAL FROM dual;";
try (Connection con = session.getJdbcConnectionAccess().obtainConnection() ; PreparedStatement pstm = con.prepareStatement(query)) {
pstm.setString(0, sequenceName);
ResultSet result = pstm.executeQuery();
if (result.next()) {
key = key + result.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return key;
}
}
this is my class im not s ure if
String query = "SELECT ?.NEXTVAL FROM dual;";
String query = "SELECT ?.NEXTVAL FROM dual;";
is good usage
dan1st
dan1st4w ago
ig it's ok if it works
userexit
userexitOP4w ago
its not String query = "SELECT " + sequenceName + ".NEXTVAL FROM dual"; must do it like this
dan1st
dan1st4w ago
well ideally not;)
userexit
userexitOP4w ago
Even if im using a preparedstatement ? shouldnt that protect it
dan1st
dan1st4w ago
yes
userexit
userexitOP4w ago
against sql injection
dan1st
dan1st4w ago
PreparedStatement only protects against SQL injections if you use string concatenation
userexit
userexitOP4w ago
this is string concatenation no
dan1st
dan1st4w ago
it is it wouldn't be vulnerable if you can be sure that all components are safe but you'd need to make sure that's actually the case
userexit
userexitOP4w ago
well isnt preparedstatement the one doing that
dan1st
dan1st4w ago
only with ? not with +
userexit
userexitOP4w ago
im going to make an enum that holds al available sequences and it compares them with that 🤷‍♂️ but yeah everytime we add a sequence in the database I would need to modify the enum only ifit was possible to use some kind of factor y or event listener for when a sequqnece is added
dan1st
dan1st4w ago
you could also get it from the classes and make sure no user data sneaks in but it might be a bit fragile the more complex the higher the risk
userexit
userexitOP4w ago
@dan1st I ended up making this:
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

public interface CustomIdentifierGenerator extends IdentifierGenerator {


void checkSequences() {
// implementation, go in database, get all the sequence_name, check if there is one we don't know about, if that's the case then add it to a class that will contain them
};


@Override
default Object generate(SharedSessionContractImplementor session, Object object) {

checkSequences();


return generateIdentifier(session, object);
}

Object generateIdentifier(SharedSessionContractImplementor session, Object object);
}
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;

public interface CustomIdentifierGenerator extends IdentifierGenerator {


void checkSequences() {
// implementation, go in database, get all the sequence_name, check if there is one we don't know about, if that's the case then add it to a class that will contain them
};


@Override
default Object generate(SharedSessionContractImplementor session, Object object) {

checkSequences();


return generateIdentifier(session, object);
}

Object generateIdentifier(SharedSessionContractImplementor session, Object object);
}
but u can see why its not a good approach everytime we add a key we queue sequences and so on im going to try and find a way in which I can simply have a event everytime a sequence is added my app gets notified
dan1st
dan1st4w ago
actually the sequence name from the config should be trusted so it should be fine in this case but in other cases, you might have issues or if you change the code in the future
userexit
userexitOP4w ago
why should it ?
dan1st
dan1st4w ago
The sequence name comes from your annotation, right? and only from that annotation
userexit
userexitOP4w ago
yeha but still rather be safe than sorry im learning about jmx
dan1st
dan1st4w ago
so users have no chance of altering it
userexit
userexitOP4w ago
to see how to push events from triggers to my java app everytime a sequence is added im going to instantiate it in a sequence holder class
dan1st
dan1st4w ago
and this would make it safe from SQL injection
userexit
userexitOP4w ago
and compare against that in the case users can alter it does this alternative im telling u seem fine ?
dan1st
dan1st4w ago
oh
userexit
userexitOP4w ago
using jmx
dan1st
dan1st4w ago
you have another possibility you can create a single table holding all sequences
|sequence_name|current_index|
|sequence_name|current_index|
userexit
userexitOP4w ago
thats TableGeneration it's already in spring
dan1st
dan1st4w ago
yes
userexit
userexitOP4w ago
yeah but then I cant use my custom logic of prefix
dan1st
dan1st4w ago
What is your custom logic? You can still make your own custom generation that uses a table internally Note: I mean a single table for all sequences I think the thing Spring normally tries to do is one table for each sequence
userexit
userexitOP4w ago
nah it does one table, like u have there and then u gotta give it the name of the table the sequence_name and the column holding the current index and the column holding the sequence name
dan1st
dan1st4w ago
yeah idk by heart
userexit
userexitOP4w ago
is it good practice to have a class holding sequence names, and then set a trigger for sequence adding to my db, and then pushing event with jmx to my applicaton and add the sequence name to the class holding them
dan1st
dan1st4w ago
I wouldn't say so
userexit
userexitOP4w ago
really why
dan1st
dan1st4w ago
At that point, I would just use something like table generation lots of unnecessary complexity and you can make your own table generation if necessary
userexit
userexitOP4w ago
but in terms of speed no ? its easier to compare a string against 5 others than to query a database from java app everytime a key is needed/generated
dan1st
dan1st4w ago
?
userexit
userexitOP4w ago
sequenceNameHolder.contains(sequenceName) if its the case then valid sequenceName
dan1st
dan1st4w ago
ah like that
userexit
userexitOP4w ago
yeah then everytime a sequence is added in my db in the trigger psuh a jmx event to my java app and do something like: sequenceNameHolder.add(newSequenceName)
dan1st
dan1st4w ago
Though in this case it might be a better idea to generate the actual names of the sequence from the application but that's your decision
userexit
userexitOP4w ago
oh yeah i know what u mena like create a sequence for the entitis myself
dan1st
dan1st4w ago
To be honest, I don't think all of that complexity is necessary
userexit
userexitOP4w ago
i was just thinking in case of a bigger project where database is maanged by another team
dan1st
dan1st4w ago
Like just use the stuff Hibernate normally does Well should your application have the permissions to change the DB structure/add sequences?
userexit
userexitOP4w ago
imo it should only insert, delete, update values from db i find sequences to be more of an internal thing to the database itself so managed by another team
dan1st
dan1st4w ago
values yes - but should it also have the permissions to create or drop sequences, tables, etc? that's for the DB team to decide
userexit
userexitOP4w ago
yes exactly so whenever they add a sequence we should be abl to be informed by it in our java app
dan1st
dan1st4w ago
I have one advice for you: Don't introduce unnecessary complexity
userexit
userexitOP4w ago
im not gonna do all that i did a simple regex for handling and then the name convention for sequences in the database is going to follow that regex but i was thinking on a very high scale application where millions of primary keys get added every day queuing the db to see all sequence nams and comparing them is awful and so maybe a jmx event will then be useful
dan1st
dan1st4w ago
Can't you use the normal sequence thing from Spring?
userexit
userexitOP4w ago
in my case though no way
dan1st
dan1st4w ago
Like do you really need a custom thing for it?
userexit
userexitOP4w ago
no because I want to have: prefix+subfix for example for my person table: PER1 PER2 PER3 PER4 Just because I want to not because I need to would be nice if i could combine these
dan1st
dan1st4w ago
I think you can also create an IdentityGenerator that delegates to a SequenceGenerator and adds a name
userexit
userexitOP4w ago
that would be mazing ill try searching that up
userexit
userexitOP4w ago
those are both impelemntations of IdentifierGenerator right ?
dan1st
dan1st4w ago
yes that's how I found them
userexit
userexitOP4w ago
okay and then using my own annotation can it @IdGeneratorType one class that implements or extends one of those
dan1st
dan1st4w ago
not sure you might need to make a custom IdentifierGeneratorthat just delegates to one of the others
userexit
userexitOP4w ago
wdym with delegates
dan1st
dan1st4w ago
public interface CustomIdentifierGenerator extends IdentifierGenerator {

private final IdentifierGenerator delegate = new OrderedSequenceGenerator();//or similar

@Override
default Object generate(SharedSessionContractImplementor session, Object object) {

return "PER" + delegate.generate(session, object);
}
}
public interface CustomIdentifierGenerator extends IdentifierGenerator {

private final IdentifierGenerator delegate = new OrderedSequenceGenerator();//or similar

@Override
default Object generate(SharedSessionContractImplementor session, Object object) {

return "PER" + delegate.generate(session, object);
}
}
something like that
userexit
userexitOP4w ago
mmm I see use a generator within a generator and the top generator all it does is add a string
dan1st
dan1st4w ago
Maybe you need to do something like
public interface CustomIdentifierGenerator extends IdentifierGenerator {

private final IdentifierGenerator delegate = new OrderedSequenceGenerator();//or similar

@Override
default Object generate(SharedSessionContractImplementor session, Object object) {
if (object instanceof String s && s.startsWith("PER")) {
object = Integer.parseInt(s.substring(3));
}
return "PER" + delegate.generate(session, object);
}
}
public interface CustomIdentifierGenerator extends IdentifierGenerator {

private final IdentifierGenerator delegate = new OrderedSequenceGenerator();//or similar

@Override
default Object generate(SharedSessionContractImplementor session, Object object) {
if (object instanceof String s && s.startsWith("PER")) {
object = Integer.parseInt(s.substring(3));
}
return "PER" + delegate.generate(session, object);
}
}
or whatever but you'd need to try that do whatever works for you
userexit
userexitOP4w ago
yeah im going to try those out nice brainstorming in here Ended up doing this @dan1st
public class PrefixedSequenceIdGenerator implements IdentifierGenerator {
private String prefix;
private SequenceStyleGenerator sequenceGenerator;

public PrefixedSequenceIdGenerator(PrefixGenerator config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
this.prefix = config.prefix();

// Initialize the sequence generator with the sequence name from the annotation
this.sequenceGenerator = new SequenceStyleGenerator();
this.sequenceGenerator.initialize(config.sequenceName(), context.getServiceRegistry());
}

@Override
public Object generate(SharedSessionContractImplementor session, Object object) {
// Generate the next sequence value using the sequence generator
Number sequenceValue = (Number) sequenceGenerator.generate(session, object);

// Return the concatenated prefix and sequence value
return prefix + sequenceValue.toString();
}
}
public class PrefixedSequenceIdGenerator implements IdentifierGenerator {
private String prefix;
private SequenceStyleGenerator sequenceGenerator;

public PrefixedSequenceIdGenerator(PrefixGenerator config, Member annotatedMember, CustomIdGeneratorCreationContext context) {
this.prefix = config.prefix();

// Initialize the sequence generator with the sequence name from the annotation
this.sequenceGenerator = new SequenceStyleGenerator();
this.sequenceGenerator.initialize(config.sequenceName(), context.getServiceRegistry());
}

@Override
public Object generate(SharedSessionContractImplementor session, Object object) {
// Generate the next sequence value using the sequence generator
Number sequenceValue = (Number) sequenceGenerator.generate(session, object);

// Return the concatenated prefix and sequence value
return prefix + sequenceValue.toString();
}
}
yeah and now im creating the sequence from myj ava code
JavaBot
JavaBot4w ago
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived. If your question was not answered yet, feel free to re-open this post or create a new one. In case your post is not getting any attention, you can try to use /help ping. Warning: abusing this will result in moderative actions taken against you.
Want results from more Discord servers?
Add your server