Add Spring Session JDBC database initializer

See gh-5879
pull/6241/merge
Vedran Pavic 9 years ago committed by Stephane Nicoll
parent 2b970f9efc
commit a251ea8bc7

@ -21,8 +21,10 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration; import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration;
@ -31,6 +33,7 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi
* *
* @author Eddú Meléndez * @author Eddú Meléndez
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Vedran Pavic
*/ */
@Configuration @Configuration
@ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class)
@ -38,6 +41,14 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi
@Conditional(SessionCondition.class) @Conditional(SessionCondition.class)
class JdbcSessionConfiguration { class JdbcSessionConfiguration {
@Bean
@ConditionalOnMissingBean
public JdbcSessionDatabaseInitializer jdbcSessionDatabaseInitializer(
SessionProperties properties, DataSource dataSource,
ResourceLoader resourceLoader) {
return new JdbcSessionDatabaseInitializer(properties, dataSource, resourceLoader);
}
@Configuration @Configuration
public static class SpringBootJdbcHttpSessionConfiguration public static class SpringBootJdbcHttpSessionConfiguration
extends JdbcHttpSessionConfiguration { extends JdbcHttpSessionConfiguration {

@ -0,0 +1,84 @@
/*
* Copyright 2012-2016 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
*
* http://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.autoconfigure.session;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.util.Assert;
/**
* Initializer for Spring Session schema.
*
* @author Vedran Pavic
* @since 1.4.0
*/
public class JdbcSessionDatabaseInitializer {
private SessionProperties properties;
private DataSource dataSource;
private ResourceLoader resourceLoader;
public JdbcSessionDatabaseInitializer(SessionProperties properties,
DataSource dataSource, ResourceLoader resourceLoader) {
Assert.notNull(properties, "SessionProperties must not be null");
Assert.notNull(dataSource, "DataSource must not be null");
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.properties = properties;
this.dataSource = dataSource;
this.resourceLoader = resourceLoader;
}
@PostConstruct
protected void initialize() {
if (!this.properties.getJdbc().getInitializer().isEnabled()) {
return;
}
String platform = getDatabaseType();
if ("hsql".equals(platform)) {
platform = "hsqldb";
}
if ("postgres".equals(platform)) {
platform = "postgresql";
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
String schemaLocation = this.properties.getJdbc().getSchema();
schemaLocation = schemaLocation.replace("@@platform@@", platform);
populator.addScript(this.resourceLoader.getResource(schemaLocation));
populator.setContinueOnError(true);
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
private String getDatabaseType() {
try {
String databaseProductName = JdbcUtils.extractDatabaseMetaData(
this.dataSource, "getDatabaseProductName").toString();
return JdbcUtils.commonDatabaseName(databaseProductName).toLowerCase();
}
catch (MetaDataAccessException ex) {
throw new IllegalStateException("Unable to detect database type", ex);
}
}
}

@ -26,6 +26,7 @@ import org.springframework.session.data.redis.RedisFlushMode;
* *
* @author Tommy Ludwig * @author Tommy Ludwig
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Vedran Pavic
* @since 1.4.0 * @since 1.4.0
*/ */
@ConfigurationProperties("spring.session") @ConfigurationProperties("spring.session")
@ -103,11 +104,29 @@ public class SessionProperties {
public static class Jdbc { public static class Jdbc {
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/"
+ "session/jdbc/schema-@@platform@@.sql";
/**
* Path to the SQL file to use to initialize the database schema.
*/
private String schema = DEFAULT_SCHEMA_LOCATION;
/** /**
* Name of database table used to store sessions. * Name of database table used to store sessions.
*/ */
private String tableName = "SPRING_SESSION"; private String tableName = "SPRING_SESSION";
private final Initializer initializer = new Initializer();
public String getSchema() {
return this.schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getTableName() { public String getTableName() {
return this.tableName; return this.tableName;
} }
@ -116,6 +135,27 @@ public class SessionProperties {
this.tableName = tableName; this.tableName = tableName;
} }
public Initializer getInitializer() {
return initializer;
}
public static class Initializer {
/**
* Create the required session tables on startup if necessary.
*/
private boolean enabled = true;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
} }
public static class Mongo { public static class Mongo {

@ -0,0 +1,109 @@
/*
* Copyright 2012-2016 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
*
* http://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.autoconfigure.session;
import java.util.Arrays;
import javax.persistence.EntityManagerFactory;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
/**
* JDBC specific tests for {@link SessionAutoConfiguration}.
*
* @author Vedran Pavic
*/
public class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfigurationTests {
@Rule
public ExpectedException expected = ExpectedException.none();
@Test
public void defaultConfig() {
load(Arrays.asList(EmbeddedDataSourceConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class),
"spring.session.store-type=jdbc");
JdbcOperationsSessionRepository repository = validateSessionRepository(
JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName"))
.isEqualTo("SPRING_SESSION");
assertThat(this.context.getBean(JdbcOperations.class)
.queryForList("select * from SPRING_SESSION")).isEmpty();
}
@Test
public void usingJpa() {
load(Arrays.<Class<?>>asList(EmbeddedDataSourceConfiguration.class,
HibernateJpaAutoConfiguration.class),
"spring.session.store-type=jdbc");
PlatformTransactionManager transactionManager = this.context
.getBean(PlatformTransactionManager.class);
assertThat(transactionManager.toString().contains("JpaTransactionManager"))
.isTrue();
assertThat(this.context.getBean(EntityManagerFactory.class)).isNotNull();
JdbcOperationsSessionRepository repository = validateSessionRepository(
JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName"))
.isEqualTo("SPRING_SESSION");
assertThat(this.context.getBean(JdbcOperations.class)
.queryForList("select * from SPRING_SESSION")).isEmpty();
}
@Test
public void disableDatabaseInitializer() {
load(Arrays.asList(EmbeddedDataSourceConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class),
"spring.session.store-type=jdbc",
"spring.session.jdbc.initializer.enabled=false");
JdbcOperationsSessionRepository repository = validateSessionRepository(
JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName"))
.isEqualTo("SPRING_SESSION");
this.expected.expect(BadSqlGrammarException.class);
assertThat(this.context.getBean(JdbcOperations.class)
.queryForList("select * from SPRING_SESSION")).isEmpty();
}
@Test
public void customTableName() {
load(Arrays.asList(EmbeddedDataSourceConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class),
"spring.session.store-type=jdbc",
"spring.session.jdbc.table-name=FOO_BAR",
"spring.session.jdbc.schema=classpath:session/custom-schema-h2.sql");
JdbcOperationsSessionRepository repository = validateSessionRepository(
JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName"))
.isEqualTo("FOO_BAR");
assertThat(this.context.getBean(JdbcOperations.class)
.queryForList("select * from FOO_BAR")).isEmpty();
}
}

@ -29,8 +29,6 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -39,7 +37,6 @@ import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSessionRepository; import org.springframework.session.MapSessionRepository;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
import org.springframework.session.data.mongo.MongoOperationsSessionRepository; import org.springframework.session.data.mongo.MongoOperationsSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -106,29 +103,6 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
assertThat(getSessionTimeout(repository)).isNull(); assertThat(getSessionTimeout(repository)).isNull();
} }
@Test
public void jdbcSessionStore() {
load(Arrays.asList(EmbeddedDataSourceConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class),
"spring.session.store-type=jdbc");
JdbcOperationsSessionRepository repository = validateSessionRepository(
JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName"))
.isEqualTo("SPRING_SESSION");
}
@Test
public void jdbcSessionStoreCustomTableName() {
load(Arrays.asList(EmbeddedDataSourceConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class),
"spring.session.store-type=jdbc",
"spring.session.jdbc.table-name=FOO_BAR");
JdbcOperationsSessionRepository repository = validateSessionRepository(
JdbcOperationsSessionRepository.class);
assertThat(new DirectFieldAccessor(repository).getPropertyValue("tableName"))
.isEqualTo("FOO_BAR");
}
@Test @Test
public void hazelcastSessionStore() { public void hazelcastSessionStore() {
load(Collections.<Class<?>>singletonList(HazelcastConfiguration.class), load(Collections.<Class<?>>singletonList(HazelcastConfiguration.class),

@ -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);

@ -367,6 +367,8 @@ content into your application; rather pick only the properties that you need.
# SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SessionProperties.{sc-ext}[SessionProperties]) # SPRING SESSION ({sc-spring-boot-autoconfigure}/session/SessionProperties.{sc-ext}[SessionProperties])
spring.session.hazelcast.map-name=spring:session:sessions # Name of the map used to store sessions. spring.session.hazelcast.map-name=spring:session:sessions # Name of the map used to store sessions.
spring.session.jdbc.initializer.enabled=true # Create the required session tables on startup if necessary.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of database table used to store sessions. spring.session.jdbc.table-name=SPRING_SESSION # Name of database table used to store sessions.
spring.session.mongo.collection-name=sessions # Collection name used to store sessions. spring.session.mongo.collection-name=sessions # Collection name used to store sessions.
spring.session.redis.flush-mode= # Flush mode for the Redis sessions. spring.session.redis.flush-mode= # Flush mode for the Redis sessions.

Loading…
Cancel
Save