Create spring-boot-session modules

This commit is contained in:
Andy Wilkinson
2025-04-03 15:59:55 +01:00
committed by Phillip Webb
parent 014240d576
commit e288c81b7b
69 changed files with 914 additions and 710 deletions

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.session.jdbc.autoconfigure;
import java.util.Collections;
import java.util.Set;
import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
/**
*
* {@link DependsOnDatabaseInitializationDetector} for
* {@link JdbcIndexedSessionRepository}.
*
* @author Andy Wilkinson
*/
class JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector
extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector {
@Override
protected Set<Class<?>> getDependsOnDatabaseInitializationBeanTypes() {
return Collections.singleton(JdbcIndexedSessionRepository.class);
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.session.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionProperties;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuraion} for Spring Session JDBC.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 4.0.0
*/
@AutoConfiguration(before = SessionAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties({ JdbcSessionProperties.class, SessionProperties.class })
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcHttpSessionConfiguration.class })
public class JdbcSessionAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnJdbcSessionDatasourceInitializationCondition.class)
JdbcSessionDataSourceScriptDatabaseInitializer jdbcSessionDataSourceScriptDatabaseInitializer(
@SpringSessionDataSource ObjectProvider<DataSource> sessionDataSource,
ObjectProvider<DataSource> dataSource, JdbcSessionProperties properties) {
DataSource dataSourceToInitialize = sessionDataSource.getIfAvailable(dataSource::getObject);
return new JdbcSessionDataSourceScriptDatabaseInitializer(dataSourceToInitialize, properties);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<JdbcIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, JdbcSessionProperties jdbcSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(jdbcSessionProperties::getTableName).to(sessionRepository::setTableName);
map.from(jdbcSessionProperties::getFlushMode).to(sessionRepository::setFlushMode);
map.from(jdbcSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
map.from(jdbcSessionProperties::getCleanupCron).to(sessionRepository::setCleanupCron);
};
}
static class OnJdbcSessionDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
OnJdbcSessionDatasourceInitializationCondition() {
super("Jdbc Session", "spring.session.jdbc.initialize-schema");
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.session.jdbc.autoconfigure;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.jdbc.init.PlatformPlaceholderDatabaseDriverResolver;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.util.StringUtils;
/**
* {@link DataSourceScriptDatabaseInitializer} for the Spring Session JDBC database. May
* be registered as a bean to override auto-configuration.
*
* @author Dave Syer
* @author Vedran Pavic
* @author Andy Wilkinson
* @author Phillip Webb
* @since 4.0.0
*/
public class JdbcSessionDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {
/**
* Create a new {@link JdbcSessionDataSourceScriptDatabaseInitializer} instance.
* @param dataSource the Spring Session JDBC data source
* @param properties the Spring Session JDBC properties
* @see #getSettings
*/
public JdbcSessionDataSourceScriptDatabaseInitializer(DataSource dataSource, JdbcSessionProperties properties) {
this(dataSource, getSettings(dataSource, properties));
}
/**
* Create a new {@link JdbcSessionDataSourceScriptDatabaseInitializer} instance.
* @param dataSource the Spring Session JDBC data source
* @param settings the database initialization settings
* @see #getSettings
*/
public JdbcSessionDataSourceScriptDatabaseInitializer(DataSource dataSource,
DatabaseInitializationSettings settings) {
super(dataSource, settings);
}
/**
* Adapts {@link JdbcSessionProperties Spring Session JDBC properties} to
* {@link DatabaseInitializationSettings} replacing any {@literal @@platform@@}
* placeholders.
* @param dataSource the Spring Session JDBC data source
* @param properties the Spring Session JDBC properties
* @return a new {@link DatabaseInitializationSettings} instance
* @see #JdbcSessionDataSourceScriptDatabaseInitializer(DataSource,
* DatabaseInitializationSettings)
*/
static DatabaseInitializationSettings getSettings(DataSource dataSource, JdbcSessionProperties properties) {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
settings.setMode(properties.getInitializeSchema());
settings.setContinueOnError(true);
return settings;
}
private static List<String> resolveSchemaLocations(DataSource dataSource, JdbcSessionProperties properties) {
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
platformResolver = platformResolver.withDriverPlatform(DatabaseDriver.MARIADB, "mysql");
if (StringUtils.hasText(properties.getPlatform())) {
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
}
return platformResolver.resolveAll(dataSource, properties.getSchema());
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.session.jdbc.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
/**
* Configuration properties for JDBC-backed Spring Session.
*
* @author Vedran Pavic
* @since 4.0.0
*/
@ConfigurationProperties("spring.session.jdbc")
public class JdbcSessionProperties {
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
+ "session/jdbc/schema-@@platform@@.sql";
private static final String DEFAULT_TABLE_NAME = "SPRING_SESSION";
private static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
/**
* Path to the SQL file to use to initialize the database schema.
*/
private String schema = DEFAULT_SCHEMA_LOCATION;
/**
* Platform to use in initialization scripts if the @@platform@@ placeholder is used.
* Auto-detected by default.
*/
private String platform;
/**
* Name of the database table used to store sessions.
*/
private String tableName = DEFAULT_TABLE_NAME;
/**
* Cron expression for expired session cleanup job.
*/
private String cleanupCron = DEFAULT_CLEANUP_CRON;
/**
* Database schema initialization mode.
*/
private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
/**
* Sessions flush mode. Determines when session changes are written to the session
* store.
*/
private FlushMode flushMode = FlushMode.ON_SAVE;
/**
* Sessions save mode. Determines how session changes are tracked and saved to the
* session store.
*/
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
public String getSchema() {
return this.schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getPlatform() {
return this.platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getTableName() {
return this.tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getCleanupCron() {
return this.cleanupCron;
}
public void setCleanupCron(String cleanupCron) {
this.cleanupCron = cleanupCron;
}
public DatabaseInitializationMode getInitializeSchema() {
return this.initializeSchema;
}
public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
this.initializeSchema = initializeSchema;
}
public FlushMode getFlushMode() {
return this.flushMode;
}
public void setFlushMode(FlushMode flushMode) {
this.flushMode = flushMode;
}
public SaveMode getSaveMode() {
return this.saveMode;
}
public void setSaveMode(SaveMode saveMode) {
this.saveMode = saveMode;
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for Spring Session JDBC.
*/
package org.springframework.boot.session.jdbc.autoconfigure;

View File

@@ -0,0 +1,3 @@
# Depends on Database Initialization Detectors
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.session.jdbc.autoconfigure.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector

View File

@@ -0,0 +1 @@
org.springframework.boot.session.jdbc.autoconfigure.JdbcSessionAutoConfiguration

View File

@@ -0,0 +1,304 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.session.jdbc.autoconfigure;
import java.time.Duration;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.PostgreSqlJdbcIndexedSessionRepositoryCustomizer;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link JdbcSessionAutoConfiguration}.
*
* @author Vedran Pavic
* @author Stephane Nicoll
*/
class JdbcSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests {
@BeforeEach
void prepareRunner() {
this.contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
SessionAutoConfiguration.class, JdbcSessionAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true");
}
@Test
void defaultConfig() {
this.contextRunner.run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
new ServerProperties().getServlet().getSession().getTimeout());
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION");
assertThat(repository).hasFieldOrPropertyWithValue("cleanupCron", "0 * * * * *");
assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION")).isEmpty();
});
}
@Test
void disableDataSourceInitializer() {
this.contextRunner.withPropertyValues("spring.session.jdbc.initialize-schema=never").run((context) -> {
assertThat(context).doesNotHaveBean(JdbcSessionDataSourceScriptDatabaseInitializer.class);
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION");
assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.NEVER);
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION"));
});
}
@Test
void customTimeout() {
this.contextRunner.withPropertyValues("spring.session.timeout=1m").run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", Duration.ofMinutes(1));
});
}
@Test
void customTableName() {
this.contextRunner.withPropertyValues("spring.session.jdbc.table-name=FOO_BAR",
"spring.session.jdbc.schema=classpath:org/springframework/boot/session/jdbc/autoconfigure/custom-schema-h2.sql")
.run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "FOO_BAR");
assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(JdbcOperations.class).queryForList("select * from FOO_BAR")).isEmpty();
});
}
@Test
void customCleanupCron() {
this.contextRunner.withPropertyValues("spring.session.jdbc.cleanup-cron=0 0 12 * * *").run((context) -> {
assertThat(context.getBean(JdbcSessionProperties.class).getCleanupCron()).isEqualTo("0 0 12 * * *");
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("cleanupCron", "0 0 12 * * *");
});
}
@Test
void customFlushMode() {
this.contextRunner.withPropertyValues("spring.session.jdbc.flush-mode=immediate").run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("flushMode", FlushMode.IMMEDIATE);
});
}
@Test
void customSaveMode() {
this.contextRunner.withPropertyValues("spring.session.jdbc.save-mode=on-get-attribute").run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("saveMode", SaveMode.ON_GET_ATTRIBUTE);
});
}
@Test
void sessionDataSourceIsUsedWhenAvailable() {
this.contextRunner.withUserConfiguration(SessionDataSourceConfiguration.class).run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
DataSource sessionDataSource = context.getBean("sessionDataSource", DataSource.class);
assertThat(repository).extracting("jdbcOperations.dataSource").isEqualTo(sessionDataSource);
assertThat(context.getBean(JdbcSessionDataSourceScriptDatabaseInitializer.class))
.hasFieldOrPropertyWithValue("dataSource", sessionDataSource);
assertThatExceptionOfType(BadSqlGrammarException.class)
.isThrownBy(() -> context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION"));
});
}
@Test
void sessionRepositoryBeansDependOnJdbcSessionDataSourceInitializer() {
this.contextRunner.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] sessionRepositoryNames = beanFactory.getBeanNamesForType(JdbcIndexedSessionRepository.class);
assertThat(sessionRepositoryNames).isNotEmpty();
for (String sessionRepositoryName : sessionRepositoryNames) {
assertThat(beanFactory.getBeanDefinition(sessionRepositoryName).getDependsOn())
.contains("jdbcSessionDataSourceScriptDatabaseInitializer");
}
});
}
@Test
void sessionRepositoryBeansDependOnFlyway() {
this.contextRunner.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.withPropertyValues("spring.session.jdbc.initialize-schema=never")
.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] sessionRepositoryNames = beanFactory.getBeanNamesForType(JdbcIndexedSessionRepository.class);
assertThat(sessionRepositoryNames).isNotEmpty();
for (String sessionRepositoryName : sessionRepositoryNames) {
assertThat(beanFactory.getBeanDefinition(sessionRepositoryName).getDependsOn()).contains("flyway",
"flywayInitializer");
}
});
}
@Test
@WithResource(name = "db/changelog/db.changelog-master.yaml", content = "databaseChangeLog:")
void sessionRepositoryBeansDependOnLiquibase() {
this.contextRunner.withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class))
.withPropertyValues("spring.session.jdbc.initialize-schema=never")
.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] sessionRepositoryNames = beanFactory.getBeanNamesForType(JdbcIndexedSessionRepository.class);
assertThat(sessionRepositoryNames).isNotEmpty();
for (String sessionRepositoryName : sessionRepositoryNames) {
assertThat(beanFactory.getBeanDefinition(sessionRepositoryName).getDependsOn())
.contains("liquibase");
}
});
}
@Test
void whenTheUserDefinesTheirOwnJdbcSessionDatabaseInitializerThenTheAutoConfiguredInitializerBacksOff() {
this.contextRunner.withUserConfiguration(CustomJdbcSessionDatabaseInitializerConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(JdbcSessionDataSourceScriptDatabaseInitializer.class)
.doesNotHaveBean("jdbcSessionDataSourceScriptDatabaseInitializer")
.hasBean("customInitializer"));
}
@Test
void whenTheUserDefinesTheirOwnDatabaseInitializerThenTheAutoConfiguredJdbcSessionInitializerRemains() {
this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(JdbcSessionDataSourceScriptDatabaseInitializer.class)
.hasBean("customInitializer"));
}
@Test
void whenTheUserDefinesTheirOwnJdbcIndexedSessionRepositoryCustomizerThenDefaultConfigurationIsOverwritten() {
String expectedCreateSessionAttributeQuery = """
INSERT INTO SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES)
VALUES (?, ?, ?)
ON CONFLICT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME)
DO UPDATE SET ATTRIBUTE_BYTES = EXCLUDED.ATTRIBUTE_BYTES
""";
this.contextRunner.withUserConfiguration(CustomJdbcIndexedSessionRepositoryCustomizerConfiguration.class)
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class))
.run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("createSessionAttributeQuery",
expectedCreateSessionAttributeQuery);
});
}
@Configuration
static class SessionDataSourceConfiguration {
@Bean
@SpringSessionDataSource
DataSource sessionDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:sessiondb");
dataSource.setUsername("sa");
return dataSource;
}
@Bean
@Primary
DataSource mainDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:mem:maindb");
dataSource.setUsername("sa");
return dataSource;
}
}
@Configuration(proxyBeanMethods = false)
static class CustomJdbcSessionDatabaseInitializerConfiguration {
@Bean
JdbcSessionDataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource,
JdbcSessionProperties properties) {
return new JdbcSessionDataSourceScriptDatabaseInitializer(dataSource, properties);
}
}
@Configuration(proxyBeanMethods = false)
static class CustomDatabaseInitializerConfiguration {
@Bean
DataSourceScriptDatabaseInitializer customInitializer(DataSource dataSource) {
return new DataSourceScriptDatabaseInitializer(dataSource, new DatabaseInitializationSettings());
}
}
@Configuration
static class CustomJdbcIndexedSessionRepositoryCustomizerConfiguration {
@Bean
PostgreSqlJdbcIndexedSessionRepositoryCustomizer postgreSqlJdbcIndexedSessionRepositoryCustomizer() {
return new PostgreSqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.session.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JdbcSessionDataSourceScriptDatabaseInitializer}.
*
* @author Stephane Nicoll
*/
class JdbcSessionDataSourceScriptDatabaseInitializerTests {
@Test
void getSettingsWithPlatformDoesNotTouchDataSource() {
DataSource dataSource = mock(DataSource.class);
JdbcSessionProperties properties = new JdbcSessionProperties();
properties.setPlatform("test");
DatabaseInitializationSettings settings = JdbcSessionDataSourceScriptDatabaseInitializer.getSettings(dataSource,
properties);
assertThat(settings.getSchemaLocations())
.containsOnly("classpath:org/springframework/session/jdbc/schema-test.sql");
then(dataSource).shouldHaveNoInteractions();
}
}

View File

@@ -0,0 +1,20 @@
CREATE TABLE FOO_BAR (
SESSION_ID CHAR(36),
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT FOO_BAR_PK PRIMARY KEY (SESSION_ID)
);
CREATE INDEX FOO_BAR_IX1 ON FOO_BAR (LAST_ACCESS_TIME);
CREATE TABLE FOO_BAR_ATTRIBUTES (
SESSION_ID CHAR(36),
ATTRIBUTE_NAME VARCHAR(100),
ATTRIBUTE_BYTES LONGVARBINARY,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES FOO_BAR(SESSION_ID) ON DELETE CASCADE
);
CREATE INDEX FOO_BAR_ATTRIBUTES_IX1 ON FOO_BAR_ATTRIBUTES (SESSION_ID);