Convert Figma logo to code with AI

JetBrains logoExposed

Kotlin SQL Framework

8,310
692
8,310
168

Top Related Projects

SQLDelight - Generates typesafe Kotlin APIs from SQL

3,136

requery - modern SQL based query & persistence for Java / Kotlin / Android

Java & Kotlin Async DataBase Driver for MySQL and PostgreSQL written in Kotlin

6,137

jOOQ is the best way to write SQL in Java

Quick Overview

JetBrains/Exposed is an open-source ORM (Object-Relational Mapping) framework for Kotlin. It provides a DSL for type-safe SQL queries and schema definitions, making database interactions more intuitive and less error-prone for Kotlin developers.

Pros

  • Type-safe SQL DSL, reducing runtime errors and improving code maintainability
  • Seamless integration with Kotlin language features, including coroutines
  • Support for multiple database dialects (e.g., MySQL, PostgreSQL, SQLite)
  • Lightweight and performant compared to some other ORM solutions

Cons

  • Learning curve for developers new to Kotlin or ORM concepts
  • Limited documentation compared to more established ORM frameworks
  • Fewer advanced features compared to some full-featured ORM solutions
  • Potential performance overhead for very complex queries or large-scale applications

Code Examples

  1. Defining a table:
object Users : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 50)
    val email = varchar("email", 100)

    override val primaryKey = PrimaryKey(id)
}
  1. Inserting data:
transaction {
    Users.insert {
        it[name] = "John Doe"
        it[email] = "john@example.com"
    }
}
  1. Querying data:
transaction {
    Users.select { Users.name like "J%" }
        .forEach { println("${it[Users.name]} - ${it[Users.email]}") }
}
  1. Updating data:
transaction {
    Users.update({ Users.id eq 1 }) {
        it[email] = "newemail@example.com"
    }
}

Getting Started

  1. Add the dependency to your build.gradle.kts:
dependencies {
    implementation("org.jetbrains.exposed:exposed-core:0.40.1")
    implementation("org.jetbrains.exposed:exposed-dao:0.40.1")
    implementation("org.jetbrains.exposed:exposed-jdbc:0.40.1")
}
  1. Connect to the database and perform operations:
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

fun main() {
    Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")

    transaction {
        SchemaUtils.create(Users)

        val userId = Users.insert {
            it[name] = "Alice"
            it[email] = "alice@example.com"
        } get Users.id

        println("Inserted user id: $userId")

        val user = Users.select { Users.id eq userId }.single()
        println("User: ${user[Users.name]} (${user[Users.email]})")
    }
}

Competitor Comparisons

SQLDelight - Generates typesafe Kotlin APIs from SQL

Pros of SQLDelight

  • Type-safe SQL queries with compile-time verification
  • Generates Kotlin code from SQL statements, ensuring consistency between database and application
  • Supports multiplatform projects, including iOS and Android

Cons of SQLDelight

  • Limited to SQLite databases
  • Steeper learning curve for developers unfamiliar with raw SQL
  • Less flexibility in query construction compared to ORM-style APIs

Code Comparison

SQLDelight:

CREATE TABLE Person (
  id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL
);

selectAll:
SELECT * FROM Person;

Exposed:

object Persons : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 50)
    override val primaryKey = PrimaryKey(id)
}

val allPersons = Persons.selectAll()

SQLDelight generates Kotlin code from SQL statements, while Exposed uses a DSL to define tables and queries. SQLDelight offers stronger type safety and compile-time verification, but Exposed provides a more Kotlin-idiomatic approach to database interactions. SQLDelight is better suited for projects requiring SQLite compatibility across multiple platforms, while Exposed offers more flexibility in terms of supported databases and query construction.

3,136

requery - modern SQL based query & persistence for Java / Kotlin / Android

Pros of requery

  • Supports both SQL and NoSQL databases, offering more flexibility
  • Provides a powerful code generation tool for entity classes
  • Offers reactive extensions for asynchronous operations

Cons of requery

  • Less Kotlin-specific features compared to Exposed
  • Steeper learning curve due to its broader scope
  • Smaller community and fewer resources compared to Exposed

Code Comparison

Exposed query example:

val query = Users.select { Users.age greater 18 }
    .orderBy(Users.name)
    .limit(10)

requery query example:

