---
title: "Exposed 1.0: ORM Kotlin com R2DBC e API Estável | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/exposed-1-0-r2dbc-kotlin-2026/"
markdown_url: "https://kotlin.dev.br/blog/exposed-1-0-r2dbc-kotlin-2026.MD"
description: "Exposed 1.0 chegou com suporte a R2DBC, API estável e melhorias de performance. Veja como migrar e usar acesso reativo a banco de dados em Kotlin."
date: "2026-04-10"
author: "Karina Melo"
---

# Exposed 1.0: ORM Kotlin com R2DBC e API Estável | Kotlin Brasil

Exposed 1.0 chegou com suporte a R2DBC, API estável e melhorias de performance. Veja como migrar e usar acesso reativo a banco de dados em Kotlin.


Depois de anos em desenvolvimento, o **Exposed 1.0** finalmente chegou — e é uma release que muda o jogo para quem trabalha com banco de dados em Kotlin. O framework SQL da JetBrains agora oferece **suporte oficial a R2DBC**, uma **API estável com garantia de compatibilidade**, e melhorias significativas de performance.

Se você já conhece o Exposed pelo nosso [tutorial completo do framework](/blog/kotlin-exposed-orm-framework-sql/), prepare-se: a versão 1.0 traz tudo que a comunidade vinha pedindo. Vamos ver o que mudou na prática.

## O que há de novo no Exposed 1.0?

### 1. Suporte a R2DBC (Reactive Relational Database Connectivity)

Essa era a feature mais pedida pela comunidade. Até a versão 0.x, o Exposed trabalhava exclusivamente com **JDBC**, o que significava acesso bloqueante ao banco. Agora, com o módulo `exposed-r2dbc`, você pode usar drivers reativos para acesso não-bloqueante.

Os bancos suportados via R2DBC são:

| Banco | Driver R2DBC |
|-------|-------------|
| PostgreSQL | r2dbc-postgresql |
| MySQL | r2dbc-mysql |
| MariaDB | r2dbc-mariadb |
| H2 | r2dbc-h2 |
| Oracle | r2dbc-oracle |
| SQL Server | r2dbc-mssql |

### 2. API estável

Com a versão 1.0, a JetBrains garante que **não haverá breaking changes até a próxima major release**. Isso significa que você pode adotar o Exposed em produção com confiança de que atualizações menores não vão quebrar seu código.

### 3. Melhorias de performance

A 1.0 traz otimizações internas no processamento de queries e no gerenciamento de conexões, resultando em menor overhead por transação.

## Setup com R2DBC

Primeiro, adicione as dependências no `build.gradle.kts`:

```kotlin
// build.gradle.kts
plugins {
    kotlin("jvm") version "2.3.20"
}

repositories {
    mavenCentral()
}

dependencies {
    // Módulo R2DBC do Exposed
    implementation("org.jetbrains.exposed:exposed-r2dbc:1.0.0")

    // Driver R2DBC para PostgreSQL
    implementation("org.postgresql:r2dbc-postgresql:1.0.7.RELEASE")

    // Coroutines (necessário para R2DBC)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.10.1")
}
```

Se quiser manter o acesso JDBC tradicional junto com R2DBC, basta adicionar ambos os módulos — eles coexistem sem conflito. Para uma aplicação web completa, use o [tutorial de Ktor com Exposed](/tutoriais/kotlin-ktor-tutorial/) como base e depois substitua a camada JDBC por R2DBC quando fizer sentido.

## Conectando com R2DBC

A conexão R2DBC usa a classe `R2dbcDatabase`:

```kotlin
import org.jetbrains.exposed.sql.r2dbc.R2dbcDatabase
import org.jetbrains.exposed.sql.r2dbc.R2dbcDatabaseConfig
import io.r2dbc.spi.ConnectionFactoryOptions
import io.r2dbc.spi.IsolationLevel

val database = R2dbcDatabase.connect(
    url = "r2dbc:postgresql://localhost:5432/kotlinbrasil",
    databaseConfig = R2dbcDatabaseConfig {
        defaultMaxAttempts = 3
        defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED
        connectionFactoryOptions {
            option(ConnectionFactoryOptions.USER, "dev_kotlin")
            option(ConnectionFactoryOptions.PASSWORD, "senha_segura")
        }
    }
)
```

Note que a URL segue o padrão `r2dbc:driver://host:porta/banco`, diferente do `jdbc:` tradicional.

## Definindo tabelas (mesmo esquema de antes)

A boa notícia é que a **definição de tabelas não muda**. Se você já tem tabelas definidas com Exposed DSL, elas funcionam tanto com JDBC quanto com R2DBC:

