この記事は エムスリー Advent Calendar 2017 の18日目の記事です。
今年はKotlinが熱い1年でした!今年の5月に、Googleがandroid開発言語としてKotlinを公式にサポートするとアナウンスしてから、急速に利用が広がっているようですね
出典:https://blog.jetbrains.com/jp/2017/11/29/828
私が所属するエムスリーでも
- 日本Kotlinユーザグループ代表、長澤太郎が書籍を2冊出版
- イベント『どこでもKotlin』シリーズを開催
- 私のチームでサーバーサイドKotlinによるシステムリニューアルを実施
とKotlinに関する話題が盛り沢山の年となりました
KotlinはJetBrains社が開発したいわゆるJVM言語です。型推論・Null Safety・便利な標準リスト操作関数などモダンな開発言語の特徴を備えつつ、既存のJavaコードともシームレスに相互運用できることが特徴です。Javaの有名なWebアプリケーションフレームワークであるSpring Bootも特に問題なく使うことができます。
この記事では、KotlinをSpring BootとDoma2(Java製のDBアクセスライブラリ)とともに使って、簡単なCRUDアプリケーションを立ち上げる手順を紹介します。まだサーバーサイドKotlinに触れたことが無い方は是非この機会に触ってみてもらえると嬉しいです!なお、本記事で作るサンプルのソースコードはgithubにあげています
エディタ: IntelliJ IDEA
Kotlin 1.1.61, Spring Boot 1.5.9, Doma 2.19.0
- Kotlinは2017/12/16現在、spring initializrでDLしたバージョン
Hello World! (たった3ステップ!)
まずはhello!と表示するREST APIを作ってみます。spring initializrを使うと、とても簡単です!
1. spring initializrからひな形をダウンロード
以下のサイトにアクセスし、所定の項目を選択した上で、「Generate Project」ボタンを押してzipをダウンロードしてください。
- Gradle、Kotlin、Spring Boot 1.5.9
- Project Metadata: 好みに合わせて入力ください
- Dependencies: Web、DevTools、Thymeleaf
2. controllerを作る
ダウンロードしたzipを解凍。IntelliJ IDEAでOpenを選択し、解凍したディレクトリを選択します
左メニューのファイルを作成したいフォルダにカーソルをあわせ、右クリック => New => Kotlin File/Classと選択します
NameをHomeController。KindをClassにしてOK
hello!と返すRestControllerを作成します。@RestController
や@GetMapping
はspringframeworkが提供するアノテーションです
package com.example.kotlinspringbootdomademo
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class HomeController {
@GetMapping("")
fun index(): String = "hello!"
}
3. 起動コマンドを叩く
$ ./gradlew clean bootRun
http://localhost:8080 にアクセスすればhello!と表示されます!
Thymeleafを使って、home画面を作成
今度はh1タグにhello Kotlin!と表示するhome画面を作成していきます。なお、htmlテンプレートにはThymeleafを使います
まずは、htmlを作成します。src/main/resources/templates/home.htmlというファイルを作成し、messageという変数をh1に展開するようにしておきます
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<h1 th:text="${message}"></h1>
</body>
</html>
次に、先程作ったHomeControllerを修正します
+import org.springframework.stereotype.Controller
+import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
-import org.springframework.web.bind.annotation.RestController
-@RestController
+@Controller
class HomeController {
@GetMapping("")
- fun index(): String = "hello!"
+ fun index(model: Model): String {
+ model.addAttribute("message", "hello Kotlin!")
+ return "home"
+ }
ビルドしなおして、 http://localhost:8080 にアクセスすると、h1タグにhello Kotlin!と表示されます!
ついでに、CSSも使って背景を赤くしてみましょう。cssファイルを作成します
body {
background-color: red;
}
作成したcssをhtmlから読み込みます
<head>
<meta charset="UTF-8" />
<title>Title</title>
+ <link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
背景が赤くなればOKです
DB(Postgresql、Docker)の準備
さて、ここからはDBアクセスです。インメモリDBのH2を使ってもよいのですが、今回はDockerでpostgresqlを立てることにします。以下のファイルを作成してください
version: "3"
services:
postgres:
build: ./docker/postgres/
image: kotlin-demo-postgres
ports:
- 5432:5432
container_name: kotlin-demo-postgres-container
FROM postgres:9.5.3
COPY ./docker-entrypoint-initdb.d /docker-entrypoint-initdb.d
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE DATABASE kotlindemo;
EOSQL
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" kotlindemo <<-EOSQL
BEGIN;
create table customer (
id serial,
name varchar(50),
email varchar(50)
);
insert into customer(name, email) values ('hoge', 'hoge@example.com');
insert into customer(name, email) values ('fuge', 'fuge@example.com');
insert into customer(name, email) values ('piyo', 'piyo@example.com');
COMMIT;
EOSQL
起動します
$ docker-machine start
$ eval $(docker-machine env default)
$ docker-compose up
コンテナにアクセスして、初期データが入っていることを確認できればOKです
$ eval $(docker-machine env default)
$ docker exec -it kotlin-demo-postgres-container psql -U postgres kotlindemo
kotlindemo=# select * from customer;
id | name | email
----+------+------------------
1 | hoge | hoge@example.com
2 | fuge | fuge@example.com
3 | piyo | piyo@example.com
(3 rows)
Doma 2でDBアクセス
DBアクセスには、Doma 2というライブラリを使います。Java製のライブラリで、2-way SQL(SQLファイルを外部ファイル化できる)のが特徴です(詳しくは公式のドキュメントをご覧ください)。ちなみに、私達のチームでは、Domaの開発で大切にしている10のことという開発方針がよいと思ったのと、社内のJavaプロジェクトで導入実績があったことからDoma 2を採用しました。
なお、Doma2はKotlin1.1.2を実験的にサポートしているのですが、私のチームでは、将来Kotlinのバージョンが上がっていった際にハマるリスクを回避するために「Domaの層は全てJavaで書く」という選択をしています(本記事で紹介するようにDomain層でKotlinにマッピングすれば、実際に触るコードは殆どKotlinになります)。今回も同じように「Domaの層は全てJavaで書く」という選択をとります
まずは依存ライブラリを入れます。doma-spring-boot-starter
を使うことで、Spring BootへのDomaの導入が簡単になります。
ext {
kotlinVersion = '1.1.61'
springBootVersion = '1.5.9.RELEASE'
+ postgresqlVersion = '42.1.4'
+ springBootDomaVersion = '1.1.1'
+ domaVersion = '2.19.0'
}
repositories {
mavenCentral()
@@ -32,6 +35,12 @@ repositories {
mavenCentral()
}
+// -------------------------------------------
+// for Doma2 with gradle
+// see: http://doma.readthedocs.io/ja/stable/build/#gradle
+// -------------------------------------------
+processResources.destinationDir = compileJava.destinationDir
+compileJava.dependsOn processResources
dependencies {
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
@@ -40,4 +49,15 @@ dependencies {
compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
runtime('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
+
+ // -------------------------------------------
+ // postgresql
+ // -------------------------------------------
+ compile "org.postgresql:postgresql:${postgresqlVersion}"
+
+ // -------------------------------------------
+ // Doma2
+ // -------------------------------------------
+ compile "org.seasar.doma.boot:doma-spring-boot-starter:${springBootDomaVersion}"
+ compile "org.seasar.doma:doma:${domaVersion}"
}
application.ymlにdomaがpostgresを使うことを記載します。(application.propertiesはyml形式にしたいので、ファイル名を変更しておきます)
$ mv src/main/resources/application.properties src/main/resources/application.yml
doma:
dialect: postgres
DomaのEntity(Java)を作成します。
package com.example.kotlinspringbootdomademo.infrastructure.doma.entity;
import org.seasar.doma.*;
import org.seasar.doma.jdbc.entity.NamingType;
@Entity(naming = NamingType.SNAKE_UPPER_CASE)
@Table(name = "customer")
public class CustomerDomaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
public String name;
public String email;
}
DomaのDaoインターフェース(Java)を作成します
package com.example.kotlinspringbootdomademo.infrastructure.doma.dao;
import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity;
import org.seasar.doma.Dao;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;
import java.util.List;
@ConfigAutowireable
@Dao
public interface CustomerDomaDao {
@Select
List<CustomerDomaEntity> selectAll();
}
SQLファイルを作成します
select
/*%expand*/*
from
customer
order by
id desc
ここまででがDomaのコード(Javaで書く部分)となります。補足ですが、既存DBが数100テーブルある場合、DomaのEntity、Dao、SQLを手動で作成していくのは骨の折れる作業です。Doma-Genを使うとこれらを自動生成することができます。
さて、ここからはKotlinです
ドメインのmodelを作成します
package com.example.kotlinspringbootdomademo.domain.model
data class Customer(val id: Int? = null, val name: String, val email: String)
Repositoryのインターフェースを作成します
package com.example.kotlinspringbootdomademo.domain.repository
import com.example.kotlinspringbootdomademo.domain.model.Customer
interface CustomerRepository {
fun findAll(): List<Customer>
}
Repositoryの実装を作成します。ここでDomaのEntity(Java)をドメインのModel(Kotlin)に詰め替えます
package com.example.kotlinspringbootdomademo.infrastructure.domarepository
import com.example.kotlinspringbootdomademo.domain.model.Customer
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import com.example.kotlinspringbootdomademo.infrastructure.doma.dao.CustomerDomaDao
import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity
import org.springframework.stereotype.Repository
@Repository
class CustomerRepositoryDomaImpl(
private val customerDomaDao: CustomerDomaDao
): CustomerRepository {
override fun findAll(): List<Customer> {
return customerDomaDao.selectAll().map { _mapToModel(it) }
}
// ここでDomaのEntity(Java)をドメインのModel(Kotlin)に詰め替える
private fun _mapToModel(domaEntity: CustomerDomaEntity): Customer {
return Customer(
id = domaEntity.id,
name = domaEntity.name,
email = domaEntity.email
)
}
}
Controllerを作成します
package com.example.kotlinspringbootdomademo.application.controller.web
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
@Controller
@RequestMapping("/customers")
class CustomerController(
private val customerRepository: CustomerRepository
) {
@GetMapping("")
fun index(model: Model): String {
val customers = customerRepository.findAll()
model.addAttribute("customers", customers)
return "customers/index"
}
}
htmlを作成します
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
<h1>顧客一覧</h1>
<ul th:unless="${customers.isEmpty()}">
<li th:each="customer: ${customers}">
<span th:text="${customer.id}"></span>
<span th:text="${customer.name}"></span>
<span th:text="${customer.email}"></span>
</li>
</ul>
</body>
</html>
最後にSpring Bootがデータベースに接続するための環境変数を設定
DOCKER_IP=$(docker-machine ip)
export SPRING_DATASOURCE_URL=jdbc:postgresql://${DOCKER_IP}:5432/kotlindemo
export SPRING_DATASOURCE_USERNAME=postgres
export SPRING_DATASOURCE_PASSWORD=
export SPRING_DATASOURCE_DRIVER_CLASS_NAME="org.postgresql.Driver"
環境変数を読み込んだうえで、起動します
$ source .env.dev
$ ./gradlew clean bootRun
http://localhost:8080/customers にアクセスしてDBの中身が表示されればOKです!
詳細ページ
一覧ページを作成することができたので、今度は詳細ページを作成していきます
domaのDaoインターフェース
public interface CustomerDomaDao {
@Select
List<CustomerDomaEntity> selectAll();
+
+ @Select
+ CustomerDomaEntity selectById(int id);
}
domaのSQL
select
/*%expand*/*
from
customer
where
id = /* id */1
repository(インターフェース)
interface CustomerRepository {
fun findAll(): List<Customer>
+ fun findById(id: Int): Customer?
repository(実装)
+ override fun findById(id: Int): Customer? {
+ return customerDomaDao.selectById(id)?.let { _mapToModel(it) }
+ }
controller
+import com.example.kotlinspringbootdomademo.application.RecordNotFoundException
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
@Controller
@@ -17,4 +19,14 @@ class CustomerController(
model.addAttribute("customers", customers)
return "customers/index"
}
+
+ @GetMapping("{id}")
+ fun show(
+ @PathVariable id: Int,
+ model: Model
+ ): String {
+ val customer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+ model.addAttribute("customer", customer)
+ return "customers/show"
+ }
idがなかった場合のExceptionを追加
package com.example.kotlinspringbootdomademo.application
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
@ResponseStatus(HttpStatus.NOT_FOUND)
class RecordNotFoundException(): RuntimeException()
htmlを作成
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
<h1 th:text="${customer.name}"></h1>
<span th:text="${customer.email}"></span>
</body>
</html>
http://localhost:8080/customers/1 にアクセスして詳細情報が表示されればOKです
作成ページ
このまま作成ページも作成してみましょう。
domaのdaoインターフェース
@@ -1,7 +1,9 @@
package com.example.kotlinspringbootdomademo.infrastructure.doma.dao;
import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity;
import org.seasar.doma.Dao;
+import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.boot.ConfigAutowireable;
@@ -15,4 +17,7 @@ public interface CustomerDomaDao {
@Select
CustomerDomaEntity selectById(int id);
+
+ @Insert
+ int insert(CustomerDomaEntity entity);
}
repository(インターフェース)
interface CustomerRepository {
fun findAll(): List<Customer>
fun findById(id: Int): Customer?
+ fun create(customer: Customer): Int
}
repository(実装)。ここでドメインのModel(Kotlin)をDomaのEntity(Java)をに詰め替える
@@ -18,6 +18,12 @@ class CustomerRepositoryDomaImpl(
return customerDomaDao.selectById(id)?.let { _mapToModel(it) }
}
+ override fun create(customer: Customer): Int {
+ val domaEntity = _mapToDomaEntity(customer)
+ customerDomaDao.insert(domaEntity)
+ return domaEntity.id
+ }
+
private fun _mapToModel(domaEntity: CustomerDomaEntity): Customer {
return Customer(
id = domaEntity.id,
@@ -25,4 +31,12 @@ class CustomerRepositoryDomaImpl(
email = domaEntity.email
)
}
+
+ // ここでドメインのModel(Kotlin)をDomaのEntity(Java)をに詰め替える
+ private fun _mapToDomaEntity(customer: Customer): CustomerDomaEntity {
+ return CustomerDomaEntity().also {
+ it.id = customer.id
+ it.name = customer.name
+ it.email = customer.email
+ }
+ }
applicationservice層を追加(@Transactional
でトランザクション境界を宣言)
package com.example.kotlinspringbootdomademo.application.service
import com.example.kotlinspringbootdomademo.application.input.CustomerInput
import com.example.kotlinspringbootdomademo.domain.model.Customer
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
@Transactional
class CustomerApplicationService(
private val customerRepository: CustomerRepository
) {
fun create(customerInput: CustomerInput): Int {
val customer = Customer(
name = customerInput.name!!,
email = customerInput.email!!
)
return customerRepository.create(customer)
}
}
ユーザーの入力を表現するクラスを作成
package com.example.kotlinspringbootdomademo.application.input
import org.hibernate.validator.constraints.NotBlank
import javax.validation.constraints.Size
class CustomerInput {
@NotBlank
@Size(max = 20)
var name: String? = null
@NotBlank
@Size(max = 50)
var email: String? = null
}
controller
+import com.example.kotlinspringbootdomademo.application.input.CustomerInput
+import com.example.kotlinspringbootdomademo.application.service.CustomerApplicationService
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
class CustomerController(
- private val customerRepository: CustomerRepository
+ private val customerRepository: CustomerRepository,
+ private val customerApplicationService: CustomerApplicationService
) {
+ @GetMapping("new")
+ fun new(input: CustomerInput): String {
+ return "customers/new"
+ }
+
+ @PostMapping("")
+ fun create(
+ @Validated customerInput: CustomerInput,
+ bindingResult: BindingResult
+ ): String {
+ if(bindingResult.hasErrors()) {
+ return "customers/new"
+ }
+
+ val id = customerApplicationService.create(customerInput)
+
+ return "redirect:/customers/${id}"
+ }
htmlを作成
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
<h1>顧客作成</h1>
<form th:method="post" th:action="@{./}" th:object="${customerInput}">
氏名: <input type="text" th:field="*{name}" />
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>
email: <input type="text" th:field="*{email}" />
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>
<input type="submit" value="作成" />
</form>
</body>
</html>
http://localhost:8080/customers/new にアクセス
間違った入力をして「作成」ボタンを押すとエラーメッセージが表示されます
正しい値を入力して「作成」を押すと、データが作成されている様子を確認できればOKです
編集ページ
最後に編集ページを作成します
domaのdaoインターフェース
@@ -4,6 +4,7 @@ import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerD
import org.seasar.doma.Dao;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
+import org.seasar.doma.Update;
import org.seasar.doma.boot.ConfigAutowireable;
import java.util.List;
@@ -19,4 +20,7 @@ public interface CustomerDomaDao {
@Insert
int insert(CustomerDomaEntity entity);
+
+ @Update
+ int update(CustomerDomaEntity entity);
}
repositoryインターフェース
@@ -6,4 +6,5 @@ interface CustomerRepository {
fun findAll(): List<Customer>
fun findById(id: Int): Customer?
fun create(customer: Customer): Int
+ fun update(customer: Customer)
repository実装
@@ -24,6 +24,11 @@ class CustomerRepositoryDomaImpl(
return domaEntity.id
}
+ override fun update(customer: Customer) {
+ val domaEntity = _mapToDomaEntity(customer)
+ customerDomaDao.update(domaEntity)
+ }
applicationserviceを修正
@@ -1,5 +1,6 @@
package com.example.kotlinspringbootdomademo.application.service
+import com.example.kotlinspringbootdomademo.application.RecordNotFoundException
import com.example.kotlinspringbootdomademo.application.input.CustomerInput
import com.example.kotlinspringbootdomademo.domain.model.Customer
import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
@@ -11,6 +12,14 @@ import org.springframework.transaction.annotation.Transactional
class CustomerApplicationService(
private val customerRepository: CustomerRepository
) {
+ fun findAll(): List<Customer> {
+ return customerRepository.findAll()
+ }
+
+ fun findById(id: Int): Customer {
+ return customerRepository.findById(id) ?: throw RecordNotFoundException()
+ }
+
fun create(customerInput: CustomerInput): Int {
val customer = Customer(
name = customerInput.name!!,
@@ -19,4 +28,15 @@ class CustomerApplicationService(
return customerRepository.create(customer)
}
+
+ fun update(id: Int, customerInput: CustomerInput) {
+ val existingCustomer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+
+ val customer = existingCustomer.copy(
+ name = customerInput.name!!,
+ email = customerInput.email!!
+ )
+
+ customerRepository.update(customer)
+ }
}
controller(repositoryを直接触るのはやめて、applicationserviceを介すように統一しています)
@@ -1,27 +1,21 @@
package com.example.kotlinspringbootdomademo.application.controller.web
-import com.example.kotlinspringbootdomademo.application.RecordNotFoundException
import com.example.kotlinspringbootdomademo.application.input.CustomerInput
import com.example.kotlinspringbootdomademo.application.service.CustomerApplicationService
-import com.example.kotlinspringbootdomademo.domain.repository.CustomerRepository
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.validation.BindingResult
import org.springframework.validation.annotation.Validated
-import org.springframework.web.bind.annotation.GetMapping
-import org.springframework.web.bind.annotation.PathVariable
-import org.springframework.web.bind.annotation.PostMapping
-import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.*
@Controller
@RequestMapping("/customers")
class CustomerController(
- private val customerRepository: CustomerRepository,
private val customerApplicationService: CustomerApplicationService
) {
@GetMapping("")
fun index(model: Model): String {
- val customers = customerRepository.findAll()
+ val customers = customerApplicationService.findAll()
model.addAttribute("customers", customers)
return "customers/index"
}
@@ -31,7 +25,7 @@ class CustomerController(
@PathVariable id: Int,
model: Model
): String {
- val customer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+ val customer = customerApplicationService.findById(id)
model.addAttribute("customer", customer)
return "customers/show"
}
@@ -54,4 +48,32 @@ class CustomerController(
return "redirect:/customers/${id}"
}
+
+ @GetMapping("{id}/edit")
+ fun edit(
+ @PathVariable id: Int,
+ customerInput: CustomerInput
+ ): String {
+ val customer = customerApplicationService.findById(id)
+
+ customerInput.name = customer.name
+ customerInput.email = customer.email
+
+ return "customers/edit"
+ }
+
+ @PatchMapping("{id}")
+ fun update(
+ @PathVariable id: Int,
+ @Validated customerInput: CustomerInput,
+ bindingResult: BindingResult
+ ): String {
+ if(bindingResult.hasErrors()) {
+ return "customers/edit"
+ }
+
+ customerApplicationService.update(id, customerInput)
+
+ return "redirect:/customers"
+ }
htmlファイル
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<link th:href="@{/css/app.css}" rel="stylesheet" />
</head>
<body>
<h1>顧客編集</h1>
<form th:method="patch" th:action="@{./}" th:object="${customerInput}">
氏名: <input type="text" th:field="*{name}" />
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>
email: <input type="text" th:field="*{email}" />
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>
<input type="submit" value="編集" />
</form>
</body>
</html>
http://localhost:8080/customers/1/edit にアクセス
こちらも不正な値を入れるとエラーメッセージが表示されます
正しい値を入力して「編集」を押すと、データが編集されている様子を確認できればOKです
削除
最後に削除を実装して終わりです
Domaのdaoインターフェース
@@ -1,10 +1,7 @@
package com.example.kotlinspringbootdomademo.infrastructure.doma.dao;
import com.example.kotlinspringbootdomademo.infrastructure.doma.entity.CustomerDomaEntity;
-import org.seasar.doma.Dao;
-import org.seasar.doma.Insert;
-import org.seasar.doma.Select;
-import org.seasar.doma.Update;
+import org.seasar.doma.*;
import org.seasar.doma.boot.ConfigAutowireable;
import java.util.List;
@@ -23,4 +20,7 @@ public interface CustomerDomaDao {
@Update
int update(CustomerDomaEntity entity);
+
+ @Delete
+ int delete(CustomerDomaEntity entity);
}
repositoryインターフェース
@@ -7,4 +7,5 @@ interface CustomerRepository {
fun findById(id: Int): Customer?
fun create(customer: Customer): Int
fun update(customer: Customer)
+ fun delete(customer: Customer)
}
repository実装
@@ -29,6 +29,11 @@ class CustomerRepositoryDomaImpl(
customerDomaDao.update(domaEntity)
}
+ override fun delete(customer: Customer) {
+ val domaEntity = _mapToDomaEntity(customer)
+ customerDomaDao.delete(domaEntity)
+ }
applicationservice
@@ -39,4 +39,9 @@ class CustomerApplicationService(
customerRepository.update(customer)
}
+
+ fun delete(id: Int) {
+ val customer = customerRepository.findById(id) ?: throw RecordNotFoundException()
+ customerRepository.delete(customer)
+ }
controller
@@ -76,4 +76,12 @@ class CustomerController(
return "redirect:/customers"
}
+
+ @DeleteMapping("{id}")
+ fun delete(
+ @PathVariable id: Int
+ ): String {
+ customerApplicationService.delete(id)
+ return "redirect:/customers"
+ }
html
@@ -12,6 +12,9 @@
<span th:text="${customer.id}"></span>
<span th:text="${customer.name}"></span>
<span th:text="${customer.email}"></span>
+ <form th:object="${customer}" th:action="@{/customers/__${customer.id}__}" th:method="delete">
+ <input type="submit" value="削除" />
+ </form>
</li>
</ul>
</body>
削除ボタンを押して該当レコードが削除されればOKです
まとめ
いかがでしたでしょうか。このように、Kotlinはandroidだけでなく、サーバーサイドでも使える言語です。既存のJavaライブラリもシームレスに使えますので、是非一度触ってみてください!have a nice Kotlin!