#How to create custom id generator for primary key in springboot
301 messages · Page 1 of 1 (latest)
⌛ This post has been reserved for your question.
Hey @weak frost! Please use
/closeor theClose Postbutton 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.
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
@wary spindle genericgenerator is deprecated
Ah right, there's IdGeneratorType
You can create your own annotation and annotate it with IdGeneratorType
so thats the way now ?
make my own annotation
yes
see the Javadoc here
it has examples for pretty much exactly that use case there
public CustomSequenceGenerator(CustomSequence config, Member annotatedMember,
CustomIdGeneratorCreationContext context)
kinda dumb to pass teh CustomIdGeneratorCreationContext
since we pass the hibernate session in the generate method
I think that gives you information on what entity it is about or something like that
I cant seem to find the doc for it
I know the first config contains the info passed in our custom annotation
and that annotatedMember has info about the attribute or method
but that last one idk
also these custom annotations
are they only good for
generation of id
or can they be used for other stuff
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
it gives good enough information
about the database
the mapping of the entity
etc
context.getDatabase().getPersistentClass()
@IdGeneratorType(YourCustomGenerator.class)
@Retention(RUNTIME) @Target({METHOD,FIELD})
public @interface YourCustomGeneration {
//you can
}
//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;
}
}
good use case i can see for this
is making a generator that uses prefix and subfix
for example
or literally anything else ;)
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);
}
Please close the objects that should be closed
doesnt garbage collector do that ?
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
okay well I guess closing stmt and con then
maybe add a finally clause
You can do it with a try-with-resources statement
which is what's done in the second answer
try-with-resources ?
what happened to try-catch-finally
😭
try(SomeAutoCloseable objectYouWantToClose = ...) {
}//objectYouWantToClose will be closed automatically here
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;
}
}
}
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
so the try (resource)
will close other resources inside the try
that are not listed between parenthsis too
right ?
inside the () of the try
not the things inside the {}
u @Retention and @Target are not found by vscdoium
But you can put multiple things in the ()
do I need another dependency ?
separated by semi colon
nice
These are always included in Java
But you might need to import it
These are in java.lang.annotation
declaration: module: java.base, package: java.lang.annotation, annotation type: Retention
not with int since you cannot close ints
But see here: https://stackoverflow.com/a/77113057/10871900
multiple things closed there
al;r ty
i came wantign help for @IdGeneratorType
I went out learning few things about try catch too
@wary spindle maybe last question for today
@Override ```java
public Serializable generate(SharedSessionContractImplementor session, Object object) {
} ```
This message has been formatted automatically. You can disable this using /preferences.
Because the old versions of Hibernate used Serializable
how will springboot format this
into a varchar
?
ANd Serializable was commonly used for entities in the past
?
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
?
in the method im overriding says return type is serializable
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)
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()) {
}
}
}
Oh you are using IdentifierGenerator?
Ah I see that one is possible as wel
just change public Serializable generate(...) to public Object generate(...)
well which one should I use
its the only one u can use
either that one or
sequencestylegenerator
I think IdentifierGenerator is perfectly fine
but sequencestylegenerator is just a implementation of identifiergenreator
use what works best for you
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;
}
}
this is my class
im not s ure if
String query = "SELECT ?.NEXTVAL FROM dual;";
is good usage
ig it's ok if it works
its not
String query = "SELECT " + sequenceName + ".NEXTVAL FROM dual";
must do it like this
well ideally not;)
yes
against sql injection
PreparedStatement only protects against SQL injections if you use string concatenation
this is string concatenation no
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
well isnt preparedstatement the one doing that
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
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
@wary spindle 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);
}
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
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
why should it
?
The sequence name comes from your annotation, right?
and only from that annotation
yeha
but still rather be safe than sorry
im learning about jmx
so users have no chance of altering it
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
and this would make it safe from SQL injection
and compare against that
in the case users can alter it
does this alternative im telling u seem fine
?
oh
using jmx
you have another possibility
you can create a single table holding all sequences
|sequence_name|current_index|
thats TableGeneration
it's already in spring
yes
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
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
yeah idk by heart
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
I wouldn't say so
really why
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
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
?
sequenceNameHolder.contains(sequenceName)
if its the case then valid sequenceName
ah like that
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)
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
oh yeah
i know what u mena
like create a sequence for the entitis
myself
To be honest, I don't think all of that complexity is necessary
i was just thinking in case of a bigger project where database is maanged by another team
Like just use the stuff Hibernate normally does
Well should your application have the permissions to change the DB structure/add sequences?
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
values yes - but should it also have the permissions to create or drop sequences, tables, etc?
that's for the DB team to decide
yes exactly
so whenever they add a sequence
we should be abl to be informed by it in our java app
I have one advice for you: Don't introduce unnecessary complexity
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
Can't you use the normal sequence thing from Spring?
Like do you really need a custom thing for it?
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
I think you can also create an IdentityGenerator that delegates to a SequenceGenerator and adds a name
that would be mazing
ill try searching that up
okay and then using my own annotation
can it @IdGeneratorType
one class
that implements or extends
one of those
not sure
you might need to make a custom IdentifierGeneratorthat just delegates to one of the others
wdym with delegates
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
mmm
I see
use a generator
within a generator
and the top generator all it does is add a string
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);
}
}
or whatever
but you'd need to try that
do whatever works for you
Ended up doing this @wary spindle
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