JDBC
Spring Session JDBC 是一個模組,可讓您使用 JDBC 作為資料儲存區來啟用工作階段管理。
-
我想要自訂資料表名稱
-
我想要自訂 SQL 查詢
-
我想要將工作階段屬性儲存為 JSON,而不是位元組陣列
-
我想要為 Spring Session JDBC 使用不同的
DataSource
-
我想要自訂 JDBC 交易
-
我想要自訂過期工作階段清除作業
將 Spring Session JDBC 新增至您的應用程式
若要使用 Spring Session JDBC,您必須將 org.springframework.session:spring-session-jdbc
相依性新增至您的應用程式
-
Gradle
-
Maven
implementation 'org.springframework.session:spring-session-jdbc'
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
如果您使用 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 {
//...
}
另一種替代方案是公開 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 隨附 |
將工作階段屬性儲存為 JSON
依預設,Spring Session JDBC 會將工作階段屬性值儲存為位元組陣列,此陣列是屬性值的 JDK 序列化的結果。
有時,以不同的格式 (例如 JSON) 儲存工作階段屬性很有用,JSON 可能在 RDBMS 中具有原生支援,允許在 SQL 查詢中具有更好的函式和運算子相容性。
在此範例中,我們將使用 PostgreSQL 作為我們的 RDBMS,並使用 JSON 而非 JDK 序列化來序列化工作階段屬性值。讓我們先建立 SPRING_SESSION_ATTRIBUTES
資料表,並為 attribute_values
資料行使用 jsonb
類型。
-
SQL
CREATE TABLE SPRING_SESSION
(
-- ...
);
-- indexes...
CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
-- ...
ATTRIBUTE_BYTES JSONB NOT NULL,
-- ...
);
若要自訂屬性值的序列化方式,首先我們需要為 Spring Session JDBC 提供一個自訂 ConversionService
,負責從 Object
轉換為 byte[]
,反之亦然。若要執行此操作,我們可以建立一個名為 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,因此我們必須註冊其 Jackson Modules,以告知 Jackson 如何正確地序列化/還原序列化 Spring Security 的物件。您可能需要針對工作階段中持續存在的其他物件執行相同的操作。 |
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 encode 函式從 bytea 轉換為 text |
這樣就完成了,您現在應該能夠在資料庫中看到儲存為 JSON 的工作階段屬性。有一個可用的範例,您可以在其中查看整個實作並執行測試。
如果您的 |
指定替代的 DataSource
依預設,Spring Session JDBC 會使用應用程式中可用的主要 DataSource
Bean。但是,在某些情況下,應用程式可能有多個 DataSource
Bean,在這種情況下,您可以透過使用 @SpringSessionDataSource
限定 Bean 來告知 Spring Session JDBC 要使用的 DataSource
-
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 | 我們使用 @SpringSessionDataSource 註解 dataSourceTwo Bean,以告知 Spring Session JDBC 它應該使用該 Bean 作為 DataSource 。 |
自訂 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
時,很可能會有 Multiple TransactionManager
,您可以透過使用 @SpringSessionTransactionManager
限定您想要與 Spring Session JDBC 搭配使用的 TransactionManager
Bean 來告知。
-
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
@SpringSessionTransactionManager
public TransactionManager transactionManager1() {
return new MyTransactionManager();
}
@Bean
public TransactionManager transactionManager2() {
return otherTransactionManager;
}
}
自訂過期工作階段清除作業
為了避免過多的過期工作階段使您的資料庫過載,Spring Session JDBC 每分鐘執行一次清除作業,以刪除過期的工作階段 (及其屬性)。您可能想要自訂清除作業的原因有很多,讓我們在以下章節中查看最常見的原因。但是,預設作業的自訂受到限制,這是故意的,Spring Session 並不打算提供穩健的批次處理,因為有很多架構或程式庫在這方面做得更好。因此,如果您想要更多的自訂功能,請考慮停用預設作業並提供您自己的作業。一個好的替代方案是使用 Spring Batch,它為批次處理應用程式提供了穩健的解決方案。
自訂清除過期工作階段的頻率
您可以自訂 cron 運算式,其定義清除作業的執行頻率,方法是使用 @EnableJdbcHttpSession
中的 cleanupCron
屬性
-
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = "0 0 * * * *") // top of every hour of every day
public class SessionConfig {
}
或者,如果您使用 Spring Boot,請設定 spring.session.jdbc.cleanup-cron
屬性
-
application.properties
spring.session.jdbc.cleanup-cron="0 0 * * * *"
停用作業
若要停用作業,您必須將 Scheduled.CRON_DISABLED
傳遞至 @EnableJdbcHttpSession
中的 cleanupCron
屬性
-
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = Scheduled.CRON_DISABLED)
public class SessionConfig {
}
自訂依到期時間刪除查詢
您可以透過 SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
Bean 使用 JdbcIndexedSessionRepository.setDeleteSessionsByExpiryTimeQuery
來客製化刪除過期工作階段的查詢
-
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'
""");
}
}