val query = select(Person::class)
    .where(Person::age.gt(18))
    .orderBy(Person::name.asc())
    .limit(10)

Both libraries offer a type-safe DSL for querying databases, but Exposed's syntax is more concise and Kotlin-idiomatic. requery's syntax is closer to SQL, which may be more familiar to developers with SQL background.

Exposed is specifically designed for Kotlin and integrates well with other Kotlin libraries, while requery is a Java library with Kotlin support. This makes Exposed more natural to use in Kotlin projects, but requery might be a better choice for mixed Java/Kotlin codebases or when working with multiple database types.

Java & Kotlin Async DataBase Driver for MySQL and PostgreSQL written in Kotlin

Pros of jasync-sql

  • Asynchronous, non-blocking database operations
  • Supports multiple database types (MySQL, PostgreSQL)
  • Lightweight and focused on performance

Cons of jasync-sql

  • Less abstraction and higher-level features compared to Exposed
  • Steeper learning curve for developers used to ORM-style APIs
  • Smaller community and ecosystem

Code Comparison

Exposed (ORM-style query):

val users = Users.select { Users.age greaterEq 18 }
    .orderBy(Users.name)
    .limit(10)

jasync-sql (raw SQL query):

connection.sendPreparedStatement("""
    SELECT * FROM users
    WHERE age >= ?
    ORDER BY name
    LIMIT 10
""", listOf(18))

Exposed provides a more Kotlin-idiomatic, type-safe approach with its DSL, while jasync-sql offers lower-level control and potentially better performance for complex queries. Exposed is generally easier for developers familiar with ORMs, while jasync-sql may be preferred for those who want more direct database access and async capabilities.

6,137

jOOQ is the best way to write SQL in Java

Pros of jOOQ

  • Supports a wide range of databases, including Oracle, SQL Server, and PostgreSQL
  • Offers type-safe SQL querying with code generation
  • Provides advanced SQL features like window functions and common table expressions

Cons of jOOQ

  • Steeper learning curve due to its comprehensive API
  • Commercial licensing required for some databases
  • More verbose syntax compared to Exposed's DSL

Code Comparison

Exposed query:

val query = Users.select { Users.age greaterEq 18 }
    .orderBy(Users.name to SortOrder.ASC)
    .limit(10)

jOOQ query:

Result<Record> result = create.select()
    .from(USERS)
    .where(USERS.AGE.greaterOrEqual(18))
    .orderBy(USERS.NAME.asc())
    .limit(10)
    .fetch();

Key Differences

  • Exposed is Kotlin-first, while jOOQ is Java-based with Kotlin support
  • jOOQ offers more advanced SQL features and database-specific optimizations
  • Exposed provides a more concise syntax, especially for simple queries
  • jOOQ has a larger community and more extensive documentation

Both libraries aim to provide type-safe database access, but they cater to different use cases and preferences. Exposed is often favored for Kotlin projects and simpler database interactions, while jOOQ is chosen for complex SQL operations and multi-database support.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Exposed logo


JetBrains team project Slack Channel TC Build status Maven Central GitHub License

Welcome to Exposed, an ORM framework for Kotlin.

Exposed is a lightweight SQL library on top of a JDBC driver for the Kotlin language. Exposed has two flavors of database access: typesafe SQL wrapping DSL and lightweight Data Access Objects (DAO).

With Exposed, you have two options for database access: wrapping DSL and a lightweight DAO. Our official mascot is the cuttlefish, which is well-known for its outstanding mimicry ability that enables it to blend seamlessly into any environment. Similar to our mascot, Exposed can be used to mimic a variety of database engines, which helps you to build applications without dependencies on any specific database engine and to switch between them with very little or no changes.

Supported Databases

  • H2 (versions 2.x; 1.x version is deprecated and will be removed in future releases)
  • MariaDB
  • MySQL
  • Oracle
  • Postgres (Also, PostgreSQL using the pgjdbc-ng JDBC driver)
  • MicrosoftSQLServer
  • SQLite

Dependencies

Maven Central configuration

Releases of Exposed are available in the Maven Central repository. You can declare this repository in your build script as follows:

Gradle Groovy and Kotlin DSL

Warning: You might need to set your Kotlin JVM target to 8, and when using Spring to 17, in order for this to work properly:

