Spring Boot (Kotlin) はじめの一歩

# 目的

Spring Bootを学習するきっかけが欲しいと友人にたのまれたので、
実際にペアプロしながら進めていくための資料として作成しました。

Spring Boot (Kotlin)入門
- MVC
- Rest
- DB Access

# 使用するもの

- JDK 8
- IntelliJ Idea
- Kotlin
- Gradle
- Spring Boot
- Doma2 (JPAのほうが最初の設定は楽だった・・)

※IntelliJ Ideaの代わりにSTS + Kotlin PluginでもOKですが、手順は記載していません。
※Mavenの場合、dependencyなどをpom用に読み替えてください。
※Doma2のEntity / Dao実装はKotlinではなくJavaになっています。

# 環境構築

## IntelliJ Ideaのインストール

こちらから
https://www.jetbrains.com/idea/

## Spring Bootプロジェクトの雛形を作成する

Spring Initializrで雛形を作成する
https://start.spring.io/

以下の内容で生成します。

- Gradle Project
- Kotlin
- Spring Boot 1.5.9
- Dependencies => Web

<img width="496" alt="SpringInitializer.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/3a7a1ee2-901a-08b5-4ef8-ad28878798aa.png">

Group / Artifactはお好みで

雛形のzipファイルがダウンロードされるので、適当な場所に展開しておきます。

## プロジェクトを開く

IntelliJを起動してImport Project

<img width="501" alt="Idea.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/86e15ce6-ff5f-03eb-2f62-4cccf6e4d51f.png">

展開したプロジェクトの雛形の中にある`build.gradle`を指定する。

<img width="313" alt="import.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/d393077c-adf8-93b0-65cb-41a1e29ea066.png">

Use auto-importにチェックを入れてOK

<img width="462" alt="use_auto_import.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/f4ea4094-ed7c-b71c-b88f-c08b10d86861.png">

ビルドが始まるので、synced successfullyになればOK

<img width="930" alt="build.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/facc1fdc-a042-f3de-baa5-3a96119b5f7c.png">

`View -> Tool Window -> Gradle`を選択してGradleのツールウィンドウを開く。
Gradleツールウィンドウ内の`demo -> Tasks -> application -> bootRun` を右クリックして Run 'demo[boot Run]'で起動する

<img width="228" alt="bootRun.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/5a80d87b-2db5-7593-f25a-a268b009e3b3.png">

```
14:44:18: Executing task 'bootRun'...

:compileKotlin
:compileJava NO-SOURCE
:copyMainKotlinClasses
:processResources
:classes
:findMainClass
:bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.9.RELEASE)

・
・
・

s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-01-20 14:44:28.125  INFO 13432 --- [           main] com.example.demo.DemoApplicationKt       : Started DemoApplicationKt in 2.45 seconds (JVM running for 2.75)
```

※この時点ではControllerなどが無いので、何もできない。

## Spring Boot起動中にIntelliJでコード修正が反映されるように`build.gradle`の設定

plugin定義に以下を追加する

```build.gradle
 apply plugin: 'idea'
 idea {
   module {
     outputDir file('build/classes/main')
     testOutputDir file('build/classes/test')
   }
 }
 if(project.convention.findPlugin(JavaPluginConvention)) {
	// Change the output directory for the main and test source sets back to the old path
	sourceSets.main.output.classesDir = new File(buildDir, "classes/main")
	sourceSets.test.output.classesDir = new File(buildDir, "classes/test")
 }
```

一番下のdependenciesに以下を追加する

```build.gradle
	compile("org.springframework.boot:spring-boot-starter-thymeleaf")
	compile("org.springframework.boot:spring-boot-devtools")
```

- thymeleaf => テンプレートエンジンのthymeleafを使う
- spring-boot-devtools => オートリロードを有効にする

## オートリロードの設定

`Setting -> Build -> Compiler`を開き、`Build project automatically`にチェックを入れてApply

<img width="764" alt="buildProjectAutomatically.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/e113c1d0-a106-62a1-8056-62c378111e6c.png">

Windowsの場合:`Sthif + Ctrl -> A`
Macの場合:` Command + Ctrl + A`
でウィンドウを開き、`Registry..`で検索

開いたウィンドウで`compiler.automake.allow.when.app.running`にチェックを入れる


