SpringでElastiCache for ValkeyをHTTPセッションDBとして利用する

図書館 AWS
図書館

この記事ではSpringからElastiCache for ValkeyをHTTPセッションDBとして利用する方法を解説します。ElastiCache for Valkey環境の構築にも簡単に触れます。

環境前提

Java21
Spring Boot3.5.11

事前準備

Elasti Cache for Valkeyの構築

動作確認用にElasti Cacheを構築します。オンデマンドとサーバレスタイプがありますが、今回はオンデマンドで構築します。

AWSコンソールから以下の内容でElastiCacheの作成を行います。以下で記載していないものは任意のものを設定して下さい。作成には結構時間がかかります(10分ほど)

設定項目設定内容補足
モードValkey
デプロイオプションノードベースのキャッシュ
作成方法クラスターキャッシュ「簡単な作成」を選ぶと設定項目が少なく構築は簡単になりますが今回は理解することも考慮して選択。
クラスターモード無効
ロケーションAWSクラウド
ノードのタイプcache.t3.microテスト用なので最小スペックにします
レプリケーション数1

ElastiCacheは起動している時間や、アクセス量によって課金されます。不要になった場合は忘れず削除してください。

Springの環境構築

SpringからElastiCacheを利用するためのステップ

SpringからElastiCacheをHTTPセッションDBとして利用するために必要となる作業ステップです。

  • 依存ライブラリの追加(pom.xml)
  • SpringBootアプリケーション設定(application.yml)
  • 接続プールと読み取り分散の設定(Java設定クラス)
  • SpringBootアプリケーションへのIAM権限付与
  • HttpSessionを保存する構成を有効化(Java設定クラス)

依存ライブラリの追加(pom.xml)

以下のライブラリをpom.xmlの依存関係に追加します。
・spring-boot-starter-data-redis:SpringSession拡張です。HttpSession経由で透過的にVolkeyにデータを保存する機能を提供します。データのライフサイクルはHTTPセッションと同じになります。
・spring-session-data-redis:RedisTemplateなどを利用し、HttpSessionとは関係なくVolkeyにデータを保存する機能を提供します。Httpセッションに関係ない情報を保存したい場合などに利用します。
・spring-cloud-aws-starter-parameter-store:Spring公式の Spring Cloud AWSです。application.ymlからAWSのパラメータストアを参照する機能を提供します。
・jackson-databind:Jackson の中核ライブラリで、Javaオブジェクトと JSON の相互変換(シリアライズ/デシリアライズ)機能を提供します。

利用可能なバージョンはMaven Centralで確認できます。「spring-cloud-aws-starter-parameter-store」であれば以下になります。
https://central.sonatype.com/search?q=spring-cloud-aws-starter-parameter-store&smo=true

