86
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

アクシスAdvent Calendar 2020

Day 8

Spring BootでサーバーサイドKotlin入門

Last updated at Posted at 2020-12-07

kotlin (1).png

普段の業務ではPHP(Laravel)を使っています。
この間Android開発でKotlinを経験し、一度は静的型付言語でWeb開発をしてみたいとも思っていたのでKotlin×Spring Bootで簡単なWebアプリを作ってみることにしました。

Kotlin触ってて思ったのが、Javaの知識を前提に書かれている情報が多いな〜1ということでした。
自分は基本情報対策くらいしかJavaやってなくて、結構調べなきゃいけなかったりと面倒だったので、できるだけJavaの知識がなくても分かるように書いていこうと思います。

環境

  • OS: macOS Catalina
  • 言語: Kotlin
  • フレームワーク: Spring Boot
  • ビルドツール: Gradle
  • IDE: Intellij IDEA

フレームワークをSpring Bootにした理由はKotlinでWeb開発するなら一番スタンダードっぽかったからです。
KtorというAPIサーバーに良さそうなフレームワークも気になるので今度試してみたいです。

事前準備

  1. JDK(Java Development Kit)をインストールします。
    JDKについては次の項目で解説していますので、分からない方はこちらを読んでみてください。

  2. Intellij IDEAをインストールし、セットアップをします。
    Intellij IDEAはKotlinの開発元であるJetBrainsが作っているIDEなこともあって、ほぼ完璧にkotlinをサポートしています。セットアップ自体は誘導に従えば大丈夫だと思います。参考になりそうな記事はこちらに。

↑自分はどっちもHomebrewでインストールしました。
ちなみにKotlinはIntellij IDEAに付属しているため、別にインストールしなくても使えます。

予備知識

JDK

準備のところでJDKをインストールしましたが、"JDKって何のために必要なんだ?KotlinなんだからKotlin用のコンパイラとかビルドツールがあれば実行できるんじゃないの?"と疑問に思った人もいるかもしれません。
そういう人のためにJavaの実行環境について少し解説します。

図1jvm.png

図1を見てください。まず、通常のコンパイル言語と違ってJavaのプログラムはコンパイルしてもいきなり実行可能ファイルができる訳ではありません。Javaバイトコードで書かれたJavaクラスファイルと呼ばれるものが生成されます。

そして、バイトコードはJVM(Java Virtual Machine)という各OS用に用意された仮想マシン上で逐一解釈されながら実行されます。この仕組みのおかげで、わざわざ環境ごとにコンパイルを行う必要なく、一度コンパイルするだけでJVMが用意された環境でならどこでも動かせるようになっています。

知ってはいると思いますが、KotlinはJVM言語2です。
その意味するところは、コンパイルされるとJVMが解釈できるJavaバイトコードを生成する言語であるということです。(図2)
図2kotlin.png

つまり、kotlinを実行したり開発する環境ではJVMが動いていなければいけません。
ここでやっとJDKの話が出てきます。

JDKにはJVM・Javaコンパイラ・ライブラリ等のJavaの開発に必要な物のセットです。したがって、準備の項目でJDKをインストールしたという訳です。
おそらく、純粋なKotlinのコードを実行するだけなら、KotlinのコンパイラとJVMがあれば動くと思いますが3、Javaのライブラリを使ったり、Javaと混在して使ったりする場合がほとんどだと思うのでJDKは入れておきましょう。

Gradle

gradle.png

Gradleはビルドツールで、コンパイル・リンク・テストなどを自動で行ってくれます。
ただ、自分は他のビルドツール4を使ったことがないのでここがいいぞ!みたいなことは良く分かってません。
設定をGroovyという言語で行うことは大きな特徴の一つだと思うので、その書き方くらいはある程度分かってるといいと思います。以下の記事が参考になります。
Groovyを知らない人のためのbuild.gradle読み書き入門

Spring Boot

springboot.png

Spring Bootは従来のJavaのフレームワークであるSpring Frameworkを簡単に使えるようにしたフレームワークです。Spring Frameworkの複雑なxmlでの設定をなくし、アノテーションを使うことでコードを簡略化しているのが特徴のようです。
Spring Frameworkを触っていないので、どのくらい楽になったとかは分かりませんが、実際の動きは後々見ていきます。