```kotlin
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.javatime.datetime
import java.time.LocalDateTime

object Desenvolvedores : Table("desenvolvedores") {
    val id = integer("id").autoIncrement()
    val nome = varchar("nome", 100)
    val linguagem = varchar("linguagem", 50).default("Kotlin")
    val senioridade = varchar("senioridade", 20)
    val salario = decimal("salario", 10, 2)
    val criadoEm = datetime("criado_em").defaultExpression(CurrentDateTime)

    override val primaryKey = PrimaryKey(id)
}

object Projetos : Table("projetos") {
    val id = integer("id").autoIncrement()
    val nome = varchar("nome", 200)
    val descricao = text("descricao")
    val desenvolvedorId = integer("desenvolvedor_id").references(Desenvolvedores.id)
    val ativo = bool("ativo").default(true)

    override val primaryKey = PrimaryKey(id)
}
```

Se termos como `Table`, `varchar` ou `autoIncrement` parecem novos, confira nosso [glossário de termos Kotlin](/glossario/) e o tutorial de [classes e objetos](/tutoriais/classes-e-objetos-kotlin/).

## CRUD reativo com R2DBC

Agora vem a parte interessante: todas as operações com R2DBC são **suspend functions**, integrando perfeitamente com [coroutines do Kotlin](/blog/coroutines-kotlin/).

### Criando tabelas e inserindo dados

```kotlin
import org.jetbrains.exposed.sql.r2dbc.suspendTransaction
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.batchInsert

suspend fun inicializarBanco() {
    suspendTransaction(db = database) {
        SchemaUtils.create(Desenvolvedores, Projetos)
    }
}

suspend fun inserirDesenvolvedores() {
    suspendTransaction(db = database) {
        // Inserção individual
        Desenvolvedores.insert {
            it[nome] = "Ana Silva"
            it[linguagem] = "Kotlin"
            it[senioridade] = "senior"
            it[salario] = 22000.toBigDecimal()
        }

        // Inserção em lote — mais eficiente
        val devs = listOf(
            Triple("Carlos Lima", "pleno", 12000),
            Triple("Maria Santos", "junior", 5500),
            Triple("Pedro Costa", "senior", 20000),
        )

        Desenvolvedores.batchInsert(devs) { (nome, nivel, sal) ->
            this[Desenvolvedores.nome] = nome
            this[Desenvolvedores.senioridade] = nivel
            this[Desenvolvedores.salario] = sal.toBigDecimal()
        }
    }
}
```

### Consultas reativas

```kotlin
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.andWhere

suspend fun buscarDevsSenior(): List<String> {
    return suspendTransaction(db = database) {
        Desenvolvedores
            .selectAll()
            .where { Desenvolvedores.senioridade eq "senior" }
            .andWhere { Desenvolvedores.salario greaterEq 15000.toBigDecimal() }
            .map { row -> "${row[Desenvolvedores.nome]} - R$ ${row[Desenvolvedores.salario]}" }
    }
}

suspend fun buscarDevsComProjetos(): List<Pair<String, String>> {
    return suspendTransaction(db = database) {
        (Desenvolvedores innerJoin Projetos)
            .selectAll()
            .where { Projetos.ativo eq true }
            .map { row ->
                row[Desenvolvedores.nome] to row[Projetos.nome]
            }
    }
}
```

A diferença principal para o JDBC é o uso de `suspendTransaction` em vez de `transaction`. O código interno é praticamente idêntico.

### Atualização e exclusão

```kotlin
import org.jetbrains.exposed.sql.update
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq

suspend fun promoverDev(nome: String, novoSalario: Int) {
    suspendTransaction(db = database) {
        Desenvolvedores.update(
            where = { Desenvolvedores.nome eq nome }
        ) {
            it[senioridade] = "senior"
            it[salario] = novoSalario.toBigDecimal()
        }
    }
}

suspend fun removerDevsInativos() {
    suspendTransaction(db = database) {
        // Remove projetos inativos primeiro (integridade referencial)
        Projetos.deleteWhere { Projetos.ativo eq false }
    }
}
```

## Integração com Spring Boot

O Exposed 1.0 trouxe melhorias significativas para quem usa [Spring Boot com Kotlin](/blog/kotlin-spring-boot/). Uma das mais impactantes é que agora você pode usar a DSL do Exposed diretamente em métodos anotados com `@Transactional`, sem precisar envolver o código em blocos `transaction { }` explícitos:

