agentsclimarketplace

Kora database jdbc

Skill kora-projects/kora-skills/plugins/kora-v1/skills/kora-database-jdbc

JDBC relational database integration for Kora. Builds compile-time @Repository interfaces extending JdbcRepository with @Query, @EntityJdbc records, @Table/@Column/@Id mapping, SQL macros (%{return#selects}, %{entity#inserts}, %{entity#where = @id}), @Batch, UpdateCount, @Id-on-method generated identifiers, transactions via JdbcConnectionFactory.inTx(), and custom JdbcResultSetMapper/JdbcRowMapper/JdbcResultColumnMapper/JdbcParameterColumnMapper. Connection pooling is HikariCP, configured under the `db` HOCON/YAML section of JdbcDatabaseConfig. Use when adding a Hikari-backed PostgreSQL/MySQL/Oracle repository to a Kora service, wiring JdbcDatabaseModule, debugging "JdbcRepository not found" graph errors, or choosing repository return signatures.From its SKILL.md

Install
npx -y skills add kora-projects/kora-skills --skill kora-database-jdbc

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • reads credentialsReads from 3 credential sources: `POSTGRES_JDBC_URL` and 2 more.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
  • runs commandsInstructs the agent to run 1 command, including `python scripts/generate_repository.py --entity User --table users --id-type Long --lang java --package com.example.model`.

SKILL.md

12.7 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it

Kora Database JDBC

JDBC-based relational database access (PostgreSQL, MySQL, Oracle) with HikariCP. Repositories are @Repository interfaces whose implementations are generated at compile time by the annotation processor — no reflection, no runtime proxies.

Prefer synchronous repository signatures (Entity, @Nullable Entity, List<Entity>, UpdateCount). The blocking JDBC driver runs on the executor bound to JdbcDatabase; virtual threads or a fixed pool handle blocking efficiently. Reach for CompletionStage/Mono only when a downstream contract requires it.


Quick Start

1. Dependencies (build.gradle)

dependencies {
    koraBom platform("ru.tinkoff.kora:kora-parent:1.2.17")
    annotationProcessor "ru.tinkoff.kora:annotation-processors"  // mandatory: generates *RepositoryImpl

    implementation "ru.tinkoff.kora:database-jdbc"
    implementation "ru.tinkoff.kora:config-hocon"
    implementation "ru.tinkoff.kora:logging-logback"

    implementation "org.postgresql:postgresql:42.7.7"  // JDBC driver is required (not bundled)
}

All ru.tinkoff.kora:* artifacts inherit their version from the kora-parent BOM — never pin them individually.

Kotlin: use ksp "ru.tinkoff.kora:symbol-processors" instead of annotationProcessor, and implementation("...") syntax.

2. Plug the module into @KoraApp

@KoraApp
public interface Application extends
        HoconConfigModule,
        LogbackModule,
        JdbcDatabaseModule { }

JdbcDatabaseModule provides JdbcDatabase, its JdbcConnectionFactory, and the JdbcDatabaseConfig reader bound to the db config section.

3. Define an entity with @EntityJdbc

@EntityJdbc
@Table("entities")
public record Entity(
        @Id @Column("id") Long id,
        @Column("value1") int field1,
        @Column("value2") String value2,
        @Nullable @Column("value3") String value3) {}

@EntityJdbc (from ru.tinkoff.kora.database.jdbc) makes the processor generate an optimized result converter. @Table/@Column/@Id come from ru.tinkoff.kora.database.common.annotation.

4. Repository with SQL macros

@Repository
public interface EntityRepository extends JdbcRepository {

    @Query("SELECT %{return#selects} FROM %{return#table} WHERE id = :id")
    @Nullable
    Entity findById(Long id);

    @Query("SELECT %{return#selects} FROM %{return#table}")
    List<Entity> findAll();

    @Query("INSERT INTO %{entity#inserts}")
    UpdateCount insert(Entity entity);

    @Query("UPDATE %{entity#table} SET %{entity#updates} WHERE %{entity#where = @id}")
    UpdateCount update(Entity entity);

    @Query("DELETE FROM entities WHERE id = :id")
    UpdateCount deleteById(Long id);
}

5. Generated identifier (auto-increment / sequence)

When the database assigns the key, annotate the method with @Id and return the id type, or use RETURNING:

@Query("INSERT INTO %{entity#inserts-= @id}")
@Id
Long insert(Entity entity);          // returns DB-generated key (works for @Batch too)

@Query("INSERT INTO entities(name) VALUES (:entity.name) RETURNING id")
long insertReturning(Entity entity); // explicit RETURNING projection

6. Transactions in a service

@Component
public final class EntityService {
    private final EntityRepository repository;

    public EntityService(EntityRepository repository) {
        this.repository = repository;
    }

    public List<Entity> saveAll(Entity one, Entity two) {
        return repository.getJdbcConnectionFactory().inTx(() -> {
            repository.insert(one);
            repository.insert(two);
            return List.of(one, two);
        });
    }
}

Every repository method called inside the inTx() lambda joins the same transaction; an exception rolls the whole block back. JdbcConnectionFactory is reachable via repository.getJdbcConnectionFactory() or by injecting JdbcConnectionFactory directly.


Configuration (application.conf)

db {
    jdbcUrl = ${POSTGRES_JDBC_URL}   // required, e.g. "jdbc:postgresql://localhost:5432/postgres"
    username = ${POSTGRES_USER}      // required
    password = ${POSTGRES_PASS}      // required
    poolName = "kora"                // required: Hikari pool name
    maxPoolSize = 10
    minIdle = 0
    connectionTimeout = "10s"        // durations are strings, not millisecond numbers
    idleTimeout = "10m"
    maxLifetime = "15m"
    telemetry.logging.enabled = false
    telemetry.metrics.enabled = true
    telemetry.tracing.enabled = true
}

Externalize every credential with ${VAR} / ${?VAR} / ${?VAR:default}. Full key list and YAML form: database-jdbc-config-reference.md.


SQL macros

Macros expand at compile time into SQL the developer could have written by hand. Target a method argument by name or the result via return; separate target and command with #.

MacroExpands toExample result
%{return#selects}column list of the return entityid, value1, value2, value3
%{return#table} / %{entity#table}@Table name (or snake_case class name)entities
%{entity#inserts}full INSERT INTO table(cols) VALUES(:entity...)see below
%{entity#updates}col = :entity.field, ... for SETvalue1 = :entity.field1, ...
%{entity#where = @id}WHERE by the @Id field(s)id = :entity.id
%{id#where}WHERE for a composite-key argument named ida = :id.a AND b = :id.b

Field enumeration after a command: = keeps only the listed fields, -= excludes them; the @id keyword refers to the @Id field(s).

@Query("INSERT INTO %{entity#inserts-= @id}")   // every column except the @Id
@Id Long insert(Entity entity);

@Query("INSERT INTO %{entity#inserts = value1,value2}")  // only these columns
UpdateCount insertPartial(Entity entity);

The only macro commands are table, selects, inserts, updates, where. There is no deletes command — write DELETE FROM ... WHERE ... explicitly.


Repository method signatures

T is the return type, List<T>, Void, or UpdateCount.

SignatureUse
T find(...)row must exist (throws otherwise)
@Nullable T find(...)optional single row — preferred over Optional (no allocation)
Optional<T> find(...)optional single row, Optional flavor
List<T> find(...)zero-or-many (empty list, never null)
UpdateCount write(...)number of affected rows for INSERT/UPDATE/DELETE
void write(...)result not needed
@Id Long insert(...)database-generated identifier
CompletionStage<T>async — requires an Executor bound to JdbcDatabase
Mono<T>reactive — add io.projectreactor:reactor-core and an Executor

Kotlin adds suspend fun ...(): T and T? / Unit returns.


References

TopicFile
@Repository, @Query, macros, batch, inheritance, multiple databasesrepository-pattern-reference.md
@Table/@Column/@Id/@Embedded, naming strategy, type mapping, generated idsentity-mapping-reference.md
inTx(), post-commit/rollback actions, isolation, lockingtransactions-reference.md
JdbcResultSetMapper/JdbcRowMapper/JdbcResultColumnMapper/JdbcParameterColumnMapper, enum/array/JSONBcustom-mappers-reference.md
HikariCP config, drivers, telemetry, YAMLdatabase-jdbc-config-reference.md
HikariCP pool tuning by workload, leak detectionconnection-pool-reference.md
Flyway / Liquibase schema migrationsmigrations-reference.md

Assets

TemplatePurpose
jdbc-entity-single-id.{java,kt}.templateentity with a single-field id
jdbc-entity-composite-id.{java,kt}.templateentity with an @Embedded composite key
jdbc-crud-single-id-repository.{java,kt}.templatefull CRUD repository (single id)
jdbc-crud-composite-id-repository.{java,kt}.templatefull CRUD repository (composite id)
jdbc-crud-abstract-macros-repository.java.template / jdbc-crud-abstract-single-id-macros-repository.kt.templatereusable generic CRUD base interface
jdbc-repository-with-enum-mapper.{java,kt}.templateentity + enum column/parameter mappers
jdbc-repository-with-array-mapper.java.templatePostgreSQL array parameter mapper
jdbc-service-with-transactions.java.template@Component service using inTx()

Generate a starter entity + repository:

python scripts/generate_repository.py --entity User --table users --id-type Long --lang java --package com.example.model

Testing

Use @KoraAppTest with test-junit5 plus a Testcontainers PostgreSQL extension; inject the real repository with @TestComponent and point the config at the container.

@TestcontainersPostgreSQL(mode = ContainerMode.PER_RUN,
        migration = @Migration(engine = Migration.Engines.FLYWAY,
                apply = Migration.Mode.PER_METHOD, drop = Migration.Mode.PER_METHOD))
@KoraAppTest(Application.class)
class EntityRepositoryTest implements KoraAppTestConfigModifier {

    @ConnectionPostgreSQL
    private JdbcConnection connection;

    @TestComponent
    private EntityRepository repository;

    @Override
    public KoraConfigModification config() {
        return KoraConfigModification.ofSystemProperty("POSTGRES_JDBC_URL", connection.params().jdbcUrl())
                .withSystemProperty("POSTGRES_USER", connection.params().username())
                .withSystemProperty("POSTGRES_PASS", connection.params().password());
    }

    @Test
    void insertThenFind() {
        repository.insert(new Entity(null, 1, "two", null));
        assertFalse(repository.findAll().isEmpty());
    }
}

Test dependencies: testImplementation "ru.tinkoff.kora:test-junit5" and testImplementation "io.goodforgod:testcontainers-extensions-postgres:0.13.1".


Common pitfalls

SymptomFix
Graph build: "required dependency JdbcRepository / Entity not found"@KoraApp must extend JdbcDatabaseModule; entity needs @EntityJdbc
*RepositoryImpl not generatedannotation processor missing (annotation-processors / KSP symbol-processors)
Generated id is always nulluse @Id on the method (and exclude it via inserts-= @id) or add RETURNING id
Macro renders literally / failsuse # not . (%{entity#inserts}); only table/selects/inserts/updates/where exist
List<T> parameter treated as one valueannotate with @Batch
Driver ClassNotFoundExceptionadd the JDBC driver dependency (it is not bundled)
Operations outside inTx() not rolled backwrap all related calls in one inTx() block
connectionTimeout = 30000 ignoreddurations are strings: "10s", "10m"
@Column on every field looks mandatoryColumn names default to snake_lower_case — @Column only for non-standard names (see Custom Mappers Advanced)
@Mapping required for custom types@Component mappers are auto-discovered by type (see Custom Mappers Advanced)
@Batch with RETURNING doesn't return rowsUse default method with inTx() for multi-row INSERT…RETURNING (see Custom Mappers Advanced)

Column Mappers

For advanced mapper patterns (auto-discovery, generic enum mappers, @Batch limitations), see Custom Mappers Advanced.


Version compatibility

ComponentVersion
Kora BOM (kora-parent)1.2.17
Java21+
Gradle9+
PostgreSQL driver42.7.x

What ships with it: 24 files

115.4 KB alongside SKILL.md, 1 of them executable

evals/

scripts/

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.