LoginSignup
6
3

More than 5 years have passed since last update.

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

Posted at

前回の「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マッパー探しているなら選択肢に十分入るかと思います。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3