Kotlin+SpringBootでWebアプリを作ってみる その2
加筆(2016/4/3 18時くらい)
はじめてのSpring Bootを書かれている槇さんから以下のツイートをいただきました。
@KissyBnts spring-boot-starter-data-jpa使っています?つかっていたらviewでLazyInitializationExceptionでないと思いますが。
— Toshiaki Maki (@making) 2016年4月3日
・・・はい、View
の確認をする前にテストコードで動かしてました。
確認した所、@LazyCollection
でアノテートしなくてもView
およびController
で問題なく動作していました。
つまり、lazy initialize
はtrue
でOKってことですね。
よって下記の@LazyCollection(value = LazyCollectionOption.FALSE)
は不要となります。
んー、テストコードがまずいのかな? 以下は失敗したテストです。
@RunWith(SpringJUnit4ClassRunner::class) @SpringApplicationConfiguration(BookApplication::class) @WebAppConfiguration class BookServiceTest{ @Autowired lateinit var bookService: BookService @Test fun findAllBookTest() { var sutList = bookService.findAllBook() 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")) assertThat(sut.authors[0].name, be("author")) assertThat(sut.likeCount, be(0)) assertThat(sut.categories[0].name, be("category")) assertThat(sut.publisher.name, be("publisher")) } }
ここら辺は要調査ですね。
テストコードがまずかったです。
槇さんが教えてくださりました。詳細はこちら
槇さん、ありがとうございました!
やること
Kotlin+SpringBootでWebアプリを作ってみる その1.1の続きです。
今回は
・Controller
と一覧ページの作成
・著者、出版社、カテゴリのEntity
作成
をやっていきます。
Controllerと一覧ページの作成
MainController.kt
package book.application.controller import book.application.service.BookService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.servlet.ModelAndView /** * 一覧画面のコントローラ. */ @Controller class MainController @Autowired constructor(private val bookService: BookService) { @RequestMapping("/main") fun main(): ModelAndView = ModelAndView("/main").apply { addObject("books", bookService.findAllBook()) } }
/main
を受けるメソッドを定義したコントローラクラス。
BookService
をコンストラクタでAutowired
してるのはval
で定数プロパティにしたかったから。
別に変数でいいですって時は@Autowired lateinit var hoge: Hoge
でいいと思います。
メソッドの方はview
に/main.html
を指定して、書籍リストを渡すだけ。
apply{}
でレシーバを返せるのでちょっとスッキリ。
個人的にapply{}
とかlet{}
は便利でとても気に入ってます。
使い方はKotlin スコープ関数 用途まとめがわかりやすくていいと思います。
main.html
<!DOCTYPE html> <html lang="ja" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>一覧ページ</title> <style> dl{ display: inline-block; width: 300px; float: left; margin: 5px; border: 1px solid gray; } dd{ max-height: 50px; overflow: auto; } </style> </head> <body> <h1>一覧ページ</h1> <dl th:each="book : ${books}" th:object="${book}"> <dt>Title</dt> <dd th:text="*{title}">本の名前</dd> <dt>SubTitle</dt> <dd th:if="*{subTitle != null}" th:text="*{subTitle}">サブタイトル</dd> <dt>LeadingSentence</dt> <dd th:text="*{leadingSentence}">リード文</dd> <dt>ImagePath</dt> <dd th:text="*{imagePath}">本当は画像を表示するぞ</dd> <dt>URL</dt> <dd><a href="#" th:href="*{url}" th:text="*{url}">どっかのURL</a></dd> </dl> </body> </html>
一覧ページを表示するhtml
ファイル。
Spring Boot
のテンプレートエンジンといえばThymeleaf
でしょってことで、使ってます。meta
タグはちゃんと閉じましょう...
MainController.main()
でModelAndView
に詰めたbooks
をある分だけ表示しています。
サブタイトルはなかったら表示されません。
Thymeleaf
はチュートリアル見ればだいたいわかる気がします。
レイアウトはCSS
書くか、Bootstrap
を使うか迷ってますが、とりあえず適当に。現状は表示できればいいのだ。
Controllerと一覧ページの作成 : done
著者、カテゴリ、出版社のEntity作成
適当なER図
こんな感じで実装していきます。
author
、category
の各テーブルの主キーとbook
テーブルの主キーで結合表を作って多対多の関係にします。共著、複数カテゴリの書籍への対応です。
publisher
テーブルとbook
テーブルは1対多の関係にします。 複数の出版社が共同で出版することはないでしょうし、翻訳書が複数の出版社から出ることはありますがそれは別の書籍ということにします。
あと、いいね的なのを実装するとか書いてたのでとりあえずいいね数を格納するカラムをそれぞれ作りました。
LikeTarget.kt
package book.application.domain import javax.persistence.Column /** * いいね対象の抽象エンティティクラス. */ abstract class LikeTarget { /** * いいね数を保持するカラム */ @Column(nullable = false) var likeCount: Int = 0 private set /** * いいね数をインクリメントする. * @return 処理後のいいね数 */ fun likeUp(): Int = likeCount++ /** * いいね数をデクリメントする. * @return 処理後のいいね数 */ fun likeDown(): Int = if(likeCount === 0){ 0 } else { likeCount-- } }
いいね対象のEntity
クラスに共通するプロパティとメソッドを定義した抽象クラスです。
interface
でいい気もする。
Author.kt
package book.application.domain import org.hibernate.annotations.LazyCollection import org.hibernate.annotations.LazyCollectionOption import javax.persistence.* /** * authorテーブルのEntity. */ @Entity @Table(name = "author") class Author() : LikeTarget() { @Id @GeneratedValue var id: Long? = null /** * 著者名. */ @Column(nullable = false) lateinit var name: String /** * 執筆した書籍リスト. */ @ManyToMany @JoinTable(name = "author_book", joinColumns = arrayOf(JoinColumn(name = "author_id")), inverseJoinColumns = arrayOf(JoinColumn(name = "book_id"))) // @LazyCollection(value = LazyCollectionOption.FALSE) ←不要です! lateinit var books: MutableList<Book> /** * セカンダリコンストラクタ. * @param id 主キー * @param name 著者名 * @param likeCount いいね数 * @param books 執筆した本リスト */ constructor(id: Long? = null, name: String, likeCount: Int = 0, books: MutableList<Book> = mutableListOf()): this(){ this.id = id this.name = name this.likeCount = likeCount this.books = books } }
上記のLikeTarget
を継承したauthor
テーブルのEntity
クラスです。
@ManyToMany
は多対多の関係を示すアノテーション。
所有者側にtargetEntity
属性を、被所有者側にmappedBy
属性を付ける感じ。
プロパティがGenerics
を使用したCollection
の場合は省略してもOKなようです。
@JoinTable
は結合表を指定するアノテーション。
joinColumns
属性で所有者側の主キーを参照する結合表のカラムを指定し、inverseJoinColumns
属性で被所有者側の主キーを参照するカラムを指定する模様。
@LazyCollection
は遅延初期化(lazy initialize)の設定をするアノテーション。
これにたどり着くまでかなり時間がかかりました。詳細は後述。
Category.kt
package book.application.domain import org.hibernate.annotations.LazyCollection import org.hibernate.annotations.LazyCollectionOption import javax.persistence.* /** * categoryテーブルのEntity. */ @Entity @Table(name = "category") class Category() : LikeTarget() { @Id @GeneratedValue var id: Long? = null private set /** * カテゴリ名. */ @Column(nullable = false) lateinit var name: String private set /** * カテゴリに属する書籍リスト. */ @ManyToMany @JoinTable(name = "category_book", joinColumns = arrayOf(JoinColumn(name = "category_id")), inverseJoinColumns = arrayOf(JoinColumn(name = "book_id"))) // @LazyCollection(value = LazyCollectionOption.FALSE) ←不要です lateinit var books: MutableList<Book> /** * セカンダリコンストラクタ * @param id 主キー * @param name カテゴリ名 * @param likeCount いいね数 * @param books カテゴリに属する書籍リスト */ constructor(id: Long? = null, name: String, likeCount: Int, books: MutableList<Book>): this(){ this.id = id this.name = name this.likeCount = likeCount this.books = books } }
Author
クラスとほぼ一緒。
Publisher.kt
package book.application.domain import org.hibernate.annotations.LazyCollection import org.hibernate.annotations.LazyCollectionOption import javax.persistence.* /** * publisherテーブルのEntity. */ @Entity @Table(name = "publisher") class Publisher() : LikeTarget() { @Id @GeneratedValue var id: Long? = null private set @Column(nullable = false) lateinit var name: String private set /** * 出版している書籍リスト. */ @OneToMany(mappedBy = "publisher") // @LazyCollection(value = LazyCollectionOption.FALSE) ←不要です lateinit var books: MutableList<Book> /** * セカンダリコンストラクタ. * @param id 主キー * @param name 出版社名 * @param likeCount いいね数 * @param books 出版している書籍リスト */ constructor(id: Long? = null, name: String, likeCount: Int = 0, books: MutableList<Book> = mutableListOf()): this(){ this.id = id this.name = name this.likeCount = likeCount this.books = books } }
publisher
テーブルのEntity
です。
@OneToMany
に変わってるくらいです。
Book.kt
package book.application.domain import org.hibernate.annotations.LazyCollection import org.hibernate.annotations.LazyCollectionOption import javax.persistence.* /** * bookテーブルのEntity. */ @Entity @Table(name = "book") class Book() : LikeTarget() { @Id @GeneratedValue var id: Long? = null private set @Column(nullable = false) lateinit var title: String private set @Column var subTitle: String? = null private set @Column(nullable = false) lateinit var leadingSentence: String private set @Column(nullable = false) lateinit var imagePath: String private set @Column(nullable = false) lateinit var url: String private set /** * 著者リスト. */ @ManyToMany(mappedBy = "books") // @LazyCollection(value = LazyCollectionOption.FALSE) ←不要です lateinit var authors: MutableList<Author> private set /** * カテゴリリスト. */ @ManyToMany(mappedBy = "books") // @LazyCollection(value = LazyCollectionOption.FALSE) ←不要です lateinit var categories: MutableList<Category> private set /** * 出版社リスト. */ @ManyToOne @JoinTable(name = "publisher_book", joinColumns = arrayOf(JoinColumn(name = "book_id")), inverseJoinColumns = arrayOf(JoinColumn(name = "publisher_id"))) lateinit var publisher: Publisher /** * セカンダリコンストラクタ * @param id 主キー * @param title 書籍名 * @param subTitle 書籍の副題 ない場合はnull * @param leadingSentence リード文 * @param url リンク先URLパス * @param likeCount いいね数 * @param authors 著者リスト * @param categories カテゴリリスト * @param publisher 出版社リスト */ constructor(id: Long? = null, title: String, subTitle: String? = null, leadingSentence: String, imagePath: String, url: String, likeCount: Int = 0, authors: MutableList<Author> = mutableListOf(), categories: MutableList<Category> = mutableListOf(), publisher: Publisher = Publisher()) : this(){ this.id = id this.title = title this.subTitle = subTitle this.leadingSentence = leadingSentence this.imagePath = imagePath this.url = url this.likeCount = likeCount this.authors = authors this.categories = categories this.publisher = publisher } }
上記Entity
クラスを追加したので、それに合わせてBook.kt
を修正。
まず、LikeTarget
クラスを継承して実装クラスとします。
多対多のauthors
とcategories
には@ManyToMany(mappedBy = "books")
と、@LazyCollection
を付けます。
一方、publisher
には@ManyToOne
と@JoinTables
をアノテートして、多対1の関係と結合表を示します。
これでBook
クラスにAuthor
、Category
、Publisher
の各クラスが紐づけられました。
main.htmlの修正
h1まで省略 <dl th:each="book : ${books}" th:object="${book}"> <dt>Title</dt> <dd th:text="*{title}">本の名前</dd> <dt>SubTitle</dt> <dd th:if="*{subTitle != null}" th:text="*{subTitle}">サブタイトル</dd> <dt>LeadingSentence</dt> <dd th:text="*{leadingSentence}">リード文</dd> <dt>ImagePath</dt> <dd th:text="*{imagePath}">本当は画像を表示するぞ</dd> <dt>Author</dt> <dd th:each="author : *{authors}" th:text="${author.name}">著者の名前</dd> <dt>Category</dt> <dd th:each="category : *{categories}" th:text="${category.name}">カテゴリ名</dd> <dt>Publisher</dt> <dd th:text="*{publisher.name}">出版社名</dd> <dt>URL</dt> <dd><a href="#" th:href="*{url}" th:text="*{url}">どっかのURL</a></dd> </dl> </body> </html>
著者・カテゴリ・出版社を追加したので修正を行います。
著者とカテゴリはth:each
であるだけ表示しています。
著者・カテゴリ・出版社のEntity作成:Done
動かしてみる
※@LazyCollection
でアノテートしなくても問題ありません。下記のもう一回動かしてみたと同様の結果なります。 (2016/4/3 追記)
最初は前述の@LazyCollection
なしで動かしました。
はい、エラー。
LazyInitializationException
これで結構はまりました。
最初は@LazyCollection
アノテーションなしで書いていて、動かしてみると org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:...
とエラーが...
初見の例外だったのとほとんどJPA触ったことなかったので、@ManyToMany
とか@JoinTable
らへんの指定が悪いのかなと思っていじってたのですが治らず。
スタックトレースを見返すとno session or session was closed
という一文が。あぁ、遅延初期化するときにHibernate
のSession
が閉じてるのかーと。
で、解決策をググるもうまくいかず。
詰んだわって感じだったので、IntelliJ助けてくれーと思って、適当にそれらしいアノテーションをcompletionで探してたら@LazayCollection
を発見。
動くに至りました。
IntelliJありがとう。たぶんeclipseでも出てくると思うけど。
まぁ、何が悪かったかというと上述の通りSession
閉じてる時に初期化をしようとしたためです。 Hibernate
はデフォルトでlazy initialize
をtrue
にしているっぽいです。
たぶん不要な初期化を遅らせることでパフォーマンス向上をはかるためだと思われます。
ということはほんとはfalse
にするべきではないのでは?って感じですけど、他に動かし方がよくわからないので一旦はこれでいきます。
他に解決方法を見つけたら修正します。
もう一回動かしてみた結果
SQLは冗長なので省略。
見た目があれなのは置いといて、表示はされてますね。
今回の目標は果たせました。
次回やること
・ページングの実装
・著者、カテゴリ、出版社のRepository作成とそれぞれの書籍リストの表示
あたりをやろうと思います。
Kotlin勉強会に行った話
Kotlin 1.0リリース記念勉強会 in 京都に行ってきました
ぶっちゃけ勉強会初参加。
期待と不安の入り混じった…
そんな感じで行きました。
以下雑すぎる所感まとめ。
Kotlinのいいとこは?
やっぱりnull safety
をあげてる方が多かった気がする。
次にextension
とかスムーズなJava
との連携と移行、getter/setter
が生えるのでコードがスッキリするとかかな?
バイバイ、Lombok...
あとはJetBrainsが作ってて、Androidに最近力入れてるしってのもあった。
知らなかったこと
・java
からnull
が渡されるとIllegalStateException
が発生する
・JRebel
・@JvmField
・Kotlin
はJavaScript
のRuntimeで動く(実験段階)
・ビルドツールKobalt
がある(ver 0.683)
・ドキュメント生成エンジンdokka
がある(ver 0.97)
・関数に対してextension
が書ける
・Java
のメソッド参照はできない(System.out::print
みたいのはNG)
・Android関連ほとんど
・ラムダ式は消せない!!!
全体を通して
まず思ったのは、1.0がリリースされたのがこの前なので当たり前ですが、発展しはじめたばっかりの言語なんだなぁってことですね。
ベストプラクティスはまだわからないけど、自分はこうしてます。ってのも多かったし、ここはKotlinでは無理・厳しいとかもあった。
Kotlinサイコーってのばっかなのかな?と思ってたけど、ダメなとこはダメだから別で代替しましょうだったり、issue投げようって感じでいいなぁと思いました。
僕はJava
よりKotlin
の方が圧倒的に好きなので、Androidだけでなく、そこ以外でも使われるように成長していって欲しいなとか思いました。
あとはAndroid開発やってみたくなったような気がしないでもないです。
発表者さんと発表資料
こざけさん : SIerアーキテクト視点でみたKotlinの紹介
きの子さん : Kotlinはじめてみました
nobuokaさん : Java EE アプリケーションと Kotlin
俺九番さん : Spring Boot + Kotlin 劇的ビフォーアフター
たくじさん : KotlinとモダンなライブラリーでAndroidアプリを作るっ
やんくさん : Kotlinこんなん出ましたけど
山本裕介さん : Dataクラスから始めるKotlin / JetBrains行ってきたよ!
たろうさん : 攻める!ラムダ式禁止おじさん
最後に
勉強会初めて行きましたが、楽しかったです。
関西圏で興味のある勉強会があればどんどん参加していこうと思います。
ちなみにKotlin勉強会は定期開催(歓喜)されるそうなので今後もお邪魔させていただきたいと。
運営・発表者の皆さん、会場等提供してくださったはてなさん、ありがとうございました!
Kotlin+SpringBootでWebアプリを作ってみる その1.1
lateinit
昨日、Kotlin+SpringBootでWebアプリを作ってみる その1を投稿してから30分くらいでこんなツイートが。
lateinit使ってみてほしい/
— やんく (@yy_yank) March 28, 2016
Kotlin+SpringBootでWebアプリを作ってみる 1 - a fledgling https://t.co/Aia0nnSdZE
やんくさんや… てか早い。
ということでlateinit
で書き換えてみる。
Book.kt
package book.application.domain import javax.persistence.* /** * bookテーブルのEntity. */ @Entity @Table(name = "book") class Book() { @Id @GeneratedValue var id: Long? = null private set @Column(nullable = false) lateinit var title: String private set @Column var subTitle: String? = null private set @Column(nullable = false) lateinit var leadingSentence: String private set @Column(nullable = false) lateinit var imagePath: String private set @Column(nullable = false) lateinit var url: String private set /** * セカンダリコンストラクタ * @param id 主キー * @param title 書籍名 * @param subTitle 書籍の副題 ない場合はnull * @param leadingSentence リード文 * @param url リンク先URLパス */ constructor(id: Long? = null, title: String, subTitle: String? = null, leadingSentence: String, imagePath: String, url: String) : this(){ this.id = id this.title = title this.subTitle = subTitle this.leadingSentence = leadingSentence this.imagePath = imagePath this.url = url } }
んー、こうかな?
プリミティブ型、nullable
はlateinit
できないから普通にプロパティを書いた。
こっちの方がgetter, setter
のアクセス制御ができるのでいいですね。カスタムは持てないようですが。
所感
上記のコードでは、id
とsubTitle
のプロパティ定義が微妙な感じがしますが、lateinit
を使う方が良さそうな感じです。
微妙なところはもっと勉強しないと。
それにしても、稚拙でも書いて公開してみるものですね。
まさかフォローしてる方に読んでもらえて軽くコメントしてもらえるとは...!
P.S
4/2(土)Kotlin 1.0リリース勉強会 in 京都 に参加します。
Kotlin初心者なりに学んだこと、感じたことを書こうと思っています。
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と一覧画面を作成
・著者、出版社、カテゴリのテーブル・エンティティの作成