November 13, 2017 に Kotlin 1.1.60 がリリースされました。
だからって訳ではないですが、以下について最初の一歩として簡単な導入部分を見ていきます。
- kotlin-dsl によるGradle プロジェクトのビルド
- Kotlin でサーブレット
- Kotlin の SQLライブラリ Exposed
- Kotlin の Web アプリケーションライブラリ Ktor
Kotlin スクリプトで Hello World
Gradle も 4.3 になり kotlin-dsl もいい感じになってきたので、Kotlin スクリプトで Hello World してみましょう。 ビルドファイルの拡張子を kt に変えるだけで、kotlin でビルドスクリプトが書けるという代物です。
新しめの Gradle なら同梱されているので特別な準備は必要ありません。
プロジェクトを作成します。gradle の init タスクを使います。
$ mkdir app $ cd app $ gradle init --type java-application
Gradle のビルドファイルを Kotlin スクリプトにリネームします。
$ mv build.gradle build.gradle.kts
build.gradle.kts を以下のように変更します。
plugins { application kotlin("jvm") version "1.1.60" } application { mainClassName = "samples.HelloWorldKt" } dependencies { compile(kotlin("stdlib")) } repositories { jcenter() }
application プラグインで指定するクラスは末尾に Kt を付けたものになります。
パッケージを作成して HelloWorld.kt ファイルを作成します。
$ mkdir -p src/main/kotlin/samples $ touch src/main/kotlin/samples/HelloWorld.kt
作成した HelloWorld.kt ファイルを以下のように編集します。
package samples fun main(args: Array<String>) { println("Hello, world!") }
実行します。
$ ./gradlew run
実行されました。
> Task :compileKotlin Using Kotlin incremental compilation > Task :run Hello, world!
IntelliJ IDEA 2017.2 では build.gradle.kts を指定してプロジェクトのインポートできないので、今ならEarly Access Program の 2017.3 EAP を使った方が良いです。
Servlet
簡単なサーブレットを Tomcat で動かしてみましょう。
先程の build.gradle.kts を以下のように変更します。
plugins { application kotlin("jvm") version "1.1.60" } application { mainClassName = "samples.HelloWorldKt" } dependencies { compile(kotlin("stdlib")) compile("org.apache.tomcat.embed:tomcat-embed-core:8.5.23") } repositories { jcenter() }
dependencies に tomcat-embed-core 加えただけです。
サーブレットクラスとTomcatの起動用処理を書きます。
package samples import org.apache.catalina.core.StandardContext import org.apache.catalina.startup.Tomcat import java.io.File import javax.servlet.http.HttpServlet import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse fun main(args: Array<String>) { val tomcat = Tomcat() val docBase = File("src/main/webapp").absolutePath val ctx = tomcat.addContext("/app", docBase) as StandardContext Tomcat.addServlet(ctx, "hello", HelloController()) .addMapping("/hello") tomcat.start() tomcat.server.await() } class HelloController : HttpServlet() { override fun doGet(req: HttpServletRequest, res: HttpServletResponse) { res.writer.write("Hello, World!!") } }
実行してみましょう。
$ ./gradlew run > Task :run XX XX, 2017 XX:XX:XX org.apache.coyote.AbstractProtocol init 情報: Initializing ProtocolHandler ["http-nio-8080"] XX XX, 2017 XX:XX:XX org.apache.tomcat.util.net.NioSelectorPool getSharedSelector 情報: Using a shared selector for servlet write/read XX XX, 2017 XX:XX:XX org.apache.catalina.core.StandardService startInternal 情報: Starting service [Tomcat] XX XX, 2017 XX:XX:XX org.apache.catalina.core.StandardEngine startInternal 情報: Starting Servlet Engine: Apache Tomcat/8.5.23 XX XX, 2017 XX:XX:XX org.apache.coyote.AbstractProtocol start 情報: Starting ProtocolHandler ["http-nio-8080"]
ブラウザから http://localhost:8080/app/hello
にアクセスすれば以下のように表示されます。
Hello, World!!
Exposed
JetBrains が作っているプロトタイプという位置づけのKotlin 向けの SQL ライブラリです。うまくすれば、ktorinx パッケージに標準ライブラリとして入るのかもしれません。
簡単に使い方をみてみましょう。
jcenter には登録されていないので、リポジトリに bintray を追加します。
plugins { application kotlin("jvm") version "1.1.60" } application { mainClassName = "samples.HelloWorldKt" } dependencies { compile(kotlin("stdlib")) compile("org.jetbrains.exposed:exposed:0.9.1") compile("com.h2database:h2:1.4.196") compile("ch.qos.logback:logback-classic:1.2.3") } repositories { jcenter() maven("https://dl.bintray.com/kotlin/exposed") }
依存に exposed と合わせて H2 と logback を追加しました。
テーブルは org.jetbrains.exposed.sql.Table() を継承して定義します。
org.jetbrains.exposed.dao パッケージには Table() を継承した IdTable() があり、通常はそのサブクラスである IntIdTable() や LongIdTable() が利用できます。
以下のように object としてテーブル定義します。
object Users : IntIdTable() { val name = varchar("name", 50).index() val city = reference("city", Cities) val age = integer("age") } object Cities: IntIdTable() { val name = varchar("name", 50) }
データベースの接続は Database.connect() で行います。
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.SchemaUtils.drop fun main(args: Array<String>) { Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") transaction { create (Cities, Users) //... drop (Users, Cities) } }
SchemaUtils.create() でテーブル生成、SchemaUtils.drop() でテーブルのドロップを行うことができます。
Entity を以下のようにクラス定義することで、このクラス経由でデータアクセスができるようになります。
class User(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<User>(Users) var name by Users.name var city by City referencedOn Users.city var age by Users.age } class City(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<City>(Cities) var name by Cities.name val users by User referrersOn Users.city }
以下のように 新しい Entity を作ることができ、データベースへ永続化されます。
val stPete = City.new { name = "St. Petersburg" } User.new { name = "b" city = stPete age = 27 }
Entity の一覧は all() で取得できます。
City.all().forEach { println(it.name) }
検索は find で行います。
User.find { Users.age greaterEq 18 }.forEach {
println(it.name)
}
更新はプロパティを書き換えるだけです。
stPete.name = "St.Petersburg"
idで取得して削除などは以下のようになります。
User[stPete.id].delete()
簡単ですね。
SQL の DSL も用意されているため、Entity 経由せず、直接 Table オブジェクトからSQLを実行することもできます。
val saintPetersburgId = Cities.insert { it[name] = "St. Petersburg" } get Cities.id
Cities は object Cities: IntIdTable() として定義したテーブルオブジェクトで、テーブルに Insert しています。
Select は以下のようにできます。
for (city in Cities.selectAll()) { println("${city[Cities.id]}: ${city[Cities.name]}") }
Join して取得することもできます。
(Users innerJoin Cities) .slice(Users.name, Users.cityId, Cities.name) .select {Cities.name.eq("St. Petersburg") or Users.cityId.isNull()} .forEach { if (it[Users.cityId] != null) { println("${it[Users.name]} lives in ${it[Cities.name]}") } else { println("${it[Users.name]} lives nowhere") } }
groupBy による集約。
((Cities innerJoin Users) .slice(Cities.name, Users.id.count()) .selectAll() .groupBy(Cities.name)) .forEach { val cityName = it[Cities.name] val userCount = it[Users.id.count()] if (userCount > 0) { println("$userCount user(s) live(s) in $cityName") } else { println("Nobody lives in $cityName") } }
update と delete。
Users.update({Users.id eq "alex"}) { it[name] = "Alexey" } Users.deleteWhere{Users.name like "%thing"}
Ktor
こちらも JetBrains が作っている Web アプリケーションフレームワークです。Kotlin 1.1 で(実験的)導入されたコルーチン使ったフレームワークになります。
build.gradle.kts を編集します。
例によって jcenter には登録されていないので、bintray のリポジトリを指定します。
plugins { application kotlin("jvm") version "1.1.60" } application { mainClassName = "samples.HelloWorldKt" } dependencies { compile(kotlin("stdlib")) compile("ch.qos.logback:logback-classic:1.2.3") compile("io.ktor:ktor-server-core:0.9.0") compile("io.ktor:ktor-server-netty:0.9.0") } repositories { jcenter() maven ("http://dl.bintray.com/kotlin/ktor") }
ktor-server-core と ktor-server-netty の依存を追加しました。
ktor では エンジン部分を差し替えできるようになっており、jetty や tomcat など色々ありますが、ここでは netty としています。
HelloWorld.kt を以下のように編集します。
package samples import io.ktor.application.* import io.ktor.http.* import io.ktor.response.* import io.ktor.routing.* import io.ktor.server.engine.* import io.ktor.server.netty.* fun main(args: Array<String>) { val server = embeddedServer(Netty, 8080) { routing { get("/") { call.respondText("Hello, world!", ContentType.Text.Html) } } } server.start(wait = true) }
実行してみましょう。
$ ./gradlew run
コルーチンは実験的です、というワーニングが出ますが、
The feature "coroutines" is experimental (see: https://kotlinlang.org/docs/diagnostics/experimental-coroutines)
ブラウザから http://localhost:8080/
にアクセスすれば以下のように表示されます。
Hello, world!
まだドキュメントは皆無に近いので、以下のサンプルを見ながら雰囲気をつかむことができます。
[https://github.com/ktorio/ktor/tree/master/ktor-samples]
まとめ
いくつかの Kotlin 周辺ライブラリやフレームワークについて見てみました。
最近 いろいろと野心的なプロジェクトが動いている Kotlin 界隈をのぞいてみるのも面白いですね。