JDBC
Spring Session JDBC 是一个使用 JDBC 作为数据存储来启用会话管理的模块。
Spring Session JDBC is a module that enables session management using JDBC as the data store.
-
I want to adding-spring-session-jdbc
-
I want to session-storage-details
-
I want to customizing-table-name
-
I want to customize-sql-queries
-
I want to save the session-attributes-as-json instead of an array of bytes
-
I want to specifying-datasource for Spring Session JDBC
-
I want to customizing-transaction-operations
-
I want to customize the customizing-cleanup-job
Adding Spring Session JDBC To Your Application
若要使用 Spring Session JDBC,您必须向应用程序添加 org.springframework.session:spring-session-jdbc
依赖项
To use Spring Session JDBC, you must add the org.springframework.session:spring-session-jdbc
dependency to your application
-
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,请参阅其 {spring-boot-ref-docs}/web.html#web.spring-session[文档] 了解更多详情。否则,您需要将 @EnableJdbcHttpSession
添加到配置类:
If you are using Spring Boot, it will take care of enabling Spring Session JDBC, see {spring-boot-ref-docs}/web.html#web.spring-session[its documentation] for more details.
Otherwise, you will need to add @EnableJdbcHttpSession
to a configuration class:
-
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
//...
}
就是这些,您的应用程序现在应该配置为使用 Spring Session JDBC。
And that is it, your application now should be configured to use Spring Session JDBC.
Understanding the Session Storage Details
默认情况下,该实现使用 SPRING_SESSION
和 SPRING_SESSION_ATTRIBUTES
表存储会话。请注意,当您 customize the table name 时,用于存储属性的表通过使用带有 _ATTRIBUTES
后缀的表名命名。如果需要进一步的自定义,您可以 customize the SQL queries used by the repository。
By default, the implementation uses SPRING_SESSION
and SPRING_SESSION_ATTRIBUTES
tables to store sessions.
Note that when you customizing-table-name, the table used to store attributes is named by using the provided table name suffixed with _ATTRIBUTES
.
If further customizations are needed, you can customize-sql-queries.
由于各种数据库供应商之间存在差异,尤其是在存储二进制数据时,请确保使用特定于您的数据库的 SQL 脚本。大多数主要数据库供应商的脚本打包为 org/springframework/session/jdbc/schema-.sql
, where 是目标数据库类型。
Due to the differences between the various database vendors, especially when it comes to storing binary data, make sure to use SQL scripts specific to your database.
Scripts for most major database vendors are packaged as org/springframework/session/jdbc/schema-.sql
, where is the target database type.
例如,对于 PostgreSQL,您可以使用以下架构脚本:
For example, with PostgreSQL, you can use the following schema script:
Unresolved include directive in modules/ROOT/pages/configuration/jdbc.adoc - include::example$session-jdbc-main-resources-dir/org/springframework/session/jdbc/schema-postgresql.sql[]
Customizing the Table Name
要自定义数据库表名,您可以使用 @EnableJdbcHttpSession
注释中的 tableName
属性:
To customize the database table name, you can use the tableName
attribute from the @EnableJdbcHttpSession
annotation:
-
Java
@Configuration
@EnableJdbcHttpSession(tableName = "MY_TABLE_NAME")
public class SessionConfig {
//...
}
另一种选择是将 SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
的实现公开为一个 bean 来直接在实现中更改表:
Another alternative is to expose an implementation of SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
as a bean to change the table directly in the implementation:
-
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");
}
}
Customizing the SQL Queries
有时,能够自定义 Spring Session JDBC 执行的 SQL 查询非常有用。在数据库中可能会并发地修改会话或其属性的情况下,例如,请求可能希望插入一个已经存在的属性,从而导致重复键异常。因此,您可以应用处理此类场景的特定于 RDBMS 的查询。要自定义 Spring Session JDBC 对您的数据库执行的 SQL 查询,您可以从 JdbcIndexedSessionRepository
使用 set*Query
方法。
At times, it is useful to be able to customize the SQL queries executed by Spring Session JDBC.
There are scenarios where there may be concurrent modifications to the session or its attributes in the database, for example, a request might want to insert an attribute that already exists, resulting in a duplicate key exception.
Because of that, you can apply RDBMS specific queries that handles such scenarios.
To customize the SQL queries that Spring Session JDBC executes against your database, you can use the set*Query
methods from 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
""";
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}
1 | The %TABLE_NAME% placeholder in the query will be replaced by the configured table name being used by JdbcIndexedSessionRepository . |
Spring Session JDBC 附带了一些 Spring Session JDBC ships with a few implementations of |
Saving Session Attributes as JSON
默认情况下,Spring Session JDBC 将会话属性值保存为字节数组,此类数组是属性值的 JDK 序列化的结果。
By default, Spring Session JDBC saves the session attributes values as an array of bytes, such array is result from the JDK Serialization of the attribute value.
有时,将会话属性保存在不同的格式中很有用,例如,JSON,它可能在 RDBMS 中提供本机支持,从而允许多功能和操作符在 SQL 查询中兼容。
Sometimes it is useful to save the session attributes in different formats, like JSON, which might have native support in the RDBMS allowing better function and operators compatibility in SQL queries.
对于此示例,我们将 PostgreSQL 用作我们的 RDBMS,并将会话属性值使用 JSON 序列化,而不是 JDK 序列化。我们首先创建一个 SPRING_SESSION_ATTRIBUTES
表,并用 attribute_values
列使用 jsonb
类型。
For this example, we are going to use PostgreSQL as our RDBMS as well as serializing the session attribute values using JSON instead of JDK serialization.
Let’s start by creating the SPRING_SESSION_ATTRIBUTES
table with a jsonb
type for the attribute_values
column.
-
SQL
CREATE TABLE SPRING_SESSION
(
-- ...
);
-- indexes...
CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
-- ...
ATTRIBUTE_BYTES JSONB NOT NULL,
-- ...
);
要自定义属性值的序列化方式,首先我们需要向 Spring Session JDBC 提供一个 {spring-framework-ref-docs}/core/validation/convert.html#core-convert-ConversionService-API[自定义 ConversionService
],负责将 Object
转换为 byte[]
,反之亦然。为此,我们可以创建一个名为 springSessionConversionService
的 ConversionService
类型 bean。
To customize how the attribute values are serialized, first we need to provide to Spring Session JDBC a {spring-framework-ref-docs}/core/validation/convert.html#core-convert-ConversionService-API[custom ConversionService
] responsible for converting from Object
to byte[]
and vice-versa.
To do that, we can create a bean of type ConversionService
named 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
copy.registerModules(SecurityJackson2Modules.getModules(this.classLoader)); 3
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 | Inject the ObjectMapper that is used by default in the application.
You can create a new one if you prefer. |
2 | Create a copy of that ObjectMapper so we only apply the changes to the copy. |
3 | Since we are using Spring Security, we must register its Jackson Modules that tells Jackson how to properly serialize/deserialize Spring Security’s objects. You might need to do the same for other objects that are persisted in the session. |
4 | Add the JsonSerializer /JsonDeserializer that we created into the ConversionService . |
在我们配置了 Spring Session JDBC 如何将我们的属性值转换为 byte[]
后,我们必须自定义插入会话属性的查询。自定义是必要的,因为 Spring Session JDBC 在 SQL 语句中将内容设置为字节,但 bytea
与 jsonb
不兼容,因此我们需要将 bytea
值编码为文本,然后将其转换为 jsonb
。
Now that we configured how Spring Session JDBC converts our attributes values into byte[]
, we must customize the query that insert the session attributes.
The customization is necessary because Spring Session JDBC sets content as bytes in the SQL statement, however, bytea
is not compatible with jsonb
, therefore we need to encode the bytea
value to text and then convert it to 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
""";
@Bean
SessionRepositoryCustomizer<JdbcIndexedSessionRepository> customizer() {
return (sessionRepository) -> sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}
1 | Uses the PostgreSQL encode function to convert from bytea to text |
就是这样,您现在应该能够在数据库中将会话属性另存为 JSON。有一个 sample available,您可以在其中看到整个实现并运行测试。
And that’s it, you should now be able to see the session attributes saved as JSON in the database. There is a sample available where you can see the whole implementation and run the tests.
Specifying an alternative DataSource
默认情况下,Spring Session JDBC 使用可用于应用程序中的主 DataSource
bean。但是,在某些情况下,应用程序可能有多个 DataSource`s beans, in such scenarios you can tell Spring Session JDBC which `DataSource
,可以使用带有 @SpringSessionDataSource
的 bean 限定符来使用:
By default, Spring Session JDBC uses the primary DataSource
bean that is available in the application.
However, there are some scenarios where an application might have multiple DataSource`s beans, in such scenarios you can tell Spring Session JDBC which `DataSource
to use by qualifying the bean with @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 | We annotate the dataSourceTwo bean with @SpringSessionDataSource to tell Spring Session JDBC that it should use that bean as the DataSource . |
Customizing How Spring Session JDBC Uses Transactions
所有 JDBC 操作以事务方式执行。使用传播设置为 REQUIRES_NEW
执行事务,以避免由于与现有事务干扰而导致意外行为(例如,在已参与只读事务的线程中运行保存操作)。要自定义 Spring Session JDBC 使用事务的方式,您可以提供一个名为 springSessionTransactionOperations
的 TransactionOperations
bean。例如,如果您希望完全禁用事务,可以执行以下操作:
All JDBC operations are performed in a transactional manner.
Transactions are performed with propagation set to REQUIRES_NEW
in order to avoid unexpected behavior due to interference with existing transactions (for example, running a save operation in a thread that already participates in a read-only transaction).
To customize how Spring Session JDBC uses transactions, you can provide a TransactionOperations
bean named springSessionTransactionOperations
.
For example, if you want to disable transactions as a whole, you can do:
-
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`s, it is very likely that there will be multiple `TransactionManager`s, you can tell which `TransactionManager
bean 可以通过使用限定符 @SpringSessionTransactionManager
与 Spring Session JDBC 配合使用:
If you want more control, you can also provide the TransactionManager
that is used by the configured TransactionTemplate
.
By default, Spring Session will try to resolve the primary TransactionManager
bean from the application context.
In some scenarios, for example when there are multiple DataSource`s, it is very likely that there will be multiple `TransactionManager`s, you can tell which `TransactionManager
bean that you want to use with Spring Session JDBC by qualifying it with @SpringSessionTransactionManager
:
-
Java
@Configuration
@EnableJdbcHttpSession
public class SessionConfig {
@Bean
@SpringSessionTransactionManager
public TransactionManager transactionManager1() {
return new MyTransactionManager();
}
@Bean
public TransactionManager transactionManager2() {
return otherTransactionManager;
}
}
Customizing the Expired Sessions Clean-Up Job
为了避免使用过期会话重载数据库,Spring Session JDBC 会每分钟执行一次清理作业,删除过期的会话(及其属性)。您可能希望自定义清理作业有多种原因,让我们在以下各节中了解最常见的原因。但是,对默认作业进行的自定义是有限的,这是有意的,Spring Session 不旨在提供健壮的批处理,因为有许多框架或类库做的更好。因此,如果您希望具备更多自定义功能,请考虑 disabling the default job 并提供您自己的功能。一个不错的选择是使用 Spring Batch,它为批处理应用程序提供了健壮的解决方案。
In order to avoid overloading your database with expired sessions, Spring Session JDBC executes a clean-up job every minute that deletes the expired sessions (and its attributes). There are several reasons that you might want to customize the clean-up job, let’s see the most common in the following sections. However, the customizations on the default job are limited, and that is intentional, Spring Session is not meant to provide a robust batch processing since there are a lot of frameworks or libraries that do a better job at that. Therefore, if you want more customization power, consider disabling-the-job and providing your own. A good alternative is to use Spring Batch which provides a robust solution for batch processing applications.
Customizing How Often Expired Sessions Are Cleaned Up
您可以使用 @EnableJdbcHttpSession
中的 cleanupCron
属性自定义定义清理作业运行频率的 {spring-framework-ref-docs}/integration/scheduling.html#scheduling-cron-expression[cron 表达式]:
You can customize the {spring-framework-ref-docs}/integration/scheduling.html#scheduling-cron-expression[cron expression] that defines how often the clean-up job runs by using the cleanupCron
attribute in @EnableJdbcHttpSession
:
-
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = "0 0 * * * *") // top of every hour of every day
public class SessionConfig {
}
或者,如果你使用 Spring Boot,则设置 spring.session.jdbc.cleanup-cron
属性:
Or, if you are using Spring Boot, set the spring.session.jdbc.cleanup-cron
property:
-
application.properties
spring.session.jdbc.cleanup-cron="0 0 * * * *"
Disabling the Job
要禁用作业,你必须将 Scheduled.CRON_DISABLED
传递到 @EnableJdbcHttpSession
中的 cleanupCron
属性:
To disable the job you must pass Scheduled.CRON_DISABLED
to the cleanupCron
attribute in @EnableJdbcHttpSession
:
-
Java
@Configuration
@EnableJdbcHttpSession(cleanupCron = Scheduled.CRON_DISABLED)
public class SessionConfig {
}
Customizing the Delete By Expiry Time Query
你可以通过 SessionRepositoryCustomizer<JdbcIndexedSessionRepository>
Bean 使用 JdbcIndexedSessionRepository.setDeleteSessionsByExpiryTimeQuery
自定删除已过期会话的查询:
You can customize the query that deletes expired sessions by using JdbcIndexedSessionRepository.setDeleteSessionsByExpiryTimeQuery
through a 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'
""");
}
}