環境構築

前置きが長くなってしまいましたが、Spring Bootの環境構築をしていきましょう。

プロジェクトの雛形を作成

  1. Spring Initializrにアクセスします。
    Spring Initializrを使うと簡単にSpring Bootプロジェクトの雛形を作ることができます。CLIもあるみたいです。

  2. 以下の画像の通り設定。
    _2020-12-02_21.31.52.png
    Groupはパッケージ名、Artifactはビルドして生成される.jarファイルの名前になります。
    Dependenciesには以下のものを追加します。

    • Spring Web: Webアプリ作成に必要な依存関係定義のセット
    • Thymleaf: テンプレートエンジン
    • Spring Data JPA: ORM
    • H2 Database: 軽量な組み込みDB
  3. "Generate"をクリックすると、雛形のzipがダウンロードされるので、解凍します。

Intellij IDEAでプロジェクトを設定

  1. Intellij IDEAを開き、"Open or Import"をクリックして、先ほど解凍したディレクトリを選択します。
    スクリーンショット 2020-12-05 13.43.51.png

  2. "springboot/src/main/kotlin/com/example/springboot/SpringbootApplication.kt"を開いてみます。
    スクリーンショット 2020-12-05 13.56.42.png
    このSpringbootApplication.ktファイルのトップレベルに定義されたmainメソッドがプロジェクトのエントリポイントになります。

  3. mainメソッドの左側にある緑のマークを押すと、アプリケーションが起動します。
    スクリーンショット 2020-12-02 21.44.40.png
    次からは、右上の緑のマークからアプリケーションを起動できるようになります。

ブラウザで登録・表示をできるようにする

今回は簡素ですがブラウザからユーザーを追加、一覧表示できるアプリケーションを作成しました。

成果物

springboot.gif

ディレクトリ構造

src
├── kotlin
│   └── com
│       └── example
│           └── springboot
│               ├── SpringbootApplication.kt
│               ├── controller
│               │   └── MainController.kt
│               ├── entity
│               │   └── User.kt
│               └── repository
│                   └── UserRepository.kt
└── resources
    ├── application.properties
    └── templates
        ├── add.html
        └── index.html
**コード**
User.kt

package com.example.springboot.entity

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
data class User(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Int = 0,
        var name: String = ""
)
UserRepository.kt

package com.example.springboot.repository

import com.example.springboot.entity.User
import org.springframework.data.repository.CrudRepository

interface UserRepository : CrudRepository<User, Int>
MainController.kt

package com.example.springboot.controller

import com.example.springboot.entity.User
import com.example.springboot.repository.UserRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam

@Controller
class MainController {
    @Autowired
    lateinit var userRepository: UserRepository

    @GetMapping("/")
    fun showUsers(model: Model): String {
        val users = userRepository.findAll()
        model.addAttribute("users", users)
        return "index"
    }

    @GetMapping("/add")
    fun showAddPage(): String  {
        return "add"
    }

    @PostMapping("/add")
    fun addNewUser(@RequestParam name: String): String {
        userRepository.save(User(0, name))
        return "redirect:/"
    }
}
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
    <h1>Users</h1>
    <input type="button" onclick="location.href='/add'" value="追加ページ">
    <ul>
        <li th:each="user : ${users}">[[${user.name}]]</li>
    </ul>
</body>
</html>
add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add</title>
</head>
<body>
    <h1>Add</h1>
    <form action="/add" method="post">
        <label for="name">Name:</label>
        <input type="text" id="name" name="name">
        <button type="submit">追加</button>
    </form>
</body>
</html>
application.properties

spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:./h2db/db_example
spring.datasource.username=username
spring.datasource.password=password
## 詳細 それでは以下に分けてアプリケーションの処理を説明していきます。
  1. トップページの表示
  2. ユーザー追加ページへの遷移
  3. ユーザーの追加

1. トップページを表示

スクリーンショット 2020-12-07 22.45.54.png

まず、ユーザーが/にアクセスしてきた時に、リクエストを受け取って処理を行うコントローラーから説明します。

MainController.kt

package com.example.springboot.controller



