#How to create custom id generator for primary key in springboot

301 messages · Page 1 of 1 (latest)

weak frost
#

very clear question in title

errant gustBOT
#

This post has been reserved for your question.

Hey @weak frost! 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.

wary spindle
#

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

weak frost
#

@wary spindle genericgenerator is deprecated

wary spindle
#

Ah right, there's IdGeneratorType

#

You can create your own annotation and annotate it with IdGeneratorType

weak frost
#

make my own annotation

wary spindle
#

yes

wary spindle
#

it has examples for pretty much exactly that use case there

weak frost
#

public CustomSequenceGenerator(CustomSequence config, Member annotatedMember,
CustomIdGeneratorCreationContext context)

#

kinda dumb to pass teh CustomIdGeneratorCreationContext

#

since we pass the hibernate session in the generate method

wary spindle
weak frost
#

I know the first config contains the info passed in our custom annotation

weak frost
#

and that annotatedMember has info about the attribute or method

#

but that last one idk

wary spindle
#

maybe you can use it to get information on the entity

#

not just the field name

weak frost
#

also these custom annotations

#

are they only good for

#

generation of id

#

or can they be used for other stuff

wary spindle
#

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

weak frost
#

it gives good enough information

#

about the database

#

the mapping of the entity

#

etc

#

context.getDatabase().getPersistentClass()

wary spindle
#
@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;
    }
}
weak frost
#

is making a generator that uses prefix and subfix

wary spindle
#

for example

weak frost
#

and since u can choose both of them through the annotation

#

it can be general

wary spindle
#

or literally anything else ;)

weak frost
#

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);  
            }
wary spindle
#

Please close the objects that should be closed

weak frost
wary spindle
wary spindle
#

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

weak frost
#

maybe add a finally clause

wary spindle
#

You can do it with a try-with-resources statement

#

which is what's done in the second answer

weak frost
#

what happened to try-catch-finally

#

😭

wary spindle
#
try(SomeAutoCloseable objectYouWantToClose = ...) {

}//objectYouWantToClose will be closed automatically here
weak frost
#

ive seen that many times

#

i thought it was just different syntax

#

lol

wary spindle
#

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

wary spindle
weak frost
#

will close other resources inside the try

#

that are not listed between parenthsis too

#

right ?

wary spindle
#

not the things inside the {}

weak frost
wary spindle
#

But you can put multiple things in the ()

weak frost
#

do I need another dependency ?

weak frost
#

nice

wary spindle
#

But you might need to import it

#

These are in java.lang.annotation

weak frost
#

try (int a = 3; int b = 3 * a)

#

is this possible

#

or nah

wary spindle
wary spindle
weak frost
#

well with resources u can close

#

my point is

wary spindle
weak frost
#

can u make the second one

#

depend

#

on the first one

wary spindle
weak frost
#

i came wantign help for @IdGeneratorType

#

I went out learning few things about try catch too

wary spindle
#

that's how stuff goes ;)

#

always keep learning

weak frost
#

@wary spindle maybe last question for today

coarse flumeBOT
#

@Override ```java

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

This message has been formatted automatically. You can disable this using /preferences.

weak frost
#

why return Serializable

#

?

wary spindle
weak frost
#

into a varchar

#

?

wary spindle
#

ANd Serializable was commonly used for entities in the past

wary spindle
weak frost
#

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

#

?

wary spindle
#

You can just return whatever type you want

#

You don't need Serializable here

weak frost
#

in the method im overriding says return type is serializable

wary spindle
#

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)

weak frost
#
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()) {

        }
    }
}
wary spindle
#

Ah I see that one is possible as wel

wary spindle
weak frost
#

its the only one u can use

#

either that one or

#

sequencestylegenerator

wary spindle
#

I think IdentifierGenerator is perfectly fine

weak frost
#

but sequencestylegenerator is just a implementation of identifiergenreator

wary spindle
#

use what works best for you

weak frost
#
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

wary spindle
#

ig it's ok if it works

weak frost
#

its not

#

String query = "SELECT " + sequenceName + ".NEXTVAL FROM dual";

#

must do it like this

wary spindle
#

well ideally not;)

weak frost
#

Even if im using a preparedstatement

#

?

#

shouldnt that protect it

wary spindle
#

yes

weak frost
#

against sql injection

wary spindle
#

PreparedStatement only protects against SQL injections if you use string concatenation

weak frost
wary spindle
#

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

weak frost
wary spindle
#

not with +

weak frost
#

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

wary spindle
#

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

weak frost
#

@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

wary spindle
#

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

wary spindle
#

and only from that annotation

weak frost
#

but still rather be safe than sorry

#

im learning about jmx

wary spindle
#

so users have no chance of altering it

weak frost
#

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

wary spindle
weak frost
#

and compare against that

weak frost
#

does this alternative im telling u seem fine

#

?

wary spindle
#

oh

weak frost
#

using jmx

wary spindle
#

you have another possibility

#

you can create a single table holding all sequences

#
|sequence_name|current_index|
weak frost
#

it's already in spring

wary spindle
#

yes

weak frost
#

yeah but then I cant use my custom logic

#

of prefix

wary spindle
#

What is your custom logic?

#

You can still make your own custom generation that uses a table internally

wary spindle
#

I think the thing Spring normally tries to do is one table for each sequence

weak frost
#

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

wary spindle
#

yeah idk by heart

weak frost
# wary spindle 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

weak frost
wary spindle
#

At that point, I would just use something like table generation

wary spindle
wary spindle
weak frost
#

its easier to compare a string against 5 others

#

than to query a database from java app

#

everytime a key is needed/generated

weak frost
#

if its the case then valid sequenceName

wary spindle
#

ah like that

weak frost
#

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)

wary spindle
#

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

weak frost
#

i know what u mena

#

like create a sequence for the entitis

#

myself

wary spindle
#

To be honest, I don't think all of that complexity is necessary

weak frost
#

i was just thinking in case of a bigger project where database is maanged by another team

wary spindle
#

Like just use the stuff Hibernate normally does

wary spindle
weak frost
#

i find sequences to be more of an internal thing to the database itself

#

so managed by another team

wary spindle
#

that's for the DB team to decide

weak frost
#

so whenever they add a sequence

#

we should be abl to be informed by it in our java app

wary spindle
#

I have one advice for you: Don't introduce unnecessary complexity

weak frost
#

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

wary spindle
weak frost
#

in my case though

#

no way

wary spindle
#

Like do you really need a custom thing for it?

weak frost
#

Just because I want to not because I need to

#

would be nice if i could combine these

wary spindle
#

I think you can also create an IdentityGenerator that delegates to a SequenceGenerator and adds a name

weak frost
#

ill try searching that up

weak frost
#

those are both impelemntations of IdentifierGenerator

#

right ?

wary spindle
#

yes

#

that's how I found them

weak frost
#

can it @IdGeneratorType

#

one class

#

that implements or extends

#

one of those

wary spindle
#

not sure

#

you might need to make a custom IdentifierGeneratorthat just delegates to one of the others

wary spindle
#
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

weak frost
#

mmm

#

I see

#

use a generator

#

within a generator

#

and the top generator all it does is add a string

wary spindle
#

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

weak frost
#

yeah

#

im going to try those out

#

nice brainstorming in here

weak frost
#

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