Jedis + KotlinでRedisを使う

Kotlin + SpringBootのアプリケーションでJedisを使ってRedisと接続します。

### 環境

* Java 17.0.8
* JDK Amazon Corretto 17
* Kotlin 1.8.22
* Gradle 8.2
* SpringBoot 3.1.3
* Redis 7.2.4
* Jedis 5.1.1

### Jedisとは

Java製のRedis用クライアントです。パフォーマンスと使いやすさが重視されています。

利用・メンテナンス実績は下記の通りです(数値はいずれも2024/02/29確認)
* GitHub Fork数：約3800
* GitHub Star数： 約11000
* 最新コミット日：2024/02/29
* 直近の主なリリース履歴：
  * v5.1.1 2024/02/24
  * v5.1.0 2023/11/21
  * v5.0.2 2023/10/19

利用実績が多く、定期的にリリースされているため安心して利用して良さそうです。

#### 依存関係

| Jedis version | Supported Redis versions | JDK Compatibility |
|---------------|--------------------------|-------------------|
| 3.9+          | 5.0 and 6.2 Family of releases | 8, 11          |
| >= 4.0        | Version 5.0 to current   | 8, 11, 17        |
| >= 5.0        | Version 6.0 to current   | 8, 11, 17        |

[GitHub](https://github.com/redis/jedis?tab=readme-ov-file#supported-redis-versions) より引用

### 導入方法

Gradleのビルドスクリプトに依存関係を追加します。

```setting.gradle.kts

val kotestVersion = "5.7.1"
dependencies {
    // https://mvnrepository.com/artifact/redis.clients/jedis
    implementation("redis.clients:jedis:5.1.1")


    // Kotestは後のサンプルコードで利用します
    testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
    testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
}
```

### サンプルコード


```kotlin:JedisSample.kt

package com.example.myapp

import redis.clients.jedis.JedisPool

class JedisSample {

  /** Jedis接続用インスタンスを生成する **/
  private val jedis = JedisPool("localhost", 6379)

  /**
   * 保存期間を指定してデータを保存する
   */
  fun set(key: String, value: String, seconds: Long) {
    jedis.resource.use {
      it.setex(key, seconds, value)
    }
  }

  /**
   * キーを指定してデータを取得する
   */
  fun get(key: String): String? {
    return jedis.resource.use {
      it.get(key)
    }
  }
}
```

サンプルコードの内容を上から順に見ていきましょう。

#### JedisPoolインスタンス生成

ます、JedisSampleクラスのメンバ変数として`JedisPool`インスタンスを生成しています。
[jevadoc](https://www.javadoc.io/static/redis.clients/jedis/5.1.1/redis/clients/jedis/JedisPool.html) では大量のコンストラクタが紹介されていますが、ここでは接続先ホストとポートのみを指定しています。

今回はRedisをローカルのコンテナで動かすのでhostはlocalhost, ポートはデフォルトの6379を指定します。

#### データ保存

続いて、以下のメソッドでRedisにデータを保存します。

```kotlin
  /**
   * 保存期間を指定してデータを保存する
   */
  fun set(key: String, value: String, seconds: Long) {
    jedis.resource.use {
      it.setex(key, seconds, value)
    }
  }
```

`use{}`ブロック内でJedisリソースを使用してsetexメソッドを呼び出しています。
setexメソッドは保存期間(秒)を指定してデータをRedisにセットします。(SET + EXPIREの略)
保存期間を過ぎるとデータは消えます。

`set(key: String, value: String)`を使えば保存期間を指定せずにデータを登録することもできますが、オンメモリというRedisの特性を考慮すると使う機会は少ないと思われます。



#### データ取得

```kotlin
  fun get(key: String): String? {
    return jedis.resource.use {
      it.get(key)
    }
  }
```

`jedis.get(key: String)` メソッドは指定したキーに対応する値が無い場合、nullを返すことに注意が必要です。

### 動作確認

#### redisコンテナ起動

```
$ docker run -d -p 6379:6379 --name redis-sample redis:7.2.4-alpine
```

#### テストコード

```kotlin
package com.example.myapp

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import java.util.concurrent.TimeUnit

class JedisTest: FunSpec({

  val jedis = JedisSample()

  test("保持期間中はデータを取得できる。保持期間を過ぎるとデータを取得できない") {
    jedis.set("hoge", "fuga", TimeUnit.SECONDS.toSeconds(2))
    jedis.get("hoge") shouldBe "fuga"
    TimeUnit.SECONDS.sleep(3)
    jedis.get("hoge") shouldBe null
  }

  test("存在しないキーは取得できない") {
    jedis.get("aaaa") shouldBe null
  }
})
```

ユニットテストを実行して2つのケースがパスすればOKです。


### 参考資料

* [Maven Repository: redis.clients » jedis](https://mvnrepository.com/artifact/redis.clients/jedis)
* [redis/jedis: Redis Java client](https://github.com/redis/jedis)
* [jedis 5.1.1 javadoc](https://www.javadoc.io/doc/redis.clients/jedis/5.1.1/index.html)
* [jedis Wiki(deprecated)](https://github.com/redis/jedis/wiki)