repositories {
    // Versions after 0.30.1
    // Versions before 0.30.1 is unavailable for now
    mavenCentral()
}

Maven

The Maven Central repository is enabled by default for Maven users.

Exposed modules

Exposed consists of the following modules:

  • exposed-core - base module, which contains both DSL api along with mapping
  • exposed-crypt - provides additional column types to store encrypted data in DB and encode/decode it on client-side
  • exposed-dao - DAO api
  • exposed-java-time - date-time extensions based on Java8 Time API
  • exposed-jdbc - transport level implementation based on Java JDBC API
  • exposed-jodatime - date-time extensions based on JodaTime library
  • exposed-json - JSON and JSONB data type extensions
  • exposed-kotlin-datetime - date-time extensions based on kotlinx-datetime
  • exposed-money - extensions to support MonetaryAmount from "javax.money:money-api"
  • exposed-spring-boot-starter - a starter for Spring Boot to utilize Exposed as the ORM instead of Hibernate

<dependencies>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-core</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-crypt</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-dao</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-java-time</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-jdbc</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-jodatime</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-json</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-kotlin-datetime</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-money</artifactId>
        <version>0.55.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.exposed</groupId>
        <artifactId>exposed-spring-boot-starter</artifactId>
        <version>0.55.0</version>
    </dependency>
</dependencies>

Gradle Groovy

dependencies {
    implementation 'org.jetbrains.exposed:exposed-core:0.55.0'
    implementation 'org.jetbrains.exposed:exposed-crypt:0.55.0'
    implementation 'org.jetbrains.exposed:exposed-dao:0.55.0'
    implementation 'org.jetbrains.exposed:exposed-jdbc:0.55.0'
    
    implementation 'org.jetbrains.exposed:exposed-jodatime:0.55.0'
    // or
    implementation 'org.jetbrains.exposed:exposed-java-time:0.55.0'
    // or
    implementation 'org.jetbrains.exposed:exposed-kotlin-datetime:0.55.0'
    
    implementation 'org.jetbrains.exposed:exposed-json:0.55.0'
    implementation 'org.jetbrains.exposed:exposed-money:0.55.0'
    implementation 'org.jetbrains.exposed:exposed-spring-boot-starter:0.55.0'
}

Gradle Kotlin DSL

In build.gradle.kts:

val exposedVersion: String by project
dependencies {
    implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-crypt:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
    
    implementation("org.jetbrains.exposed:exposed-jodatime:$exposedVersion")
    // or
    implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
    // or
    implementation("org.jetbrains.exposed:exposed-kotlin-datetime:$exposedVersion")
    
    implementation("org.jetbrains.exposed:exposed-json:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-money:$exposedVersion")
    implementation("org.jetbrains.exposed:exposed-spring-boot-starter:$exposedVersion")
}

and in gradle.properties

exposedVersion=0.55.0

Samples

Check out the samples for a quick start.

Links

Currently, Exposed is available for maven/gradle builds. Check the Maven Central and read Getting Started to get an insight on setting up Exposed.

For more information visit the links below:

Filing issues

Please note that we are moving away from GitHub Issues for reporting of bugs and features. Please log any new requests on YouTrack. You must be logged in to view and log issues, otherwise you will be met with a 404.

Community

Do you have questions? Feel free to request an invitation for the kotlinlang slack and join the project conversation at our #exposed channel.

Pull requests

We actively welcome your pull requests. However, linking your work to an existing issue is preferred.

  • Fork the repo and create your branch from main.
  • Name your branch something that is descriptive to the work you are doing. i.e. adds-new-thing.
  • If you've added code that should be tested, add tests and ensure the test suite passes.
  • Make sure you address any lint warnings.
  • If you make the existing code better, please let us know in your PR description.

See the contribution guidelines for more details.

Examples

SQL DSL

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.like
import org.jetbrains.exposed.sql.transactions.transaction

object Users : Table() {
    val id: Column<String> = varchar("id", 10)
    val name: Column<String> = varchar("name", length = 50)
    val cityId: Column<Int?> = (integer("city_id") references Cities.id).nullable()

    override val primaryKey = PrimaryKey(id, name = "PK_User_ID") // name is optional here
}

object Cities : Table() {
    val id: Column<Int> = integer("id").autoIncrement()
    val name: Column<String> = varchar("name", 50)

