Redis
Kotlin
spring
Jackson
spring-boot

Spring Boot2.0 KotlinでSpring Data Redisを使ってみたけど、けど

Kotlinはじめましたが、微妙なところではまります。
動きはしたのですが謎なところが多かったです。

とりあえず、軽く説明をします。
Apiを叩くとredisに保存、読み取りするだけのapiを作ります。

ソースはこちら
GitHub spring boot 2.0
GitHub spring boot 1.5

実装

gradle

build.gradle
buildscript {
    ext {
        kotlinVersion = '1.2.20'
        springBootVersion = '2.0.0.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
        classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
    }
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'eclipse-wtp'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'war'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "1.8"
    }
}
compileTestKotlin {
    kotlinOptions {
        freeCompilerArgs = ["-Xjsr305=strict"]
        jvmTarget = "1.8"
    }
}

repositories {
    mavenCentral()
}

configurations {
    providedRuntime
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    compile("org.jetbrains.kotlin:kotlin-reflect")
    providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
    testCompile('org.springframework.boot:spring-boot-starter-test')

    compile('org.springframework.boot:spring-boot-starter-data-redis')
    compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.4")
    compile("org.apache.commons:commons-pool2:2.5.0")
    compile("redis.clients:jedis:2.9.0")
}

1つ目の謎です。
spring-boot 1.5の時に追加したコンポーネントはspring-boot-starter-data-redis, jackson-module-kotlinだけでしたが、2.0の時はさらにcommons-pool2, jedisが必要でした。
spring-boot-starter-data-redisにcommons-pool2, jedisが依存していてもよさそうです。。。

Kotlin

SampleController.kt
package com.example

import com.example.service.SampleService
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController

@RestController
class SampleController(
        val sampleService: SampleService
) {
    @RequestMapping(value = ["/sample-redis"], method = [RequestMethod.GET])
    fun sampleReids(): SampleDto? {
        return sampleService.getSampleDto();
    }

    @RequestMapping(value = ["/sample-redis2"], method = [RequestMethod.GET])
    fun sampleReids2(): SampleDto? {
        return sampleService.getSampleDto2();
    }
}
SampleDto.kt
package com.example

import java.io.Serializable

class SampleDto(val id:Int, val name:String):Serializable
SampleConfiguration.kt
package com.example

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.StringRedisSerializer

@Configuration
class SampleConfiguration {
    @Bean
    fun jedisConnectionFactory(): JedisConnectionFactory {
        return JedisConnectionFactory()
    }

    @Bean
    fun redisTemplate(): RedisTemplate<String, SampleDto> {
        val template  = RedisTemplate<String, SampleDto>()
        template.setConnectionFactory(jedisConnectionFactory())
        template.keySerializer = StringRedisSerializer()

        val jackson2JsonRedisSerializer = Jackson2JsonRedisSerializer(SampleDto::class.java)
        val om = jacksonObjectMapper()
        jackson2JsonRedisSerializer.setObjectMapper(om)
        template.valueSerializer = jackson2JsonRedisSerializer

        return template;
    }

    @Bean
    fun redisTemplate2(): RedisTemplate<String, String> {
        val template  = RedisTemplate<String, String>()
        template.setConnectionFactory(jedisConnectionFactory())
        template.keySerializer = StringRedisSerializer()
        template.valueSerializer = StringRedisSerializer()
        return template;
    }
}

2つ目の謎です。
connectionFactoryを設定する時の実装が1.5と2.0で違っていました。

Kotlin:spring-boot 1.5
template.connectionFactory = jedisConnectionFactory()

Kotlin:spring-boot 2.0
template.setConnectionFactory(jedisConnectionFactory())

本当は = で設定できるのですかね?

KotlinではJavaの時とは違い Jackson2JsonRedisSerializerを直接設定しても使えなかったので、作成した後にjacksonObjectMapperを設定しました。

redisTemplateの型パラメータを使ってSampleDtoをAnyにしたかったのですが、うまくいきませんでした。
たぶんできるとおもうんだけど。。。
なので、ServiceでredisTemplate2でjson変換をしてConfigurationで文字列を保存するようにしました。

SampleService.kt
package com.example.service

import com.example.SampleDto

interface SampleService {
    fun getSampleDto(): SampleDto?
    fun getSampleDto2(): SampleDto?
}
SampleServiceImpl.kt
package com.example.service

import com.example.SampleDto
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service

@Service
class SampleServiceImpl(
        val redisTemplate:RedisTemplate<String, SampleDto>,
        val redisTemplate2:RedisTemplate<String, String>
):SampleService {
    override fun getSampleDto():SampleDto? {
        redisTemplate.opsForValue().set("test_dto", SampleDto(1, "aab"))
        return redisTemplate.opsForValue().get("test_dto")
    }

    override fun getSampleDto2():SampleDto? {
        val sampleDto = SampleDto(1, "aac")

        val om = jacksonObjectMapper()

        var jsonStr = om.writeValueAsString(sampleDto)
        redisTemplate2.opsForValue().set("test_dto2", jsonStr)

        val jsonStr2 = redisTemplate2.opsForValue().get("test_dto2")
        return om.readValue(jsonStr2 ?: "{}", SampleDto::class.java)
    }
}

参考

Kotlin、難しいです。。。
謎だらけです。

GitHub spring boot 2.0
GitHub spring boot 1.5