Kotlin+SpringBootでWebアプリを作ってみる その1
どうもKotlinが楽しそうなので、ためしにWebアプリケーションを作成してみます。
フレームワークは(ほぼ触ったこと無いですが)Spring Bootを使います。
作る予定のもの
・一覧表示、検索機能(今回は本)
・ログイン、ユーザ機能
・いいね的なの
・新着、ランキングとか
KotlinもSpring Bootもチュートリアルレベルなので大変そうですが...
今回はメインクラスの作成と本のエンティティ作成くらいまで。
環境とか概要
・IDEA: IntelliJ IDEA 2016
・Spring Boot Ver: 1.3.3
・Project Name: BookApplication
・Build Tool: Gradle (Ver 2.9)
・Language: Kotlin (Ver 1.0.0)
・DB: MySQL (Ver 5.1.38)
テーブル構造
今回は本のテーブルのみを作成。
UML図はこんな感じ。
ソース
build.gradle
buildscript { ext { springBootVersion = '1.3.3.RELEASE' springLoadedVersion = '1.2.5.RELEASE' kotlinVersion = '1.0.0' mysqlVersion = '5.1.38' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath "org.springframework:springloaded:${springLoadedVersion}" classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") } } apply plugin: 'kotlin' apply plugin: 'eclipse' apply plugin: 'spring-boot' jar { baseName = 'bookapplication' version = '0.0.1-SNAPSHOT' } sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('org.springframework.boot:spring-boot-starter-thymeleaf') compile('org.springframework.boot:spring-boot-starter-web') compile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}") compile("mysql:mysql-connector-java:${mysqlVersion}") testCompile('org.springframework.boot:spring-boot-starter-test') } eclipse { classpath { containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER') containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8' } } task wrapper(type: Wrapper) { gradleVersion = '2.9' }
ビルドファイル。
Spring InitializrからProjectを作成し、Library選択でWeb, JPA, Thymeleafを選択して生成。
追加でSpringLoadedとMySQLのコネクタを。
SpringLoadedはホットデプロイに必要な感じ。
application.yml
spring: # DataSource datasource: url: jdbc:mysql://localhost/book_application username: user password: user driver-class-name: com.mysql.jdbc.Driver # JPA jpa: hibernate: ddl-auto: update show-sql: true # Thymeleaf thymeleaf: cache: false
設定ファイル。propertiesファイルは好きじゃないのでYAMLで書きます。
hibernate
のddl-auto
は、
update
: テーブルが存在しなければ作成する。存在し、マッピング情報が同じならばそのまま使用。マッピング情報に変更があればテーブル構成をアップデートする。
create
: アプリ起動時にテーブルを破棄して新規にテーブルを作成する。そのためアプリ終了後は再度起動するまでデータは残る。
create-drop
: アプリ起動時にテーブルを作成、アプリ終了時にテーブルを破棄する。
none
: たぶん何もしない。
あとはthymeleaf
のキャッシュを切って変更が即時反映されるようにしています。
BookApplication.kt
package book.application import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication @SpringBootApplication open class BookApplication{ companion object { @JvmStatic fun main(args: Array<String>) { SpringApplication.run(BookApplication::class.java, *args) } } }
Book.kt
package book.application.domain import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id import javax.persistence.Table /** * bookテーブルのEntity. * @param id 主キー * @param title 書籍名 * @param subTitle 書籍の副題 ない場合はnull * @param leadingSentence リード文 * @param url リンク先URLパス */ @Entity @Table(name = "book") data class Book(@Id @GeneratedValue var id: Long? = null, @Column(nullable = false) var title: String = "", @Column var subTitle: String? = null, @Column(nullable = false) var leadingSentence: String = "", @Column(nullable = false) var imagePath: String = "", @Column(nullable = false) var url: String = "") { }
bookテーブルのエンティティクラス。
default constructor (引数なしのコンストラクタ)が無いとエラーを吐くので、全てのフィールドに初期値を設定しています。
lateinit @Column var ...
でも書けますが、セカンダリコンストラクタを書かないといけなくなるのと、プリミティブ型、nullableが書けなくなるのでこちらがいいかなと思ってます。
もっと良い書き方があれば教えて欲しいです。
BookRepository.kt
package book.application.repository import book.application.domain.Book import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository /** * bookテーブルのRepository. */ @Repository interface BookRepository : JpaRepository<Book, Long> { }
bookテーブルのリポジトリインターフェイス。
メソッドはのちのち定義していきます。
BookService.kt
package book.application.service import book.application.domain.Book import book.application.repository.BookRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service /** * DBからのデータ取得と加工を行う. */ @Service class BookService @Autowired constructor(private val bookRepository: BookRepository) { /** * 全書籍リストの取得 * @return 書籍リスト */ fun findAllBook(): MutableList<Book> = bookRepository.findAll() }
現状bookテーブルしかないので簡易のサービスクラスを作りました。
クラス名は変えないといけない気がしますね。
BookServiceTest.kt
package book.application.service import book.application.BookApplication import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.SpringApplicationConfiguration import org.springframework.test.context.junit4.SpringJUnit4ClassRunner import org.springframework.test.context.web.WebAppConfiguration import org.hamcrest.CoreMatchers.`is` as be @RunWith(SpringJUnit4ClassRunner::class) @SpringApplicationConfiguration(BookApplication::class) @WebAppConfiguration class BookServiceTest{ @Autowired lateinit var bookService: BookService @Test fun findAllBookTest() { var sutList = bookService.findAllBook()[f:id:KissyBnts:20160329000402p:plain] var sut = sutList[0] assertThat(sutList.size, be(1)) assertThat(sut.id, be(1L)) assertThat(sut.title, be("title")) assertThat(sut.subTitle, be("subTitle")) assertThat(sut.leadingSentence, be("leading")) assertThat(sut.imagePath, be("imagePath")) assertThat(sut.url, be("url")) } }
INSERT INTO book (title, sub_title, leading_sentence, image_path, url) VALUES ('title', 'subTitle', 'leading', 'imagePath', 'url')
が実行されている体でのテスト。(@Before
でちゃんと書かないといけないですが...)
hamcrest
のis
がKotlinの予約語なのでbe
に置き換えています。ここはちょっと微妙ですね。
あとは@Autowired
を使えるようにするためにいろいろアノテーションを書かないといけないです。
実行
うん、ちゃんと実行・取得できてる。
実際はJUnitで@Autowired
するのにかなり躓きましたが...
DB使ったテストコードはそのうちちゃんと書きます。
(職場がテストコードを書かない感じなので、JUnit実践入門を使ってちょっとずつ勉強している...)
所感としては、KotlinらしいこともSpring Bootっぽいこともあんまりしてないですが、
仕事で触ってるstrutsとJava6という悲しい組み合わせの万倍
かなり楽しそうな感じがします。
Kotlinはnullに対して堅牢なのがいいですね。
ラムダ書きやすいし、スマートキャストもいい。
スコープ関数も使いやすくて好きです。
あとSwiftに似てるので、Kotlinがある程度書けるようになったらswiftも書いてみようと思います。
次回
・Controllerと一覧画面を作成
・著者、出版社、カテゴリのテーブル・エンティティの作成