    override val primaryKey = PrimaryKey(id, name = "PK_Cities_ID")
}

fun main() {
    Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver", user = "root", password = "")

    transaction {
        addLogger(StdOutSqlLogger)

        SchemaUtils.create(Cities, Users)

        val saintPetersburgId = Cities.insert {
            it[name] = "St. Petersburg"
        } get Cities.id

        val munichId = Cities.insert {
            it[name] = "Munich"
        } get Cities.id

        val pragueId = Cities.insert {
            it.update(name, stringLiteral("   Prague   ").trim().substring(1, 2))
        }[Cities.id]

        val pragueName = Cities.selectAll().where { Cities.id eq pragueId }.single()[Cities.name]
        println("pragueName = $pragueName")

        Users.insert {
            it[id] = "andrey"
            it[name] = "Andrey"
            it[Users.cityId] = saintPetersburgId
        }

        Users.insert {
            it[id] = "sergey"
            it[name] = "Sergey"
            it[Users.cityId] = munichId
        }

        Users.insert {
            it[id] = "eugene"
            it[name] = "Eugene"
            it[Users.cityId] = munichId
        }

        Users.insert {
            it[id] = "alex"
            it[name] = "Alex"
            it[Users.cityId] = null
        }

        Users.insert {
            it[id] = "smth"
            it[name] = "Something"
            it[Users.cityId] = null
        }

        Users.update({ Users.id eq "alex" }) {
            it[name] = "Alexey"
        }

        Users.deleteWhere{ Users.name like "%thing" }

        println("All cities:")

        for (city in Cities.selectAll()) {
            println("${city[Cities.id]}: ${city[Cities.name]}")
        }

        println("Manual join:")
        
        (Users innerJoin Cities)
            .select(Users.name, Cities.name)
            .where {
                (Users.id.eq("andrey") or Users.name.eq("Sergey")) and
                    Users.id.eq("sergey") and Users.cityId.eq(Cities.id)
            }.forEach { 
                println("${it[Users.name]} lives in ${it[Cities.name]}") 
            }

        println("Join with foreign key:")

        (Users innerJoin Cities)
            .select(Users.name, Users.cityId, Cities.name)
            .where { Cities.name.eq("St. Petersburg") or Users.cityId.isNull() }
            .forEach { 
                if (it[Users.cityId] != null) { 
                    println("${it[Users.name]} lives in ${it[Cities.name]}") 
                } 
                else { 
                    println("${it[Users.name]} lives nowhere") 
                } 
            }

        println("Functions and group by:")

        ((Cities innerJoin Users)
            .select(Cities.name, Users.id.count())
            .groupBy(Cities.name)
            ).forEach {
                val cityName = it[Cities.name]
                val userCount = it[Users.id.count()]

                if (userCount > 0) {
                    println("$userCount user(s) live(s) in $cityName")
                } else {
                    println("Nobody lives in $cityName")
                }
            }

        SchemaUtils.drop(Users, Cities)
    }
}

