JDBC
Spring Session JDBC は、JDBC [Wikipedia] (英語) をデータストアとして使用するセッション管理を可能にするモジュールです。
Spring Session JDBC をアプリケーションに追加する
Spring Session JDBC を使用するには、アプリケーションに org.springframework.session:spring-session-jdbc
依存関係を追加する必要があります。
Spring Boot を使用している場合は、Spring Session JDBC の有効化が処理されます。詳細については、そのドキュメントを参照してください。それ以外の場合は、@EnableJdbcHttpSession
を構成クラスに追加する必要があります。
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
//...
}
以上で、アプリケーションは Spring Session JDBC を使用するように構成されます。
セッションストレージの詳細を理解する
デフォルトでは、実装では SPRING_SESSION
テーブルと SPRING_SESSION_ATTRIBUTES
テーブルを使用してセッションを保存します。テーブル名をカスタマイズする場合、属性の格納に使用されるテーブルの名前は、指定されたテーブル名に接尾辞 _ATTRIBUTES
を付けて付けられることに注意してください。さらにカスタマイズが必要な場合は、リポジトリで使用される SQL クエリをカスタマイズできます。
さまざまなデータベースベンダー間の違いのため、特にバイナリデータの保存に関しては、データベースに固有の SQL スクリプトを使用してください。ほとんどの主要なデータベースベンダーのスクリプトは、org/springframework/session/jdbc/schema-*.sql
としてパッケージ化されています。ここで、*
はターゲットデータベース型です。
例: PostgreSQL では、次のスキーマスクリプトを使用できます。
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BYTEA NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);
テーブル名のカスタマイズ
データベーステーブル名をカスタマイズするには、@EnableJdbcHttpSession
アノテーションの tableName
属性を使用できます。
Java
@Configuration
@EnableJdbcHttpSession(tableName = "MY_TABLE_NAME")
public class SessionConfig {
//...
}
もう 1 つの方法は、SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
の実装を Bean として公開し、実装内でテーブルを直接変更することです。
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
public TableNameCustomizer tableNameCustomizer() {
return new TableNameCustomizer();
}
}
public class TableNameCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setTableName("MY_TABLE_NAME");
}
}
SQL クエリのカスタマイズ
Spring Session JDBC によって実行される SQL クエリをカスタマイズできると便利な場合があります。データベース内のセッションまたはその属性に同時に変更が加えられる可能性があるシナリオがあります。たとえば、リクエストですでに存在する属性を挿入する必要があり、重複キー例外が発生する可能性があります。そのため、そのようなシナリオを処理する RDBMS 固有のクエリを適用できます。Spring Session JDBC がデータベースに対して実行する SQL クエリをカスタマイズするには、JdbcIndexedSessionRepository
の set*Query
メソッドを使用できます。
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
public QueryCustomizer tableNameCustomizer() {
return new QueryCustomizer();
}
}
public class QueryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = """
INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) (1)
VALUES (?, ?, ?)
ON CONFLICT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME)
DO NOTHING
""";
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = """
UPDATE %TABLE_NAME%_ATTRIBUTES
SET ATTRIBUTE_BYTES = encode(?, 'escape')::jsonb
WHERE SESSION_PRIMARY_ID = ?
AND ATTRIBUTE_NAME = ?
""";
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
sessionRepository.setUpdateSessionAttributeQuery(UPDATE_SESSION_ATTRIBUTE_QUERY);
}
}
1 | クエリ内の %TABLE_NAME% プレースホルダーは、JdbcIndexedSessionRepository で使用されている構成されたテーブル名に置き換えられます。 |
Spring Session JDBC には、最も一般的な RDBMS 用に最適化された SQL クエリを構成する |
セッション属性を JSON として保存する
デフォルトでは、Spring Session JDBC はセッション属性値をバイト配列として保存します。この配列は属性値の JDK 直列化の結果です。
場合によっては、セッション属性を JSON などのさまざまな形式で保存すると便利です。これにより、RDBMS でネイティブサポートが提供され、SQL クエリでの関数と演算子の互換性が向上する可能性があります。
この例では、RDBMS として PostgreSQL (英語) を使用し、JDK 直列化ではなく JSON を使用してセッション属性値を直列化します。まず、attribute_values
列に jsonb
型を持つ SPRING_SESSION_ATTRIBUTES
テーブルを作成しましょう。
SQL
CREATE TABLE SPRING_SESSION
(
-- ...
);
-- indexes...
CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
-- ...
ATTRIBUTE_BYTES JSONB NOT NULL,
-- ...
);
属性値の直列化方法をカスタマイズするには、まず、Object
から byte[]
へ、またはその逆の変換を担当するカスタム ConversionService
を Spring Session JDBC に提供する必要があります。これを行うには、springSessionConversionService
という名前の、型 ConversionService
の Bean を作成します。
Java
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
@Configuration
@EnableJdbcHttpSession
public class SessionConfig implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Bean("springSessionConversionService")
public GenericConversionService springSessionConversionService(ObjectMapper objectMapper) { (1)
ObjectMapper copy = objectMapper.copy(); (2)
// Register Spring Security Jackson Modules
copy.registerModules(SecurityJackson2Modules.getModules(this.classLoader)); (3)
// Activate default typing explicitly if not using Spring Security
// copy.activateDefaultTyping(copy.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
GenericConversionService converter = new GenericConversionService();
converter.addConverter(Object.class, byte[].class, new SerializingConverter(new JsonSerializer(copy))); (4)
converter.addConverter(byte[].class, Object.class, new DeserializingConverter(new JsonDeserializer(copy))); (4)
return converter;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
static class JsonSerializer implements Serializer<Object> {
private final ObjectMapper objectMapper;
JsonSerializer(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public void serialize(Object object, OutputStream outputStream) throws IOException {
this.objectMapper.writeValue(outputStream, object);
}
}
static class JsonDeserializer implements Deserializer<Object> {
private final ObjectMapper objectMapper;
JsonDeserializer(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Object deserialize(InputStream inputStream) throws IOException {
return this.objectMapper.readValue(inputStream, Object.class);
}
}
}
1 | アプリケーションでデフォルトで使用される ObjectMapper を挿入します。必要に応じて、新しいものを作成することもできます。 |
2 | その ObjectMapper のコピーを作成して、そのコピーにのみ変更を適用します。 |
3 | Spring Security を使用しているため、Spring Security のオブジェクトを適切に直列化 / 逆直列化する方法を Jackson に指示する Jackson モジュールを登録する必要があります。セッション内に保持されている他のオブジェクトに対しても同じことを行う必要がある場合があります。 |
4 | 作成した JsonSerializer /JsonDeserializer を ConversionService に追加します。 |
Spring Session JDBC が属性値を byte[]
に変換する方法を構成したため、セッション属性を挿入および更新するクエリをカスタマイズする必要があります。カスタマイズが必要なのは、Spring Session JDBC が SQL 文でコンテンツをバイトとして設定するためですが、bytea
は jsonb
と互換性がないため、bytea
値をテキストにエンコードしてから jsonb
に変換する必要があります。
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = """
INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES)
VALUES (?, ?, encode(?, 'escape')::jsonb) (1)
""";
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = """
UPDATE %TABLE_NAME%_ATTRIBUTES
SET ATTRIBUTE_BYTES = encode(?, 'escape')::jsonb
WHERE SESSION_PRIMARY_ID = ?
AND ATTRIBUTE_NAME = ?
""";
@Bean
SessionRepositoryCustomizer<JdbcIndexedSessionRepository> customizer() {
return (sessionRepository) -> {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
sessionRepository.setUpdateSessionAttributeQuery(UPDATE_SESSION_ATTRIBUTE_QUERY);
};
}
}
1 | PostgreSQL エンコード (英語) 関数を使用して bytea から text に変換します |
これで、データベースに JSON として保存されたセッション属性を確認できるようになります。実装全体を確認してテストを実行できるサンプルが用意されています [GitHub] (英語) 。
|
代替 DataSource
の指定
デフォルトでは、Spring Session JDBC は、アプリケーションで使用可能なプライマリ DataSource
Bean を使用します。ただし、アプリケーションに複数の DataSource
Bean が含まれるシナリオがいくつかあります。そのようなシナリオでは、Bean を @SpringSessionDataSource
で修飾することで、どの DataSource
を使用するかを Spring Session JDBC に指示できます。
Java
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
public DataSource dataSourceOne() {
// create and configure datasource
return dataSourceOne;
}
@Bean
@SpringSessionDataSource (1)
public DataSource dataSourceTwo() {
// create and configure datasource
return dataSourceTwo;
}
}
1 | dataSourceTwo Bean に @SpringSessionDataSource のアノテーションを付けて、その Bean を DataSource として使用する必要があることを Spring Session JDBC に伝えます。 |
Spring Session JDBC によるトランザクションの使用方法のカスタマイズ
すべての JDBC 操作はトランザクション方式で実行されます。既存のトランザクションへの干渉による予期しない動作 (たとえば、読み取り専用トランザクションにすでに参加しているスレッドでの保存操作の実行など) を回避するために、トランザクションは伝播を REQUIRES_NEW
に設定して実行されます。Spring Session JDBC がトランザクションを使用する方法をカスタマイズするには、springSessionTransactionOperations
という名前の TransactionOperations
Bean を提供します。例: トランザクション全体を無効にしたい場合は、次のように実行できます。
Java
import org.springframework.transaction.support.TransactionOperations;
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean("springSessionTransactionOperations")
public TransactionOperations springSessionTransactionOperations() {
return TransactionOperations.withoutTransaction();
}
}
より詳細な制御が必要な場合は、構成された TransactionTemplate
によって使用される TransactionManager
を提供することもできます。デフォルトでは、Spring Session はアプリケーションコンテキストからプライマリ TransactionManager
Bean を解決しようとします。一部のシナリオでは、たとえば、複数の DataSource
がある場合、複数の TransactionManager
が存在する可能性が非常に高いため、@SpringSessionTransactionManager
で修飾することで、どの TransactionManager
Bean を Spring Session JDBC で使用するかを判断できます。
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
@SpringSessionTransactionManager
public TransactionManager transactionManager1() {
return new MyTransactionManager();
}
@Bean
public TransactionManager transactionManager2() {
return otherTransactionManager;
}
}
期限切れセッションのクリーンアップジョブのカスタマイズ
期限切れのセッションによるデータベースのオーバーロードを避けるために、Spring Session JDBC は期限切れのセッション (およびその属性) を削除するクリーンアップジョブを 1 分ごとに実行します。クリーンアップジョブをカスタマイズする理由はいくつかありますが、次のセクションで最も一般的な理由を見てみましょう。ただし、デフォルトジョブのカスタマイズは制限されており、これは意図的なものであり、Spring Session は堅牢なバッチ処理を提供することを意図したものではありません。これより優れた処理を行うフレームワークやライブラリが多数あるためです。さらにカスタマイズ機能が必要な場合は、デフォルトのジョブを無効にして独自のジョブを提供することを検討してください。代わりに、バッチ処理アプリケーションに堅牢なソリューションを提供する Spring Batch を使用することもできます。
期限切れのセッションをクリーンアップする頻度のカスタマイズ
@EnableJdbcHttpSession
の cleanupCron
属性を使用して、クリーンアップジョブを実行する頻度を定義する cron 式をカスタマイズできます。
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = "0 0 * * * *") // top of every hour of every day
public class SessionConfig {
}
または、Spring Boot を使用している場合は、spring.session.jdbc.cleanup-cron
プロパティを設定します。
spring.session.jdbc.cleanup-cron="0 0 * * * *"
ジョブの無効化
ジョブを無効にするには、Scheduled.CRON_DISABLED
を @EnableJdbcHttpSession
の cleanupCron
属性に渡す必要があります。
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = Scheduled.CRON_DISABLED)
public class SessionConfig {
}
有効期限による削除クエリのカスタマイズ
JdbcIndexedSessionRepository.setDeleteSessionsByExpiryTimeQuery
から SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
Bean を使用して、期限切れのセッションを削除するクエリをカスタマイズできます。
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
public SessionRepositoryCustomizer<JdbcIndexedSessionRepository> customizer() {
return (sessionRepository) -> sessionRepository.setDeleteSessionsByExpiryTimeQuery("""
DELETE FROM %TABLE_NAME%
WHERE EXPIRY_TIME < ?
AND OTHER_COLUMN = 'value'
""");
}
}