agentsclimarketplace

Kora database jdbc

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

Agent Skills for Kora Framework — compile-time DI for Java/Kotlin backend development.

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.

One thing to look at

  • 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.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

12.7 KB, 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

Keep looking

Skills are one crate of 328,083. 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.