KotlinでJersey+Exposedを使ったRESTサーバーを作る

[前回の「KotlinでJersey+JPAを使ったRESTサーバーを作る」](https://qiita.com/nijuya_o/items/228a8af7fe12b1595c01)のJPA部分を、
JetBrains社製の Kotlin SQL Libraryである [Exposed](https://github.com/JetBrains/Exposed) に置き換えてみたいたと思います。

DB関係の部分しか変更がないのでそこだけ書きます。
REST APIの部分は[前回](https://qiita.com/nijuya_o/items/228a8af7fe12b1595c01)のを見てください。

## 依存関係の変更
まずGradleの依存関係ですが、以下の変更前の部分を消して、変更後にかかれている所を追加してください。

```（変更前）build.gradle
dependencies {
    ...
    compile "org.springframework.boot:spring-boot-starter-data-jpa:$spring_version"
    ...
}
```



```（変更後）build.gradle
repositories {
    。。。
    maven { url { "https://dl.bintray.com/kotlin/exposed" } }
}

dependencies {
    ...
    compile 'org.jetbrains.exposed:exposed:0.10.2'
    compile 'org.jetbrains.exposed:spring-transaction:0.10.2'
    ...
}
```

## DB設定
DBの設定クラスを追加します。`@Transactional`が使えるようにしています。

```DBConfig.kt
@Configuration
@EnableTransactionManagement
class DBConfig(@Autowired val dataSource: DataSource) : TransactionManagementConfigurer {
    init {
        Database.connect(dataSource)
    }
    @Bean
    override fun annotationDrivenTransactionManager() = SpringTransactionManager(dataSource)
}
```
今回 classの前に`open`の修飾子をつけていませんが、普通にやるとエラーになります。
詳細は今回触れませんが、[ここ](https://kotlinlang.org/docs/reference/compiler-plugins.html)に書かれている設定を`build.gradle`に書くことで、`open`を省略できます。


## DB接続用のTableとDAOの定義

O/Rマッピングされるモデルの定義になります。

```UserMode.kt
object UserTable : IntIdTable("user") {
    val name = varchar("name", length = 50)
    val age = integer("age")
    val address = text("address")
}

class UserModel(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<UserModel>(UserTable)

    var name by UserTable.name
    var age by UserTable.age
    var address by UserTable.address
}

```

## DAOを使ったCRUDの実装
前回の`UserRepository`からI/Fを変えずに中身だけ `Exposed`の`DAO`を使った実装にします。

```UserRepositoryImpl.kt

/**
 * UserModelをUserに変換する拡張メソッド
 */
fun UserModel.toEntity() = User(id = id.value, name = name, address = address, age = age)

@Service
class UserRepositoryImpl : UserRepository {

     @Transactional
    override fun get(id: Int): User {
        getWithFamily(id)
        return (UserModel.findById(id)?: throw NotFoundException()).toEntity()
    }

    @Transactional
    override fun getAll() = UserModel.all().map { it.toEntity() }

    @Transactional
    override fun delete(id: Int) {
        UserModel.findById(id)?.delete()
    }

    @Transactional
    override fun create(name: String, age: Int, address: String) =
            UserModel.new {
                this.name = name
                this.age = age
                this.address = address
            }.toEntity()

    @Transactional
    override fun update(user: User) = 
        (UserModel.findById(user.id)?.apply {
            name = user.name
            address = user.address?: "不定"
            age = user.age
        }?: throw NotFoundException()).toEntity()
}

```

`DAO`だと`Update`と`Delete`は１回`Select`して取得しないとできないっぽいです。

## DSLを使ったCRUDの実装
`DAO`ではなく、`DSL`を使って同じように実装してみます。

```UserRepositoryImpl.kt

fun ResultRow.toUser() = User(id = get(UserTable.id).value, name = get(UserTable.name), address = get(UserTable.address), age = get(UserTable.age))

@Service
class UserRepositoryImpl : UserRepository {
    @Transactional
    override fun get(id: Int)
            = UserTable.select { UserTable.id eq id }.map { it.toUser() }.getOrElse(0) { throw  NotFoundException() }

    @Transactional
    override fun getAll() = UserTable.selectAll().map { it.toUser() }

    @Transactional
    override fun delete(id: Int) {
        UserTable.deleteWhere { UserTable.id eq id }
    }

    @Transactional
    override fun create(name: String, age: Int, address: String): User {
        val id = UserTable.insertAndGetId {
            it[UserTable.name] = name
            it[UserTable.age] = age
            it[UserTable.address] = address
        }
        // I/Fをあわせてしまった為余計な取得処理が必要になってしまった・・・
        return get(id.value)
    }

    @Transactional
    override fun update(user: User) : User {
        UserTable.update({ UserTable.id eq user.id }) {
            it[name] = user.name
            it[age] = user.age
            it[address] = user.address ?: "不定"
        }
        // I/Fをあわせてしまった為余計な取得処理が必要になってしまった・・・
        return get(user.id)
    }
}

```
`Delete`や`Update`は`DAO`のときのように先に`Select`しなくて`SQL`を実行することができます。
とはいえ、`Select`などの取得系は`DAO`を使ったほうがスッキリ書けたようにも感じるので、用途にあわせて`DAO`と`DSL`を使い分けるのがいいのかなと感じました。


---

軽く使ってみた限りの感触としては、`DSL`は`S2`と`Doma2`を足して２で割った感じで、`DAO`は`Hibernate`とかかな・・・
`JOIN`したりとかもう少し深掘りが必要ですが、割と簡単に扱えたので`Kotlin`で`O/Rマッパー`探しているなら選択肢に十分入るかと思います。