<img width="562" alt="compiler.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/6deaefea-1b12-a02e-00ae-a9b198d7b122.png">

# ThymeleafでHTMLを返す

## Controllerの作成

`com.example.demo`の下に`controller`パッケージを作成する。
`controller`パッケージの下に、`GreetingController.kt`を作成する。

```GreetingController.kt
package com.example.demo.controller

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam

@Controller
@RequestMapping("greeting")
class GreetingController {

    @GetMapping("/hello")
    fun hello(
            @RequestParam(value = "name", required = false, defaultValue = "world") name: String,
            model: Model): String {
        model.addAttribute("name", name)
        return "greeting"
    }
}
```

## html(Thymeleaf)の作成
`src/main/resources/templates`の下に`greeting.html`を作成する

```greeting.thml
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" http-equiv="Content-Type" content="text/html" />
    <title>Getting Started: Saving Web Content</title>
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
```

## 動作確認

再度Spring Bootを実行する

Webブラウザで`http://localhost:8080/greeting/hello`にアクセス

<img width="201" alt="helloworld.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/24467/9712760a-d2ed-38ed-a7e7-bd2c141d4a95.png">

`http://localhost:8080/greeting/hello?name=hoge`にしてみると、
表示がHello, Hoge!に変わる。

# DB環境準備

## Virtual Box / Vagrantのインストール

※Windows10 Homeで作業したためDocker for Windowsが使えないので仕方なくVirtual Box / Vagrantを使っています。
Windows10 ProやMacの方はDockerで構築する方が楽なので、VirtualBox / Vagrantの章は飛ばしてdocker-composeからはじめてOKです

Virtual Box
https://www.virtualbox.org/

Vagrant
https://www.vagrantup.com/downloads.html

プラグインのインストール

```
$ vagrant plugin install vagrant-vbguest
```

CentOS7のBoxをインストール・起動

```
cd [任意のディレクトリ]
vagrant init centos/7
```

`Vagrantfile`が生成されるので、ポートフォワード・IPアドレスのバインディングを設定する

```Vagrantfile
# 以下の設定を追加する
  config.vm.network "forwarded_port", guest: 3306, host: 3306
  config.vm.network "private_network", ip: "192.168.33.10"
```

```
vagrant up

# 終わったら↓でrunningになっていることを確認
vagrant status

# vagrantに接続する
vagrant ssh
```

## Vagrant上のCentOS7にDockerをインストール

https://docs.docker.com/engine/installation/linux/docker-ce/centos/

```
# インストール確認
docker -v
# docker起動
sudo systemctl start docker
# 動作確認
sudo docker run hello-world
# vagrant起動時にdockerが起動するように
sudo systemctl enable docker
```

一般ユーザーでもdockerコマンドを使えるようにする

```
sudo gpasswd -a $USER docker
sudo systemctl restart docker

# 一度再ログインする
exit
```

続いてdocker-composeをインストールする
※Docker for Mac (Windows)の場合、同時にインストールされるのでスキップしてOK

https://docs.docker.com/compose/install/#install-compose

```
$ sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

$ sudo chmod +x /usr/local/bin/docker-compose

$ docker-compose --version
```

`docker-compose.yml`の作成

```docker-compose.yml
db:
  image: mysql:5.7
  ports:
    - "3306:3306"
  environment:
    MYSQL_ROOT_PASSWORD: root
  volumes_from:
    - data

data:
  image: busybox:1
  volumes:
    - /var/lib/mysql:/var/lib/mysql # macの場合、適当なディレクトリを指定する
```

イメージの作成

```
$ docker-compose up -d
$ docker-compose ps
[vagrant@localhost ~]$ docker-compose ps
     Name                  Command             State            Ports
------------------------------------------------------------------------------
vagrant_data_1   sh                            Exit 0
vagrant_db_1     docker-entrypoint.sh mysqld   Up       0.0.0.0:3306->3306/tcp
```
mysqlへの接続確認

```
$ docker exec -it vagrant_db_1 bash
# mysql -uroot -proot

# 適当にデータベースを作成しておく
mysql> create database test;
```


ホストマシン(Windows / Mac)からMySQLに接続してみる。