例:pom.xmlへの追記内容

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.11</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>21</java.version>
        <spring-cloud-aws.version>4.0.0</spring-cloud-aws.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Cloud AWS BOM -->
            <dependency>
                <groupId>io.awspring.cloud</groupId>
                <artifactId>spring-cloud-aws-dependencies</artifactId>
                <version>${spring-cloud-aws.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <!-- Spring Data Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- Spring Session -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <!-- Spring Cloud AWS Parameter Store -->
        <dependency>
            <groupId>io.awspring.cloud</groupId>
            <artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
        </dependency>

        <!-- JSON の相互変換(シリアライズ/デシリアライズ) -->
        <!-- spring-boot-starter-webに含まれるのでspring-boot-starter-web利用している場合は不要 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- Java 8日時API対応 -->
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

    </dependencies>
    </build>

</project>

SpringBootアプリケーション設定

今回はローカル環境とAWS環境があることを想定し、以下2つのapplication.ymlを用意し、Spring Profilesで切り替えます。
・application-local.yml:ローカル環境用
・application-dev.yml:AWS環境用

Spring Profileの切替は環境変数(SPRING_PROFILES_ACTIVE=local)や起動パラメータ(–spring.profiles.active=local)などで行います。

ローカル環境用:application-local.yml

ローカル環境でRedisを利用しない場合は次のように記載します。pom.xmlでライブリを読み込んでいるだけで自動構成でRedisが有効になるので無効化しておきます。

spring:
  application:
    name: ElastiCacheSample

  autoconfigure:
    exclude:
      # Redis関連
      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
      - org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
      - org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration
      # Spring Session関連
      - org.springframework.boot.autoconfigure.session.SessionAutoConfiguration
      - org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration
      - org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration
      # AWS Parameter Store関連(最重要!)
      - io.awspring.cloud.autoconfigure.config.parameterstore.ParameterStoreAutoConfiguration
      - io.awspring.cloud.autoconfigure.config.parameterstore.ParameterStoreBootstrapConfiguration
      - io.awspring.cloud.autoconfigure.config.parameterstore.ParameterStorePropertySourceLocator
      # AWS Secrets Manager関連
      - io.awspring.cloud.autoconfigure.config.secretsmanager.SecretsManagerAutoConfiguration
      - io.awspring.cloud.autoconfigure.config.secretsmanager.SecretsManagerBootstrapConfiguration
      # AWS Core関連
      - io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration
      - io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration
      - io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration

  session:
    timeout: 60m

AWS環境用:application-dev.yml

AWS環境用ではRedisが利用できるようにRedisの環境や接続設定、SpringのHTTPセッションにRedisを利用する設定を行います。
Redis接続の認証情報は「password: ${ssm:/myapp/valkey/password}」でパラメータストアから取得するようにしています。

spring:
  application:
    name: ElastiCacheSample

  # Parameter Storeからプロパティをインポート
  # プレフィックスなし(ルート直下から取得)
  config:
    import: "aws-parameterstore:/"

  # AWS関連設定
  cloud:
    aws:
      # リージョン設定
      region:
        static: ap-northeast-1
      
      # Parameter Store設定
      parameterstore:
        enabled: true
      
      # Secrets Manager(使用しない)
      secretsmanager:
        enabled: false

  # セッション設定
  session:
    # セッション有効期限(TTL)
    timeout: 60m

    redis:
      # セッションキーのプレフィックス
      namespace: session:prefix
      
      # Redisへの書き込みタイミング
      flush-mode: on-save
      
      # セッションデータ保存タイミング
      save-mode: on-set-attribute
      
      # リポジトリタイプ
      repository-type: default
        
  # Redis (ElastiCache) 接続設定
  data:
    redis:
      # ===================================================
      # クラスターモード無効の場合の設定
      # プライマリエンドポイントへ接続
      # ===================================================
      host: master.myvalkey.xxx.amazonaws.com
      port: 6379
      
      # 認証情報(Parameter Storeから取得)
      username: ${elasticache-user} # Parameter Store: /elasticache-user
      password: ${elasticache-password}  # Parameter Store: /elasticache-password
      
      # SSL/TLS設定(転送中の暗号化が有効)
      ssl:
        enabled: true
      
      # タイムアウト設定
      timeout: 3000ms
      connect-timeout: 3000ms

      # Lettuce接続プール設定
      # クラスターモード無効(単一ノード)のため控えめな設定
      lettuce:
        pool:
          enabled: true
          max-active: 8
          max-idle: 8
          min-idle: 2
          max-wait: 3000ms
          time-between-eviction-runs: 60000ms
        
        shutdown-timeout: 200ms

HttpSessionにElastiCacheを利用する設定

@EnableRedisHttpSessionを利用するとSpring SessionがHttpSessionの実装をRedis(Valkey)ベースのものに置き換えるように構成されます。
ElastiCacheの設定はapplication.ymlで設定しているものが自動適用されます。

package com.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * ElastiCache(Valkey)をHTTPセッションストアとして使用するための設定クラス
 * 
 * <p>application.ymlの以下の設定が自動的に適用されます:</p>
 * <ul>
 *   <li>spring.session.timeout → セッション有効期限(maxInactiveIntervalInSeconds)</li>
 *   <li>spring.session.redis.namespace → Redisキープレフィックス</li>
 *   <li>spring.session.redis.flush-mode → Redisへの書き込みタイミング</li>
 *   <li>spring.session.redis.save-mode → セッション保存タイミング</li>
 * </ul>
 */
@Configuration
@EnableRedisHttpSession
public class ElastiCacheConfig {
    
    /**
     * application.ymlの設定が自動的に適用されるため、
     * 基本的にはこのアノテーションのみで動作します。
     * 
     * カスタマイズが必要な場合のみ、Beanメソッドを追加してください。
     */
}

@EnableRedisHttpSessionでは以下のようにパラメータを指定することも可能です。
application.ymlとは別の値を指定したい場合などに利用してください。

@Configuration
@EnableRedisHttpSession(
    maxInactiveIntervalInSeconds = 3600,
    redisNamespace = "${spring.session.redis.namespace:session:prefix}",
    flushMode = FlushMode.ON_SAVE,
    saveMode = SaveMode.ON_SET_ATTRIBUTE
)
public class ElastiCacheConfig {
}

ElastiCacheでJSON保存するための設定

ElastiCacheのデフォルトではJavaのシリアライザを利用しバイナリ形式で保存を行います。ここではJSON形式で保存したい場合の設定例を記載します。JSON形式で保存する場合はシリアライザをどうするかなど、いくつか考慮が必要です。

今回は以下を行います。
①JSON保存のシリアライザに「GenericJackson2JsonRedisSerializer」を利用する
 ObjectMapperの動作を変更する必要がある。
 全体に影響がでないよう、ElastiCache専用のObjectMapperを作成する

②Java 8日時API(LocalDateTime, ZonedDateTime等)のサポート
 java.util.Date, java.sql.TimestampなどはJacksonでデフォルト対応しているが
 LocalDate, LocalDateTimeなどは対応していないため、追加設定を行う。
 jacksonのJavaTimeModuleをObjectMapperに登録することで対応する。

③日付型はISO-8601形式で保存
 可読性、保守性を上げるためにデフォルトのUNIXタイムスタンプ(数値)ではな
 ISO-8601形式の文字列でシリアライズさせる

④不明なフィールドを無視
 デフォルト動作ではデシリアライズ時にクラス定義にないフィールドがJSONに
 存在するとエラーになります。
 後方互換性を向上するために不明なフィールドは無視するよう設定します。

フィールドが1つもないクラス(空のBean)をシリアライズを許可
 デフォルト(true)では、空のクラスをJSONに変換しようとするとエラーになるため
 許可するよう設定します。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

/**
 * ElastiCache(Redis)専用のJackson設定クラス。
 * 
 * <p><strong>重要:この設定はElastiCacheのセッション保存専用です。</strong></p>
 * <p>ObjectMapperをBean登録せず、ローカル変数として作成することで、
 * Spring全体のJSON変換(Web APIのレスポンス等)には一切影響を与えません。</p>
 * 
 * <h3>主な機能:</h3>
 * <ul>
 *   <li>型情報付きJSON(@class)を有効化 - 正確な型復元のため</li>
 *   <li>Java 8日時API(LocalDateTime, ZonedDateTime等)のサポート</li>
 *   <li>ISO-8601形式で日時を保存(例: "2026-03-07T15:30:45")</li>
 *   <li>不明なフィールドを無視 - クラス変更時の後方互換性確保</li>
 * </ul>
 * 
 * <h3>ElastiCacheに保存されるJSON例:</h3>
 * <pre>{@code
 * {
 *   "@class": "com.example.domain.UserInfo",
 *   "userId": "user001",
 *   "userName": "田中太郎",
 *   "loginTime": "2026-03-07T15:30:45"
 * }
 * }</pre>
 */
@Configuration
public class RedisJacksonConfig {

    /**
     * Spring Session Redisで使用されるデフォルトのRedisSerializer Bean。
     * 
     * <p>このBean名(springSessionDefaultRedisSerializer)は、
     * Spring Sessionが自動的に検出して使用する特別な名前です。</p>
     * 
     * <p><strong>Spring全体への影響なし:</strong></p>
     * <p>ObjectMapperをローカル変数として作成しているため、
     * この設定はElastiCacheのセッション保存にのみ適用され、
     * 他のWebAPIのJSON変換など他の用途には影響しません。</p>
     *
     * @return RedisSerializer<Object> セッション用のJSONシリアライザ
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        // ElastiCache専用のObjectMapperを作成(Bean登録しない)
        ObjectMapper redisMapper = createElastiCacheObjectMapper();
        
        // GenericJackson2JsonRedisSerializerに渡して返す
        return new GenericJackson2JsonRedisSerializer(redisMapper);
    }

    /**
     * ElastiCache専用のObjectMapperを構築する。
     * 
     * <p>このメソッドで作成されたObjectMapperは、Springコンテナに登録されないため、
     * ElastiCacheのセッション保存以外には使用されません。</p>
     * 
     * @return ObjectMapper ElastiCache用に設定されたObjectMapper
     */
    private ObjectMapper createElastiCacheObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // ========================================
        // 1. Java 8日時API(LocalDateTime, ZonedDateTime等)のサポート
        // ========================================
        mapper.registerModule(new JavaTimeModule());
        
        // 日時をタイムスタンプ(数値)ではなくISO-8601形式(文字列)で保存
        // 例: 1709798445000 → "2026-03-07T15:30:45"
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // ========================================
        // 2. 型情報付きJSON(@class)を有効化
        // ========================================
        // NON_FINAL: finalでないクラス(ほぼすべて)に型情報を付与
        // これにより、セッションから取り出したときに正確な型で復元される
        mapper.activateDefaultTyping(
                mapper.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL
        );

        // ========================================
        // 3. 不明なフィールドを無視して復元可能にする
        // ========================================
        // クラス定義にフィールドを追加・削除しても、
        // 既存のセッションデータ(ElastiCache内のJSON)を復元可能にする
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // ========================================
        // 4. 空のBean(フィールドなし)でもシリアライズエラーにしない
        // ========================================
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        
        return mapper;
    }
}

SpringBootアプリケーションへのIAM権限付与

SpringBootアプリケーションにAWS Parameter Storeへのアクセス権限を付与します。

1. Parameter Storeの読み取りを許可するIAMポリシーを作成

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParameters",
        "ssm:GetParametersByPath"
      ],
      "Resource": [
        "arn:aws:ssm:ap-northeast-1:123456789012:parameter/myapp/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "ssm:DescribeParameters"
      ],
      "Resource": "*"
    }
  ]
}

2. IAM ロール作成

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

3. 「1」で作成したIAMポリシーを「2」で作成したIAMロールにアタッチ

4.EC2のインスタンスプロファイルを作成し、IAMロールをアタッチする

これでEC2からパラメータストアにアクセス可能になります。

以上

タイトルとURLをコピーしました