@Controller
class MainController {
    @Autowired
    lateinit var userRepository: UserRepository

    @GetMapping("/")
    fun showUsers(model: Model): String {
        val users = userRepository.findAll()
        model.addAttribute("users", users)
        return "index"
    }


@Controller

コントローラーのクラス名の前に@Controllerアノテーションもしくは@RestControllerをつけます。
後述するテンプレートを使う場合は、@Controllerを使用します。@RestControllerを使うと、メソッドの戻り値がそのままレスポンスボディになります。
HTMLを返したい時は@Controller、APIのエントリポイントにしたいなら@RestControllerという認識で大丈夫だと思います。

@Autowired

@AutowiredアノテーションはDIするクラスを指定する為に使います。
今回では、DBからのデータの取得を行うUserRepositoryのインスタンスが注入されます。

@GetMapping

@GetMapping("/")アノテーションは、/に対してGETメソッドでリクエストが飛んできた場合、アノテートされたメソッド(今回はshowUsers())が呼び出されるということを意味しています。

Model

テンプレートに値を渡したい時は、メソッドの引数にModelを指定し、ModelのaddAttribute()メソッドの第一引数にテンプレートでの変数名、第二引数に値を渡します。

userRepository.findAll()

DIしたUserRepositoryインスタンスの全ユーザーを取得するメソッドを実行しています。
~Repositoryクラスはデータの取得を抽象化5するレイヤです。UserRepositoryクラスの中身については後述します。

return "index"

テンプレートに"templates/index.html"を指定しています。
@Controllerがついたコントローラー内のメソッドの返り値に文字列を指定すると、そのファイル名のテンプレートをViewに指定します。

それでは、Viewに指定されているテンプレートを見てみましょう。テンプレートエンジンには前述した通り、Thymleafが使われています。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
    <h1>Users</h1>
    <input type="button" onclick="location.href='/add'" value="追加ページ">
    <ul>
        <li th:each="user : ${users}">[[${user.name}]]</li>
    </ul>
</body>
</html>

${users}

コントローラーから渡された値は${}で囲って使うことができます。

th:each=""

th:each="各要素を格納する変数名 : ${iterableな変数}"で繰り返しタグを生成出来ます。

[[${user.name}]]

これをタグで挟むと[[]]中身がその要素のテキストとして出力されます。
<タグ名 th:text="中身">も同じことを表します。

次に、データの取得を抽象化するリポジトリを説明します。

UserRepository.kt

package com.example.springboot.repository

import com.example.springboot.entity.User
import org.springframework.data.repository.CrudRepository

interface UserRepository : CrudRepository<User, Int>

CrudRepository

Spring Data JPAが用意した、テーブル操作の基本であるCRUD(Create、Read、Upload、Delete)を行うインターフェースです。このインターフェースを実装したインターフェースを用意することでCRUD操作が可能になります。
また、CrudRepositoryの第一型引数には操作するエンティティクラス、第二型引数にはエンティティのIDの型を指定します。
はじめは、コントローラーにUserRepositoryが注入されているのに、UserRepositoryの定義はインターフェースになっていて、どうやって実装クラスなしにインスタンスを生成するのか不思議でしたが、どうやらSpring Data JPAが実行時によしなにやってくれているようです。(適当ですみません)

最後に、リポジトリで使用されていたエンティティの中身をみます。エンティティはDBの1テーブルに対応します。

User.kt


@Entity
data class User(
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Int = 0,
        var name: String = ""
)

@Entity

このアノテーションがついたクラスと同名のテーブルがマッピングされます。

@Id

主キーに対応するフィールドに付けます。

@GeneratedValue(strategy = GenerationType.IDENTITY)

MysqlでいうAUTO_INCREMENTのような指定。
GenerationType.の後にくる値で意味が変わってきます。

意味
IDENTITY AUTO_INCREMENTを指定
SEQUENCE DBのシーケンシャルオブジェクトを使用する
TABLE シーケンシャルな値を管理するテーブルを生成する
AUTO DBごとに異なる方法を選択する

2. ユーザー追加ページへの遷移

スクリーンショット 2020-12-07 22.46.51.png

次に、ユーザーが"追加ページ"ボタンを押して/addにアクセスした場合のコントローラー動きを見ます。

MainController.kt



@Controller
class MainController {