設定方法はクライアント次第だけどこんな感じでつながるはず
※Vagrantを使わない場合はHostは`172.0.0.1`

| 項目              |            設定値 |
|:-----------------|------------------:|
| Host             |      192.168.33.10|
| Database         |              test |
| User             |              root |
| Password         |              root |

適当にテーブルを作っておく

```sql
CREATE TABLE anything(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(255),
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
```

# プロジェクトにDoma2を設定する

```build.gradle
# repositoriesに↓を追加
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }

# compileKotlinの前に↓を追加
processResources.destinationDir = compileJava.destinationDir
compileJava.dependsOn processResources

# dependenciesに↓を追加
	compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
	compile('org.seasar.doma.boot:doma-spring-boot-starter:1.0.2')
```

`application.properties`の設定

```application.properties
# JDBC
spring.datasource.url=jdbc:mysql://192.168.33.10:3306/test
# vagrantを使わない場合はこちら
# spring.datasource.url=jdbc:mysql://172.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# DOMA
doma.dialect=mysql
doma.naming=snake_lower_case
```

# DBアクセスの実装

`src/main`配下に`java`ディレクトリを作成
`src/main/java` 配下に `com.example.demo.entity`, `com.example.demo.dao`パッケージを作成する。

以下のクラスを作成していく

```com.example.demo.entity.AnythingEntity.java
package com.example.demo.entity;

import org.seasar.doma.Entity;
import org.seasar.doma.GeneratedValue;
import org.seasar.doma.GenerationType;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

import java.sql.Timestamp;

@Entity
@Table(name = "anything")
public class AnythingEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer id;

    public String name;

    public Timestamp createdAt;

    public Timestamp updatedAt;
}
```

```com.example.demo.entity.AnythingDao.java
package com.example.demo.dao;

import com.example.demo.entity.AnythingEntity;
import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;

import java.util.List;

@ConfigAutowireable
@Dao
public interface AnythingDao {

    @Select
    List<AnythingEntity> selectAll();

    @Insert
    int insert(AnythingEntity anything);
}
```

===ここからkotlin配下===

`com.example.demo.service`パッケージを作成する

以下のクラスを作成する

```com.example.demo.service.AnythingService.kt
package com.example.demo.service

import com.example.demo.dao.AnythingDao
import com.example.demo.entity.AnythingEntity
import org.springframework.stereotype.Service

@Service
class AnythingService(
        val anythingDao: AnythingDao
) {

    fun findAll(): List<AnythingEntity> {
        return this.anythingDao.selectAll()
    }

    fun insert(anything: AnythingEntity): Int {
        return this.anythingDao.insert(anything)
    }
}
```

```com.example.demo.controller.AnythingController.kt
package com.example.demo.controller

import com.example.demo.entity.AnythingEntity
import com.example.demo.service.AnythingService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("anything")
class AnythingController(
        val anythingService: AnythingService
) {

    @GetMapping("/findAll")
    fun findAll(): List<AnythingEntity> {
        return this.anythingService.findAll()
    }

    // 本当はGetにするべきではないですが、面倒なので・・・
    @GetMapping("/insert")
    fun insert(@RequestParam(value = "name", required = false, defaultValue = "doma") name: String): String {

        val entity = AnythingEntity()
        entity.name = name
        this.anythingService.insert(entity)

        return "success"
    }
}
```

sqlファイルを作成する

```src/main/resources/META-INF/com/example/demo/dao/AnythingDao/selectAll.sql
SELECT
  *
FROM
  anything
```

# 動作確認

curlかブラウザで以下を実行
http://localhost:8080/anything/insert
http://localhost:8080/anything/insert?name=fuga
http://localhost:8080/anything/findAll

以下のような結果が帰ってくればOK
※上の2行でDBへのインサート、最後の行でインサートした結果を取得しています。

[{"id":1,"name":"doma","createdAt":1516459862000,"updatedAt":1516459862000},{"id":2,"name":"fuga","createdAt":1516459895000,"updatedAt":1516459895000}]

# まとめ

今回はSpring Bootで開発を始める第一歩を書きました。
Doma2を使ったのは自分が触ってみたかったからです。
余裕があったら続編を書くかもしれません。

#  最終的なコードはこちら
https://github.com/Yuki10Kobayashi/SpringBootDoma2Demo