What is Exposed and why is it needed?
Exposed is a lightweight but powerful ORM library (Object-Relational Mapping) for the Kotlin programming language, developed by JetBrains. It provides two main approaches to working with databases: DSL (Domain Specific Language) and DAO (Data Access Objects).
Why is Exposed needed?
- Type safety: Unlike raw SQL queries, Exposed uses typed Kotlin constructs, allowing errors to be caught at compile time.
- Conciseness: Code in Exposed is significantly shorter and more readable than equivalent SQL queries or Java code with JPA.
- Performance: Exposed does not use reflection or proxy objects (like Hibernate), making it very fast and predictable.
- Flexibility: You can easily switch between DSL (for complex queries) and DAO (for simple CRUD operations) within the same project.
- Support for multiple databases: PostgreSQL, MySQL, MariaDB, SQLite, H2, Oracle, SQL Server.
Installing Exposed
Exposed is installed via the Gradle or Maven build system. Add the following dependencies to your build.gradle.kts:
dependencies { // Core library implementation("org.jetbrains.exposed:exposed-core:0.44.1") // DSL for queries implementation("org.jetbrains.exposed:exposed-dao:0.44.1") // JDBC driver for your database (example for PostgreSQL) implementation("org.postgresql:postgresql:42.7.1") // HikariCP connection pool (recommended) implementation("com.zaxxer:HikariCP:5.1.0") // SQL logging (optional) implementation("ch.qos.logback:logback-classic:1.4.14")}For Maven (pom.xml):
<dependency> <groupId>org.jetbrains.exposed</groupId> <artifactId>exposed-core</artifactId> <version>0.44.1</version></dependency>Quick start: code example
Let's create a simple task management application (ToDo):
import org.jetbrains.exposed.sql.*import org.jetbrains.exposed.sql.transactions.transactionimport org.jetbrains.exposed.sql.SqlExpressionBuilder.eqimport java.time.LocalDateTime
// 1. Define the tableobject Tasks : Table() { val id = integer("id").autoIncrement() val title = varchar("title", 255) val description = text("description").nullable() val isCompleted = bool("is_completed").default(false) val createdAt = datetime("created_at").default(LocalDateTime.now())
override val primaryKey = PrimaryKey(id)}
// 2. Data class for conveniencedata class Task( val id: Int, val title: String, val description: String?, val isCompleted: Boolean, val createdAt: LocalDateTime)
fun main() { // 3. Connect to the database (H2 in-memory for example) Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
transaction { // 4. Create the table SchemaUtils.create(Tasks)
// 5. Insert data val insertId = Tasks.insert { it[title] = "Learn Exposed" it[description] = "Read the documentation and write an example" } get Tasks.id
println("Inserted task with ID: $insertId")
// 6. Select all tasks val allTasks = Tasks.selectAll().map { row -> Task( id = row[Tasks.id], title = row[Tasks.title], description = row[Tasks.description], isCompleted = row[Tasks.isCompleted], createdAt = row[Tasks.createdAt] ) } println("All tasks: $allTasks")
// 7. Update a task Tasks.updat