    @GetMapping("/add")
    fun showAddPage(): String  {
        return "add"
    }


}

ここでは/addGETメソッドで来たリクエストをshowAddPage()メソッドにマッピングして、テンプレートに"add.html"を指定しています。

では、そのテンプレートの中身を見ます。

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add</title>
</head>
<body>
    <h1>Add</h1>
    <form action="/add" method="post">
        <label for="name">Name:</label>
        <input type="text" id="name" name="name">
        <button type="submit">追加</button>
    </form>
</body>
</html>

ここでは特別なことはしておらず、単純にnameという項目のフォームとsubmitボタンがあるだけです。

3. ユーザーの追加

スクリーンショット 2020-12-07 22.47.56.png

最後に、/addPOSTメソッドのリクエストが来た時のコントローラーの動きを確認します。

MainController.kt



@Controller
class MainController {
    @Autowired
    lateinit var userRepository: UserRepository



    @PostMapping("/add")
    fun addNewUser(@RequestParam name: String): String {
        userRepository.save(User(0, name))
        return "redirect:/"
    }
}

@PostMapping

前述の@GetMappingアノテーションから推測できると思いますが、/addに対してPOSTメソッドでリクエストが飛んできた場合、アノテートされたメソッド(今回はaddNewUser())が呼び出されます。

@RequestParam

指定した引数がリクエストパラメータであることを示します。ここではフォームで入力した文字列がnameという変数に入ります。

userRepository.save(User(0, name))

ここではフィールドnameaddNewUser()に渡されたnameであるようなインスタンスを生成し、DBに保存しています。
0になっている箇所はIDで、ここに既存のIDを入れてしまうとUpdateになってしまうので、存在しない0を渡し、Createするようにしています。

return "redirect:/"

/にリダイレクトさせるという意味になります。

以上で、今回のアプリケーションで行っている処理については終わりました。ここで今更ですが、application.propetiesで行っているDBの接続情報の設定を少しだけ説明します。

application.properties

spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:./h2db/db_example
spring.datasource.username=username
spring.datasource.password=password

spring.jpa.hibernate.ddl-auto=update

アプリケーションの起動時にDBのスキーマに対する操作を設定します。有効にすると@Entityが付与されたクラス通りにスキーマを生成してくれます。設定値は以下の通りです。

意味
note 何もしない
validate 検証のみ行う
update 必要なテーブルがない場合は作成する
create 必要なテーブルを作成する。既存のテーブルは削除し、新規作成する
create-drop 必要なテーブルを作成する。セッション終了後に削除する

spring.datasource.driver-class-name=org.h2.Driver

DBの接続ドライバの指定です。

spring.datasource.url=jdbc:h2:./h2db/db_example

DBの接続先URLです。今回はファイルに保存をしています。

spring.datasource.username=username

DBの接続ユーザーです。

spring.datasource.password=password

DBの接続パスワードです。

最後に

初めて静的型付言語でweb開発やってみました。まだまだ規模が小さくてPHP×Laravelとの比較など行えませんが、これから色々いじってみて良いところ、悪いところを探して行こうかなという感じです。
ktorも気になりますし、マイクロサービスなんかも気になるのでこれから勉強して行けたらいいなと思っています。

参考情報

横山恭大, 『入門!実践!サーバーサイドkotlin』, 株式会社インプレス R&D, 2019
Spring Boot と Kotlin を使用した Web アプリケーションの構築
Spring Boot で Thymeleaf 使い方メモ
Spring Boot 使い方メモ

  1. 大量にいるJavaユーザーへの学習コストが低い&Javaのエコシステムをそのまま流用できるっていうのがkotlinの強力なメリットなので仕方ない部分だとは思います

  2. 他にもScala、GroovyなどのたくさんのJVM言語があります。

  3. 間違っていたらすみません。その時はご指摘いただけると助かります。

  4. Make、Ant、Mavenなど

  5. データの取得先(DBやAPI)を隠蔽する。本来はControllerとRepositoryは直接やりとりをせず、Serviceレイヤが間に入るべきですが簡単の為省いています。

86
56
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
86
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?