JDBC
Spring Session JDBC 是一個模組,它允許使用 JDBC 作為資料儲存來管理會話。
-
我想自定義表名
-
我想將會話屬性儲存為 JSON 而不是位元組陣列
-
我想為 Spring Session JDBC 使用不同的
DataSource -
我想自定義過期會話清理作業
將 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 = convert_from(?, 'UTF8')::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)儲存會很有用,這可能在 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 模組,這些模組告訴 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 (?, ?, convert_from(?, 'UTF8')::jsonb) (1)
""";
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = """
UPDATE %TABLE_NAME%_ATTRIBUTES
SET ATTRIBUTE_BYTES = convert_from(?, 'UTF8')::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 時,很可能存在多個 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,它為批處理應用程式提供了強大的解決方案。
自定義過期會話的清理頻率
您可以使用 @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 屬性
-
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'
""");
}
}