```kotlin
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.jetbrains.exposed.sql.selectAll

@Service
class DesenvolvedorService {

    @Transactional(readOnly = true)
    fun listarTodos(): List<DesenvolvedorDTO> {
        return Desenvolvedores
            .selectAll()
            .map { row ->
                DesenvolvedorDTO(
                    id = row[Desenvolvedores.id],
                    nome = row[Desenvolvedores.nome],
                    senioridade = row[Desenvolvedores.senioridade],
                    salario = row[Desenvolvedores.salario]
                )
            }
    }

    @Transactional
    fun atualizarSalario(id: Int, novoSalario: Int) {
        Desenvolvedores.update(
            where = { Desenvolvedores.id eq id }
        ) {
            it[salario] = novoSalario.toBigDecimal()
        }
    }
}

data class DesenvolvedorDTO(
    val id: Int,
    val nome: String,
    val senioridade: String,
    val salario: java.math.BigDecimal
)
```

O Exposed 1.0 também suporta **GraalVM native image**, o que permite compilar seu aplicativo Spring Boot com Exposed para uma imagem nativa com startup em milissegundos.

## Migrando da versão 0.x para 1.0

Se você já usa o Exposed em projetos existentes, a migração é tranquila na maioria dos casos. Os principais pontos de atenção:

```kotlin
// ANTES (0.61.0) - pacote antigo
import org.jetbrains.exposed.sql.transactions.transaction

// DEPOIS (1.0.0) - mesmo pacote, mas para R2DBC use:
import org.jetbrains.exposed.sql.r2dbc.suspendTransaction

// ANTES - Coordenadas Maven antigas
// org.jetbrains.exposed:exposed-core:0.61.0

// DEPOIS - Coordenadas Maven novas
// org.jetbrains.exposed:exposed-core:1.0.0
// org.jetbrains.exposed:exposed-r2dbc:1.0.0 (novo módulo)
```

A JetBrains disponibiliza um [guia de migração completo](https://www.jetbrains.com/help/exposed/about.html) na documentação oficial.

## JDBC vs R2DBC: quando usar cada um?

Essa é uma dúvida comum. Aqui vai um resumo prático:

| Cenário | Recomendação |
|---------|-------------|
| API REST com [Spring Boot](/guias/guia-kotlin-backend-spring/) | JDBC (mais simples, suficiente para a maioria dos casos) |
| API com alta concorrência | R2DBC (menos threads bloqueadas) |
| Aplicação com [Ktor](/guias/guia-kotlin-backend-ktor/) e coroutines | R2DBC (integração natural com suspend) |
| Projeto legado migrando para Kotlin | JDBC (compatibilidade com drivers existentes) |
| Microserviços reativos | R2DBC (alinhado com arquitetura reativa) |

Se você trabalha com [microserviços em Kotlin](/guias/guia-kotlin-microservicos/), o R2DBC é especialmente interessante por não bloquear threads durante I/O de banco.

## Performance: JDBC vs R2DBC

Em testes internos da JetBrains, o R2DBC mostrou vantagens em cenários de alta concorrência:

```kotlin
// Exemplo: processamento paralelo com R2DBC e coroutines
import kotlinx.coroutines.*

suspend fun processarEmParalelo(ids: List<Int>) = coroutineScope {
    ids.map { id ->
        async {
            suspendTransaction(db = database) {
                Desenvolvedores
                    .selectAll()
                    .where { Desenvolvedores.id eq id }
                    .firstOrNull()
            }
        }
    }.awaitAll()
}
```

Com JDBC tradicional, cada consulta paralela ocuparia uma thread do pool. Com R2DBC e coroutines, as mesmas operações compartilham um número muito menor de threads, liberando recursos para outras tarefas.

Para mais sobre concorrência em Kotlin, confira nosso [guia completo de coroutines](/guias/guia-coroutines-completo/) e o artigo sobre [Kotlin Flow](/blog/kotlin-flow/).

## Conclusão

O Exposed 1.0 é um marco importante para o ecossistema Kotlin. Com API estável, suporte a R2DBC e integração melhorada com Spring Boot, ele se consolida como a opção mais idiomática para acesso a banco de dados em Kotlin — sem a complexidade do Hibernate e com toda a segurança de tipos que a linguagem oferece.

Se você está começando com backend em Kotlin, nosso [guia de REST APIs](/guias/guia-kotlin-rest-api/) é um ótimo ponto de partida. Para quem já trabalha com Exposed, a [documentação oficial da versão 1.0](https://www.jetbrains.com/help/exposed/about.html) e o [repositório no GitHub](https://github.com/JetBrains/Exposed) têm tudo que você precisa para migrar.

---

Se você trabalha com backend em múltiplas linguagens, vale comparar como ORMs funcionam em outros ecossistemas: <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go usa GORM e sqlx</a> com uma abordagem mais explícita, enquanto <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python tem SQLAlchemy</a> como referência. O Exposed combina o melhor dos dois mundos: type safety forte com APIs expressivas.

*Quer acompanhar o mercado Kotlin no Brasil? Veja as [vagas abertas](/vagas/) e os [salários por senioridade](/carreira/salarios-kotlin-brasil/).*
