前回の「KotlinでJersey+JPAを使ったRESTサーバーを作る」のJPA部分を、
JetBrains社製の Kotlin SQL Libraryである Exposed に置き換えてみたいたと思います。
DB関係の部分しか変更がないのでそこだけ書きます。
REST APIの部分は前回のを見てください。
依存関係の変更
まずGradleの依存関係ですが、以下の変更前の部分を消して、変更後にかかれている所を追加してください。
dependencies {
...
compile "org.springframework.boot:spring-boot-starter-data-jpa:$spring_version"
...
}
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
が使えるようにしています。
@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マッピングされるモデルの定義になります。
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
を使った実装にします。
/**
* 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
は1回Select
して取得しないとできないっぽいです。
DSLを使ったCRUDの実装
DAO
ではなく、DSL
を使って同じように実装してみます。
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
を足して2で割った感じで、DAO
はHibernate
とかかな・・・
JOIN
したりとかもう少し深掘りが必要ですが、割と簡単に扱えたのでKotlin
でO/Rマッパー
探しているなら選択肢に十分入るかと思います。