Generated SQL:

    SQL: CREATE TABLE IF NOT EXISTS Cities (id INT AUTO_INCREMENT, name VARCHAR(50) NOT NULL, CONSTRAINT PK_Cities_ID PRIMARY KEY (id))
    SQL: CREATE TABLE IF NOT EXISTS Users (id VARCHAR(10), name VARCHAR(50) NOT NULL, city_id INT NULL, CONSTRAINT PK_User_ID PRIMARY KEY (id), CONSTRAINT FK_Users_city_id__ID FOREIGN KEY (city_id) REFERENCES Cities(id) ON DELETE RESTRICT ON UPDATE RESTRICT)
    SQL: INSERT INTO Cities (name) VALUES ('St. Petersburg')
    SQL: INSERT INTO Cities (name) VALUES ('Munich')
    SQL: INSERT INTO Cities (name) VALUES (SUBSTRING(TRIM('   Prague   '), 1, 2))
    SQL: SELECT Cities.id, Cities.name FROM Cities WHERE Cities.id = 3
    pragueName = Pr
    SQL: INSERT INTO Users (id, name, city_id) VALUES ('andrey', 'Andrey', 1)
    SQL: INSERT INTO Users (id, name, city_id) VALUES ('sergey', 'Sergey', 2)
    SQL: INSERT INTO Users (id, name, city_id) VALUES ('eugene', 'Eugene', 2)
    SQL: INSERT INTO Users (id, name, city_id) VALUES ('alex', 'Alex', NULL)
    SQL: INSERT INTO Users (id, name, city_id) VALUES ('smth', 'Something', NULL)
    SQL: UPDATE Users SET name='Alexey' WHERE Users.id = 'alex'
    SQL: DELETE FROM Users WHERE Users.name LIKE '%thing'
    All cities:
    SQL: SELECT Cities.id, Cities.name FROM Cities
    1: St. Petersburg
    2: Munich
    3: Pr
    Manual join:
    SQL: SELECT Users.name, Cities.name FROM Users INNER JOIN Cities ON Cities.id = Users.city_id WHERE ((Users.id = 'andrey') or (Users.name = 'Sergey')) and (Users.id = 'sergey') and (Users.city_id = Cities.id)
    Sergey lives in Munich
    Join with foreign key:
    SQL: SELECT Users.name, Users.city_id, Cities.name FROM Users INNER JOIN Cities ON Cities.id = Users.city_id WHERE (Cities.name = 'St. Petersburg') or (Users.city_id IS NULL)
    Andrey lives in St. Petersburg
    Functions and group by:
    SQL: SELECT Cities.name, COUNT(Users.id) FROM Cities INNER JOIN Users ON Cities.id = Users.city_id GROUP BY Cities.name
    1 user(s) live(s) in St. Petersburg
    2 user(s) live(s) in Munich
    SQL: DROP TABLE IF EXISTS Users
    SQL: DROP TABLE IF EXISTS Cities

DAO

import org.jetbrains.exposed.dao.*
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

object Users : IntIdTable() {
    val name = varchar("name", 50).index()
    val city = reference("city", Cities)
    val age = integer("age")
}

object Cities: IntIdTable() {
    val name = varchar("name", 50)
}

class User(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<User>(Users)

    var name by Users.name
    var city by City referencedOn Users.city
    var age by Users.age
}

class City(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<City>(Cities)

    var name by Cities.name
    val users by User referrersOn Users.city
}

fun main() {
    Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver", user = "root", password = "")

    transaction {
        addLogger(StdOutSqlLogger)

        SchemaUtils.create(Cities, Users)

        val stPete = City.new {
            name = "St. Petersburg"
        }

        val munich = City.new {
            name = "Munich"
        }

        User.new {
            name = "a"
            city = stPete
            age = 5
        }

        User.new {
            name = "b"
            city = stPete
            age = 27
        }

        User.new {
            name = "c"
            city = munich
            age = 42
        }

        println("Cities: ${City.all().joinToString { it.name }}")
        println("Users in ${stPete.name}: ${stPete.users.joinToString { it.name }}")
        println("Adults: ${User.find { Users.age greaterEq 18 }.joinToString { it.name }}")
    }
}

Generated SQL:

    SQL: CREATE TABLE IF NOT EXISTS Cities (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL)
    SQL: CREATE TABLE IF NOT EXISTS Users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, city INT NOT NULL, age INT NOT NULL, CONSTRAINT FK_Users_city__ID FOREIGN KEY (city) REFERENCES Cities(id) ON DELETE RESTRICT ON UPDATE RESTRICT)
    SQL: CREATE INDEX Users_name ON Users (name)
    SQL: INSERT INTO Cities (name) VALUES ('St. Petersburg')
    SQL: INSERT INTO Cities (name) VALUES ('Munich')
    SQL: SELECT Cities.id, Cities.name FROM Cities
    Cities: St. Petersburg, Munich
    SQL: INSERT INTO Users (name, city, age) VALUES ('a', 1, 5)
    SQL: INSERT INTO Users (name, city, age) VALUES ('b', 1, 27)
    SQL: INSERT INTO Users (name, city, age) VALUES ('c', 2, 42)
    SQL: SELECT Users.id, Users.name, Users.city, Users.age FROM Users WHERE Users.city = 1
    Users in St. Petersburg: a, b
    SQL: SELECT Users.id, Users.name, Users.city, Users.age FROM Users WHERE Users.age >= 18
    Adults: b, c

Contributing

Please see the contribution guide before contributing.

By contributing to the Exposed project, you agree that your contributions will be licensed under Apache License, Version 2.0.