weblog

技術的なメモ置き場。

Micronaut + Thymeleaf を試す

  • Micronaut: 1.2.2
  • Thymeleaf: 3.0.11.RELEASE

適当なディレクトリでMicronautアプリを生成。

$ mn create-app -f kotlin -i

dependenciesにmicronaut-viewsとThymeleafを追加。

build.gradle

implementation "io.micronaut:micronaut-views"
runtimeOnly "org.thymeleaf:thymeleaf:3.0.11.RELEASE"

一旦適当なテキストを返すControllerを用意する。

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller
class ViewController {
    @Get
    fun index() = "test"
}

アプリケーションを起動してGetしてみる。

$ curl http://localhost:8080 -i
HTTP/1.1 200 OK
Date: Mon, 23 Sep 2019 05:08:15 GMT
content-type: application/json
content-length: 4
connection: keep-alive

test

テンプレートの表示

src/main/resources にviewsディレクトリを用意し、index.html を作成する。

<!DOCTYPE html>
<html lang="ja">
<body>
test
</body>
</html>

@View

先ほど作成したControllerを修正する。
@View に作成したテンプレートファイル名を指定する。

import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.views.View

@Controller
class ViewController {
    @Get
    @View("index")
    fun index() = HttpResponse.ok<String>()
}

アプリケーションを起動してGetしてみる。

$ curl http://localhost:8080 -i
HTTP/1.1 200 OK
Content-Type: text/html
Date: Mon, 23 Sep 2019 05:14:41 GMT
content-length: 44
connection: keep-alive

<!DOCTYPE html>
<html lang="ja">
<body>
test
</body>
</html>

ModelAndView

@ViewではなくModelAndViewを使っても同等のことが行える。 Controllerに以下のメソッドを追加する。

@Get("modelandview")
fun modelandview() = ModelAndView("index", "")  // 第一引数にテンプレートファイル名を指定

アプリケーションを起動してGetしてみる。

$ curl http://localhost:8080/modelandview -i
HTTP/1.1 200 OK
Content-Type: text/html
Date: Mon, 23 Sep 2019 05:21:14 GMT
content-length: 44
connection: keep-alive

<!DOCTYPE html>
<html lang="ja">
<body>
test
</body>
</html>

変数の表示

以下のテンプレートファイルを用意する。

src/main/resources/views/pet.html

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<body>
<h1 th:text="${name}"></h1>
<h1 th:text="${age}"></h1>
</body>
</html>

POJOを使った場合

以下のPOJOを用意する。

data class Pet(
    val name: String,
    val age: Int
)

Controllerに以下のメソッドを追加する。

@Get("pojo")
@View("pet")
fun pojo() = HttpResponse.ok(Pet("taro", 10))

アプリケーションを起動してGetしてみる。

$ curl http://localhost:8080/pojo -i
HTTP/1.1 200 OK
Content-Type: text/html
Date: Mon, 23 Sep 2019 05:31:51 GMT
content-length: 80
connection: keep-alive

<!DOCTYPE html>
<html lang="ja">
<body>
<h1>taro</h1>
<h1>10</h1>
</body>
</html>

Mapを使った場合

Controllerに以下のメソッドを追加する。

@Get("map")
@View("pet")
fun map() = HttpResponse.ok(mapOf("name" to "jiro", "age" to 2))

アプリケーションを起動してGetしてみる。

$ curl http://localhost:8080/map -i
HTTP/1.1 200 OK
Content-Type: text/html
Date: Mon, 23 Sep 2019 05:44:01 GMT
content-length: 80
connection: keep-alive

<!DOCTYPE html>
<html lang="ja">
<body>
<h1>jiro</h1>
<h1>2</h1>
</body>
</html>

ModelAndVIewとPOJOを使った場合

Controllerに以下のメソッドを追加する。

@Get("modelandviewpojo")
fun modelAndViewAndPojo() = ModelAndView("pet", Pet("saburo", 3))

アプリケーションを起動してGetしてみる。

$ curl http://localhost:8080/modelandviewpojo -i
HTTP/1.1 200 OK
Content-Type: text/html
Date: Mon, 23 Sep 2019 05:49:24 GMT
content-length: 82
connection: keep-alive

<!DOCTYPE html>
<html lang="ja">
<body>
<h1>saburo</h1>
<h1>3</h1>
</body>
</html>

Micronaut + Kotlin + DomaでTransactionを楽に扱う

前回の内容だとTransactionの扱いが面倒なので、 MethodInterceptorを使って楽をできるようにする。

kentama.hatenablog.com

アノテーションの用意

@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Around
@Type(TransactionInterceptor::class)
annotation class Transactional

MethodInterceptorの用意

@Singleton
class TransactionInterceptor(
    private val domaConfig: DomaConfig
) : MethodInterceptor<Any, Any> {
    override fun intercept(context: MethodInvocationContext<Any, Any>): Any {
        val txManager = domaConfig.transactionManager
        return txManager.required(Supplier { context.proceed() })
    }
}

利用

あとは、適当な箇所に @Transactional を付与すれば自動的にTransactionを開始してくれる。 アノテーションにオプションを用意すればREQUIRED以外も利用できるようにできる。

Micronaut + Kotlin + Doma + HikariCP を試す

前回の内容にHikariCPを適用してみる。

kentama.hatenablog.com

HikariCPの設定

build.gradleに以下を追加する。

dependencies {
    // 省略
    runtimeOnly 'io.micronaut.configuration:micronaut-jdbc-hikari'
}

application.ymlに以下を追加する。

datasources:
  default:
    url: jdbc:postgresql://localhost:5432/dvdrental
    username: postgres
    password: password

DomaのConfig設定

micronaut-jdbc-hikariが設定するDataSourceを利用したいので、LocalTransactionDataSourceとLocalTransactionManagerをFactoryで生成する。

@Factory
class DomaConfigFactory {

    @Bean
    fun localTransactionDataSource(dataSource: DataSource) = LocalTransactionDataSource(dataSource)

    @Bean
    fun localTransactionManager(dataSource: LocalTransactionDataSource) =
        LocalTransactionManager(dataSource.getLocalTransaction(ConfigSupport.defaultJdbcLogger))
}

DIしたいので、Configクラスはobjectではなくclassで作成する。

@Singleton
class DomaConfig : Config {

    private val dialect = PostgresDialect()

    @Inject
    private lateinit var dataSource: LocalTransactionDataSource

    @Inject
    private lateinit var transactionManager: LocalTransactionManager

    override fun getDialect() = dialect

    override fun getDataSource() = dataSource

    override fun getTransactionManager() = transactionManager

    override fun getNaming() = Naming.SNAKE_LOWER_CASE!!
}

Daoの設定

作成したConfigクラスをDIしたいので Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject::class) を追加する。

@Dao
@AnnotateWith(annotations = [
    Annotation(target = AnnotationTarget.CLASS, type = Singleton::class),
    Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject::class)
])
interface CountryDao {
    @Select
    fun findAll(): List<Country>
}

Factoryクラスが苦しいが致し方なし。