Anıl Şenocak

The main idea is to:
The project compares two different approaches side-by-side:
This makes the project useful as a reference for teams that want flexible Oracle ID generation without hardcoding a sequence generator on every entity.
When using Oracle with JPA/Hibernate, sequence-based IDs are common. However, the default approach usually requires every entity to declare its own fixed configuration, for example:
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ\_USER\_GENERATOR")
@SequenceGenerator(name = "SEQ\_USER\_GENERATOR", sequenceName = "users\_seq", allocationSize = 1)
This works well but becomes repetitive when many entities follow a naming convention such as:
This project centralizes sequence handling in a reusable generator so that the application can:
The core component is DynamicSequenceGenerator. Its responsibilities are:
The generator is attached to the User entity through:
@GeneratedValue(generator = "dynamic-seq")
@GenericGenerator(name = "dynamic-seq", strategy = "com.github.senocak.DynamicSequenceGenerator")
The generator determines the sequence name using conventions:
This means the sequence naming rule is defined once in the generator rather than repeated across all entities.
For bulk insert operations, the generator can fetch multiple IDs in one Oracle query:
SELECT users_seq.NEXTVAL FROM dual CONNECT BY LEVEL <= ?
Returned IDs are stored in a temporary in-memory queue and reused during persistence. This reduces database round trips significantly compared with fetching IDs one-by-one.
IdGenerationContext stores the desired batch size using ThreadLocal. Before calling saveAll, the app sets the batch size for the current work unit. The generator reads that value and decides whether to:
After persistence, the thread-local state is cleared to avoid leaking configuration into later operations.
If batchSize <= 1, the generator executes:
SELECT <sequence>.NEXTVAL FROM dual
This is the simple path for one-at-a-time inserts.
If batchSize > 1, the generator executes:
SELECT <sequence>.NEXTVAL FROM dual CONNECT BY LEVEL <= ?
The returned values are stored in an internal queue and reused by later generate() calls.
Benefits:
This application demonstrates a Dynamic Oracle ID generation strategy for Spring Boot and Kotlin. Its main contribution is a custom Hibernate generator that:
For custom implementation;
2026-05-27T23:21:33.780+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:4, Time:75, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["SELECT users_seq.NEXTVAL FROM dual CONNECT BY LEVEL <= ?"]
Params:[(5)]
2026-05-27T23:21:33.783+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Fetched 5 ids into local pool (requested 5). Returning id after batch fetch: 1
2026-05-27T23:21:33.800+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 2
2026-05-27T23:21:33.801+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 3
2026-05-27T23:21:33.801+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 4
2026-05-27T23:21:33.802+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 5
2026-05-27T23:21:33.836+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:4, Time:14, Success:True
Type:Prepared, Batch:True, QuerySize:1, BatchSize:5
Query:["insert into users (name,id) values (?,?)"]
Params:[(John 1 0,1),(John 2 1,2),(John 3 2,3),(John 4 3,4),(John 5 4,5)]
2026-05-27T23:21:33.849+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:5, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["SELECT users_seq.NEXTVAL FROM dual CONNECT BY LEVEL <= ?"]
Params:[(5)]
2026-05-27T23:21:33.849+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Fetched 5 ids into local pool (requested 5). Returning id after batch fetch: 6
2026-05-27T23:21:33.850+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 7
2026-05-27T23:21:33.850+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 8
2026-05-27T23:21:33.850+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 9
2026-05-27T23:21:33.850+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 10
2026-05-27T23:21:33.858+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:5, Time:7, Success:True
Type:Prepared, Batch:True, QuerySize:1, BatchSize:5
Query:["insert into users (name,id) values (?,?)"]
Params:[(John 6 0,6),(John 7 1,7),(John 8 2,8),(John 9 3,9),(John 10 4,10)]
2026-05-27T23:21:33.875+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:6, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["SELECT users_seq.NEXTVAL FROM dual CONNECT BY LEVEL <= ?"]
Params:[(2)]
2026-05-27T23:21:33.875+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Fetched 2 ids into local pool (requested 2). Returning id after batch fetch: 11
2026-05-27T23:21:33.876+03:00 INFO 49079 --- [SpringKotlinOracle] [ main] c.g.senocak.DynamicSequenceGenerator : Returning id from local pool: 12
2026-05-27T23:21:33.882+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:6, Time:4, Success:True
Type:Prepared, Batch:True, QuerySize:1, BatchSize:2
Query:["insert into users (name,id) values (?,?)"]
Params:[(John 11 0,11),(John 12 1,12)]
For traditional implementation;
2026-05-27T23:21:38.967+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:20, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.971+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.973+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.974+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.976+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.977+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.978+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.979+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.991+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:11, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.992+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.993+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:38.994+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select users_seq.nextval"]
Params:[()]
2026-05-27T23:21:39.009+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:12, Success:True
Type:Prepared, Batch:True, QuerySize:1, BatchSize:10
Query:["insert into roles (name,id) values (?,?)"]
Params:[(John 0,13),(John 1,14),(John 2,15),(John 3,16),(John 4,17),(John 5,18),(John 6,19),(John 7,20),(John 8,21),(John 9,22)]
2026-05-27T23:21:39.010+03:00 DEBUG 49079 --- [SpringKotlinOracle] [ main] n.t.d.l.l.SLF4JQueryLoggingListener :
Name:dataSource, Connection:7, Time:0, Success:True
Type:Prepared, Batch:True, QuerySize:1, BatchSize:2
Query:["insert into roles (name,id) values (?,?)"]
Params:[(John 10,23),(John 11,24)]
GitHub - senocak/Spring-Oracle-Dynamic-Id-Generation: Spring Oracle Dynamic Id Generation
Happy coding.