Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

前回の「KotlinでJersey+JPAを使ったRESTサーバーを作る」のJPA部分を、
JetBrains社製の Kotlin SQL Libraryである Exposed に置き換えてみたいたと思います。

DB関係の部分しか変更がないのでそこだけ書きます。
REST APIの部分は前回のを見てください。

依存関係の変更

まず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の修飾子をつけていませんが、普通にやるとエラーになります。
詳細は今回触れませんが、ここに書かれている設定を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を変えずに中身だけ ExposedDAOを使った実装にします。

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だとUpdateDeleteは1回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)
    }
}

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


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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away