对于最新的稳定版本,请使用 Spring Session 3.5.0! |
JDBC
Spring Session JDBC 是一个使用 JDBC 作为数据存储实现会话管理的模块。
-
我想知道 JDBC 架构是如何定义的
-
我想自定义表名
-
我想将会话属性保存为 JSON 而不是字节数组
-
我想使用不同的
DataSource
针对 Spring Session 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
tables 来存储会话。
请注意,在自定义表名称时,用于存储属性的表使用提供的表名称命名,该表名称后缀为_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
);
自定义表名
要自定义数据库表名称,您可以使用tableName
属性@EnableJdbcHttpSession
注解:
-
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 查询,可以使用set*Query
methods 从JdbcIndexedSessionRepository
.
-
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% placeholder 将替换为JdbcIndexedSessionRepository . |
Spring Session JDBC 附带了一些 |
将会话属性另存为 JSON
默认情况下,Spring Session JDBC 将会话属性值保存为字节数组,这样的数组是属性值的 JDK 序列化的结果。
有时,以不同的格式保存会话属性很有用,例如 JSON,这可能在 RDBMS 中具有本机支持,从而在 SQL 查询中实现更好的函数和运算符兼容性。
在此示例中,我们将使用 PostgreSQL 作为 RDBMS,并使用 JSON 而不是 JDK 序列化来序列化会话属性值。
我们首先创建SPRING_SESSION_ATTRIBUTES
table 中带有jsonb
type 的attribute_values
列。
-
SQL
CREATE TABLE SPRING_SESSION
(
-- ...
);
-- indexes...
CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
-- ...
ATTRIBUTE_BYTES JSONB NOT NULL,
-- ...
);
要自定义属性值的序列化方式,首先我们需要向 Spring Session JDBC 提供一个习惯ConversionService
负责从Object
自byte[]
反之亦然。
为此,我们可以创建一个ConversionService
叫springSessionConversionService
.
-
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[]
,我们必须自定义插入和更新 session 属性的查询。
自定义是必要的,因为 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 的会话属性。 有一个示例,您可以在其中查看整个实现并运行测试。
如果您的 |
指定备选方案DataSource
默认情况下,Spring Session JDBC 使用主DataSource
应用程序中可用的 bean。
但是,在某些情况下,应用程序可能具有多个DataSource
s bean 的 bean 中,在这种情况下,你可以告诉 Spring Session JDBC 哪个DataSource
通过使用@SpringSessionDataSource
:
-
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 告诉 Spring Session JDBC 它应该使用该 bean 作为DataSource . |
自定义 Spring Session JDBC 使用事务的方式
所有 JDBC作都以事务方式执行。
执行事务时,propagation 设置为REQUIRES_NEW
为了避免由于干扰现有事务而导致意外行为(例如,在已参与只读事务的线程中运行 Save作)。
要自定义 Spring Session JDBC 使用事务的方式,你可以提供一个TransactionOperations
名为springSessionTransactionOperations
.
例如,如果要将事务作为一个整体禁用,则可以执行以下作:
-
Java
import org.springframework.transaction.support.TransactionOperations;
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean("springSessionTransactionOperations")
public TransactionOperations springSessionTransactionOperations() {
return TransactionOperations.withoutTransaction();
}
}
如果您想要更多控制权,您还可以提供TransactionManager
,由配置的TransactionTemplate
.
默认情况下,Spring Session 将尝试解析 primaryTransactionManager
bean 中。
在某些情况下,例如,当有多个DataSource
s,则很可能会有多个TransactionManager
s,您可以分辨出是哪个TransactionManager
要用于 Spring Session JDBC 的 bean,方法是使用@SpringSessionTransactionManager
:
-
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
@SpringSessionTransactionManager
public TransactionManager transactionManager1() {
return new MyTransactionManager();
}
@Bean
public TransactionManager transactionManager2() {
return otherTransactionManager;
}
}
自定义 Expired Sessions 清理作业
为了避免过期的会话使数据库过载,Spring Session JDBC 每分钟执行一次清理作业,删除过期的会话(及其属性)。 您可能希望自定义清理作业的原因有多种,让我们在以下部分中了解最常见的原因。 但是,默认作业的自定义是有限的,这是有意为之的, Spring Session 并不意味着提供健壮的批处理,因为有很多框架或库在这方面做得更好。 因此,如果您想要更多的自定义功能,请考虑禁用默认作业并提供您自己的作业。 一个很好的选择是使用 Spring Batch,它为批处理应用程序提供了强大的解决方案。
自定义清理过期会话的频率
-
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
到cleanupCron
属性@EnableJdbcHttpSession
:
-
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = Scheduled.CRON_DISABLED)
public class SessionConfig {
}
自定义 Delete By Expiry Time 查询
您可以使用JdbcIndexedSessionRepository.setDeleteSessionsByExpiryTimeQuery
通过SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
豆:
-
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'
""");
}
}