Repository-mønster med Kotlin object og Connection/TransactionalSession extension-funksjoner, DBUtils-transaksjoner og ResultSet-mapping
Install
mkdir -p .claude/skills/repository && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15048" && unzip -o skill.zip -d .claude/skills/repository && rm skill.zipInstalls to .claude/skills/repository
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Repository-mønster med Kotlin object og Connection/TransactionalSession extension-funksjoner, DBUtils-transaksjoner og ResultSet-mappingAbout this skill
Repository & Database Access Patterns
Repository- og databaseaksessmønstre: object-repositories med Connection-extensions, DBUtils-transaksjoner, ResultSet-mapping og feilhåndtering.
All database access goes through
DBUtils.asyncTransaction {}(suspending) orDBUtils.transaction {}(blocking). Repositories are Kotlinobjects with extension functions onConnectionorTransactionalSession.
DataSource
PostgresDataSource is a singleton object using HikariCP. In non-local environments it integrates with Vault for credentials:
object PostgresDataSource {
val dataSource: HikariDataSource by lazy { dataSource() }
fun migrate() {
dataSource(role = postgresConfig.adminUser).use { migrate(it) }
}
fun migrate(dataSource: HikariDataSource) {
Flyway.configure()
.dataSource(dataSource)
.initSql("""SET ROLE "${postgresConfig.adminUser}"""")
.lockRetryCount(-1)
.validateMigrationNaming(true)
.load()
.migrate()
}
}
Transactions via DBUtils
Always use DBUtils.asyncTransaction {} (suspending) or DBUtils.transaction {} (blocking). Never use bare JDBC connections directly.
// Suspending (use in coroutine context / service layer)
dataSource.asyncTransaction { session ->
KravRepository.run { session.insertAllNewKrav(kravLinjer, filnavn) }
}
// Blocking (use in sync contexts)
dataSource.transaction { session ->
KravRepository.run { session.updateStatus(corrId, status) }
}
Repository Pattern
Repositories are Kotlin objects with extension functions on Connection (raw JDBC) or TransactionalSession (kotliquery). Use RepositoryExtensions helpers:
object KravRepository {
fun Connection.getAllUnsentKrav() =
executeSelect(
"""select * from krav where status = ?""",
Status.KRAV_IKKE_SENDT.value,
).toKrav()
fun Connection.updateSentKrav(
corrId: String,
kravidentifikatorSKE: String,
status: String,
) = executeUpdate(
"""update krav set kravidentifikator_ske = ?, status = ?, tidspunkt_sendt = now() where corr_id = ?""",
kravidentifikatorSKE,
status,
corrId,
)
// For kotliquery TransactionalSession (e.g. when returnGeneratedKey is needed)
fun getKravTableIdFromCorrelationId(
tx: TransactionalSession,
corrID: String,
): Long =
tx.single(
queryOf("select id from krav where corr_id = ?", corrID)
.map { row -> row.long("id") }
.asSingle,
) ?: throw IllegalStateException("Krav med corrId $corrID ikke funnet")
}
ResultSet Mapping
Centralise ResultSet → domain mapping in RepositoryMappers.kt using getColumn<T>():
fun ResultSet.toKrav() =
toList {
Krav(
kravId = getColumn("id"),
saksnummerNAV = getColumn("saksnummer_nav"),
status = getColumn("status"),
kravtype = getColumn("kravtype"),
corrId = getColumn("corr_id"),
kravidentifikatorSKE = getColumn("kravidentifikator_ske"),
// ... remaining fields
)
}
private fun <T> ResultSet.toList(mapper: ResultSet.() -> T) =
buildList {
while (next()) { add(mapper()) }
}
Error Handling
Wrap bare Connection usage with useAndHandleErrors:
dataSource.connection.useAndHandleErrors { con ->
con.getAllUnsentKrav()
}
Boundaries
✅ Always
- Use
DBUtils.asyncTransaction {}/DBUtils.transaction {}— never bareConnectionin service code - Use
object+ extension functions for repositories - Map
ResultSet→ domain inRepositoryMappers.ktusinggetColumn<T>() - Wrap
Connectionusage withuseAndHandleErrors
🚫 Never
- Skip Flyway migrations for schema changes
- Use bare JDBC connections directly in service code
- Scatter ResultSet mapping logic outside
RepositoryMappers.kt