Commit 8ab9c7c7 authored by Artemis's avatar Artemis 🐱

Started working on a new article, about Exposed, kotlin

parent 681c24dd
---
title: A small introduction to Exposed
subtitle: a Kotlin DAO library
published: false
---
# A bit of context, before anything else
I've been working on a small Kotlin/Java desktop software for managing some images I had.
I wanted to have an embedded SQLite database to store my selections and such,
but didn't want to bother with raw SQL,
and so I began searching for a proper DAO system.
As for (almost) every project, my main criterias were
- Lightness
- Ease of setup/use
The first answer coming from the Java world in regard of a DAO system is Hibernate.
The thing is... Hibernate is *heavy*, and a real pain to set up if you do it manually.
Also, if I could find a kotlin-based library, it'd be awesome.
# Introducing Exposed
[Exposed](https://github.com/JetBrains/Exposed) is a Kotlin-based DAO library, made to work with generic drivers (unlike Hibernate), and which have been developed by the JetBrains team.
The setup is pretty straightforward, as you only need two things:
- Exposed
- A database driver (here, I'll use SQLite, as it fits my needs)
> Available drivers and their configurations can be found
> [here](https://github.com/JetBrains/Exposed/wiki/DataBase-and-DataSource#datasource).
Basically, your gradle (if used) build file will receive two new dependencies.
```gradle
dependencies {
// Your other dependencies...
compile 'org.jetbrains.exposed:exposed:0.11.2'
compile 'org.xerial:sqlite-jdbc:3.21.0.1'
}
```
"Connecting" to our database is easy, too.
```kotlin
Database.connect("jdbc:sqlite:/path/to/database.db", "org.sqlite.JDBC")
```
You'll note that we don't retrieve the instance, as it is currently managed by the library itself.
To avoid issues with SQLite, e.g. for concurrent work, we'll store this instance into a variable, which we'll have to reference on each request.
```kotlin
object Db {
val db by lazy {
Database.connect("jdbc:sqlite:/path/to/database.db", "org.sqlite.JDBC")
}
}
```
# Building an entity
Now that we've finished configuring our database connection, we can start defining our DAO models.
We have two different types: `Tables` and `Rows`, whose roles are pretty self-explanatory.
As an example, I'll use a dumbed-down data model, taken from my gallery software project.
So, I want to store a reference to every image I have, with an additional field to check if I approved the image (after a dump import, to remove every unwanted image).
The first step is making the DSL for our table, which is comprised of the following infos.
- An integer ID, used as primary key
- A file name, limited to 2048 in length
- A file hash, whose length is 64 (SHA-256 hash)
- A simple boolean
Since our integer id is a pretty standard model, Exposed provides us a helper object, which we directly can extend.
```kotlin
object Images : IntIdTable() {
val fileName = varchar("fileName", 2048)
val fileHash = varchar("fileHash", 64) // SHA256 hash
val approved = bool("approved").default(false)
}
```
The string-values inside type descriptors (`varchar()`, `bool()`, etc.) are column names.
The `.default()` lets us give a default value.
Here, an image is, by default, not approved.
After we have built our table descriptor, it's time to build our row class, which will represent a single row (entity) of our table.
As our table is based on `IntID`, our entity type is an IntEntity (which allows us to leverage the PK column logic).
```kotlin
class Image(id: EntityID<Int>) : IntEntity(id)
```
We'll define this class as the row class tied to our Images table by defining the companion object as the EntityClass.
```kotlin
class Image(id: EntityID<Int>) : IntEntity(id) {
// Defining Image to be the class representing an entity (row) of Images
companion object : IntEntityClass<Image>(Images)
}
```
But that's not yet done, as our Image entity still doesn't know its available attributes.
We have three of them, fileName, fileHash and approved.
```kotlin
class Image(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Image>(Images)
// Defining our attributes
var fileName by Images.fileName
var fileHash by Images.fileHash
var approved by Images.approved
}
```
This `by` operator allows us to directly associate our attribute (left part) to the linked column (right part).
This is all we need to work with our Images table, the wrap-up code is below.
```kotlin
object Images : IntIdTable() {
val fileName = varchar("fileName", 2048)
val fileHash = varchar("fileHash", 64) // SHA256 hash
val approved = bool("approved").default(false)
}
class Image(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Image>(Images)
var fileName by Images.fileName
var fileHash by Images.fileHash
var approved by Images.approved
}
```
# Transactions
Every request is part of a "request group" (a request group can contain one or no request, in which case it'll do nothing).
> The mechanism here is called a transaction and, if you've worked with relational database management systems before, you surely have heard of this mechanism.
> In that sense, I won't dig into details on how it works, but only explain how to work with them in Exposed.
Every Exposed-based request must be done inside a transaction scope.
It's defined as simply as `transaction { /* your work */ }`, but, as SQLite only support two transaction mechanisms, respectively "Serializable" and "Read uncommited", we need to configure our transaction block to use those modes.
Our block then becomes `transaction(opts) {}`.
```kotlin
transaction(Connection.TRANSACTION_SERIALIZABLE, 1) {
// The 1 stands for the repetition attempts count, in case of incident.
}
```
If you remember, we also extracted the database instance into a variable earlier, to use it in case of request.
Now the question arises, how to use it?
It's simple, as we only need to pass it as an addition parameter to transaction.
```kotlin
transaction(Connection.TRANSACTION_SERIALIZABLE, 1, Db.db) {
}
```
# Requests
TODO
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment