Compare commits

..

20 Commits

Author SHA1 Message Date
Eleftheria Stein
cfc1a1e7ce Release 2.5.0-M1 2021-01-19 13:55:51 +01:00
Eleftheria Stein
e17d0cc1d9 Upgrade test dependencies 2021-01-18 12:54:54 +01:00
Eleftheria Stein
0a0766e4a8 Upgrade Hazelcast 4 to 4.1.1
Closes gh-1761
2021-01-18 12:43:34 +01:00
Eleftheria Stein
4108c77797 Upgrade Hazelcast to 3.12.11
Closes gh-1760
2021-01-18 12:41:02 +01:00
Eleftheria Stein
c015a69a4a Upgrade Spring Data to 2021.0.0-M2
Closes gh-1759
2021-01-18 12:39:30 +01:00
Eleftheria Stein
293cf3f730 Upgrade Spring Security to 5.5.0-M1
Closes gh-1758
2021-01-18 12:37:49 +01:00
Eleftheria Stein
6f79e87c8f Upgrade Spring Framework to 5.3.3
Closes gh-1757
2021-01-18 12:35:29 +01:00
Eleftheria Stein
d74c5b1445 Upgrade Reactor to 2020.0.3
Closes gh-1755
2021-01-18 12:34:01 +01:00
Eleftheria Stein
6075089691 Upgrade samples to Spring Boot 2.4.2
Closes gh-1756
2021-01-18 12:31:54 +01:00
Eleftheria Stein
e7a0924904 Upgrade test dependencies 2021-01-12 13:34:47 +01:00
daisuzz
319f0a97ad Fix example in RedisIndexedSessionRepository and Storage Details section
* Fix example in JavaDocs of RedisIndexedSessionRepository

* Fix example in Storage Details section of documentation
2021-01-04 09:32:44 +01:00
Eleftheria Stein
95de199aa4 Upgrade samples to Spring Boot 2.4.1
Closes gh-1744
2020-12-14 09:56:17 +01:00
Eleftheria Stein
db589b7c29 Update community extensions section of reference manual
Closes gh-1736
2020-12-09 13:02:30 +01:00
Vedran Pavic
2aae51b1a1 Rationalize JDBC integration tests
This commit reduces the JDBC integration tests to only single (latest) version per RDBMS vendor, due to a growing number of integration tests. Additionally, the configuration of most containers is simplified due to improved defaults within the Testcontainers library.
2020-11-28 00:26:17 +01:00
Vedran Pavic
e721efeb85 Optimize insert attribute statement in JdbcIndexedSessionRepository
At present, the SQL statement used to insert a session attribute record contains a nested select statement that verifies the existence of parent record in the session table. Such approach can be susceptible to deadlocks on certain RDMBSs.

This commit optimizes the SQL statement used to insert session attribute so that it doesn't perform a nested select statement.

Closes: #1550
2020-11-28 00:26:17 +01:00
Vedran Pavic
0111c6e686 Provide database specific JdbcIndexedSessionRepository customizers
This commit provides JdbcIndexedSessionRepository customizers for the following SQL dialects:

- PostgreSQL
- MySQL (also used by MariaDB)
- SQL Server
- IBM DB2
- Oracle

These customizers are intended to address the concurrency issues occurring on insert of new session attribute by applying SQL dialect specific SQL upsert/merge statement instead of a generic insert.

Closes: #1213
2020-11-27 23:54:03 +01:00
Eleftheria Stein
07058c0cdf Add artifactory credentials to build 2020-11-17 13:59:06 +01:00
Thomas Heigl
5f5168814d Delay allocating Strings for message channel and body 2020-11-10 08:03:57 -05:00
Vedran Pavic
55502f336d Harmonize Redis key namespace configurations
At present, the RedisSessionRepository#setKeyNamespace expects users to provide the trailing colon (:) character that is used as separator between namespace segments. This is unlike RedisIndexedSessionRepository and ReactiveRedisSessionRepository that apply the separator implicitly in their respective #setRedisKeyNamespace methods.

This commit harmonizes the Redis key namespaces configurations across all Redis-backed session repository implementations.
2020-11-10 06:55:19 -05:00
Eleftheria Stein
0e83e3f1e0 Next development version 2020-11-10 11:48:27 +01:00
39 changed files with 760 additions and 462 deletions

View File

@@ -3,10 +3,9 @@ name: CI
on:
push:
branches:
- 2.4.x
- master
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch: # Manual trigger
env:
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}

View File

@@ -4,13 +4,21 @@ buildscript {
snapshotBuild = version.endsWith('SNAPSHOT')
milestoneBuild = !(releaseBuild || snapshotBuild)
springBootVersion = '2.4.0-M4'
springBootVersion = '2.4.2'
}
repositories {
gradlePluginPortal()
maven { url 'https://repo.spring.io/plugins-release/' }
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven {
url = 'https://repo.spring.io/plugins-snapshot'
if (project.hasProperty('artifactoryUsername')) {
credentials {
username "$artifactoryUsername"
password "$artifactoryPassword"
}
}
}
}
dependencies {

View File

@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
version=2.4.2
version=2.5.0-M1

View File

@@ -3,8 +3,8 @@ dependencyManagement {
mavenBom 'io.projectreactor:reactor-bom:2020.0.3'
mavenBom 'org.junit:junit-bom:5.7.0'
mavenBom 'org.springframework:spring-framework-bom:5.3.3'
mavenBom 'org.springframework.data:spring-data-bom:2020.0.3'
mavenBom 'org.springframework.security:spring-security-bom:5.4.2'
mavenBom 'org.springframework.data:spring-data-bom:2021.0.0-M2'
mavenBom 'org.springframework.security:spring-security-bom:5.5.0-M1'
mavenBom 'org.testcontainers:testcontainers-bom:1.15.1'
}
@@ -27,10 +27,10 @@ dependencyManagement {
dependency 'junit:junit:4.13.1'
dependency 'mysql:mysql-connector-java:8.0.22'
dependency 'org.apache.derby:derby:10.14.2.0'
dependency 'org.assertj:assertj-core:3.18.0'
dependency 'org.assertj:assertj-core:3.18.1'
dependency 'org.hsqldb:hsqldb:2.5.1'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.0'
dependency 'org.mockito:mockito-core:3.5.15'
dependency 'org.mariadb.jdbc:mariadb-java-client:2.7.1'
dependency 'org.mockito:mockito-core:3.7.7'
dependency 'org.postgresql:postgresql:42.2.18'
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.session.data.redis;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -36,6 +37,7 @@ import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.FlushMode;
@@ -86,7 +88,7 @@ import org.springframework.util.Assert;
* details.
*
* <pre>
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr2:attrName someAttrValue2
* HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 maxInactiveInterval 1800 lastAccessedTime 1404360000000 sessionAttr:attrName someAttrValue sessionAttr:attrName2 someAttrValue2
* EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
* APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
* EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
@@ -129,8 +131,8 @@ import org.springframework.util.Assert;
* The {@link RedisIndexedSessionRepository.RedisSession} keeps track of the properties
* that have changed and only updates those. This means if an attribute is written once
* and read many times we only need to write that attribute once. For example, assume the
* session attribute "sessionAttr2" from earlier was updated. The following would be
* executed upon saving:
* session attribute "attrName2" from earlier was updated. The following would be executed
* upon saving:
* </p>
*
* <pre>
@@ -272,10 +274,20 @@ public class RedisIndexedSessionRepository
private String sessionCreatedChannelPrefix;
private byte[] sessionCreatedChannelPrefixBytes;
private String sessionDeletedChannel;
private byte[] sessionDeletedChannelBytes;
private String sessionExpiredChannel;
private byte[] sessionExpiredChannelBytes;
private String expiredKeyPrefix;
private byte[] expiredKeyPrefixBytes;
private final RedisOperations<Object, Object> sessionRedisOperations;
private final RedisSessionExpirationPolicy expirationPolicy;
@@ -381,8 +393,13 @@ public class RedisIndexedSessionRepository
private void configureSessionChannels() {
this.sessionCreatedChannelPrefix = this.namespace + "event:" + this.database + ":created:";
this.sessionCreatedChannelPrefixBytes = this.sessionCreatedChannelPrefix.getBytes();
this.sessionDeletedChannel = "__keyevent@" + this.database + "__:del";
this.sessionDeletedChannelBytes = this.sessionDeletedChannel.getBytes();
this.sessionExpiredChannel = "__keyevent@" + this.database + "__:expired";
this.sessionExpiredChannelBytes = this.sessionExpiredChannel.getBytes();
this.expiredKeyPrefix = this.namespace + "sessions:expires:";
this.expiredKeyPrefixBytes = this.expiredKeyPrefix.getBytes();
}
/**
@@ -501,25 +518,24 @@ public class RedisIndexedSessionRepository
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] messageChannel = message.getChannel();
byte[] messageBody = message.getBody();
String channel = new String(messageChannel);
if (channel.startsWith(this.sessionCreatedChannelPrefix)) {
if (ByteUtils.startsWith(messageChannel, this.sessionCreatedChannelPrefixBytes)) {
// TODO: is this thread safe?
@SuppressWarnings("unchecked")
Map<Object, Object> loaded = (Map<Object, Object>) this.defaultSerializer.deserialize(message.getBody());
handleCreated(loaded, channel);
handleCreated(loaded, new String(messageChannel));
return;
}
String body = new String(messageBody);
if (!body.startsWith(getExpiredKeyPrefix())) {
byte[] messageBody = message.getBody();
if (!ByteUtils.startsWith(messageBody, this.expiredKeyPrefixBytes)) {
return;
}
boolean isDeleted = channel.equals(this.sessionDeletedChannel);
if (isDeleted || channel.equals(this.sessionExpiredChannel)) {
boolean isDeleted = Arrays.equals(messageChannel, this.sessionDeletedChannelBytes);
if (isDeleted || Arrays.equals(messageChannel, this.sessionExpiredChannelBytes)) {
String body = new String(messageBody);
int beginIndex = body.lastIndexOf(":") + 1;
int endIndex = body.length();
String sessionId = body.substring(beginIndex, endIndex);
@@ -611,7 +627,7 @@ public class RedisIndexedSessionRepository
}
private String getExpiredKeyPrefix() {
return this.namespace + "sessions:expires:";
return this.expiredKeyPrefix;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -42,13 +42,13 @@ import org.springframework.util.Assert;
*/
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
private static final String DEFAULT_KEY_NAMESPACE = "spring:session";
private final RedisOperations<String, Object> sessionRedisOperations;
private Duration defaultMaxInactiveInterval = Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
private String keyNamespace = DEFAULT_KEY_NAMESPACE;
private String keyNamespace = DEFAULT_KEY_NAMESPACE + ":";
private FlushMode flushMode = FlushMode.ON_SAVE;
@@ -76,12 +76,23 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
/**
* Set the key namespace.
* @param keyNamespace the key namespace
* @deprecated since 2.4.0 in favor of {@link #setRedisKeyNamespace(String)}
*/
@Deprecated
public void setKeyNamespace(String keyNamespace) {
Assert.hasText(keyNamespace, "keyNamespace must not be empty");
this.keyNamespace = keyNamespace;
}
/**
* Set the Redis key namespace.
* @param namespace the Redis key namespace
*/
public void setRedisKeyNamespace(String namespace) {
Assert.hasText(namespace, "namespace must not be empty");
this.keyNamespace = namespace.trim() + ":";
}
/**
* Set the flush mode.
* @param flushMode the flush mode

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -102,18 +102,36 @@ class RedisSessionRepositoryTests {
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
}
@Test
void setRedisKeyNamespace_ValidNamespace_ShouldSetNamespace() {
this.sessionRepository.setRedisKeyNamespace("test");
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "keyNamespace")).isEqualTo("test:");
}
@Test
void setKeyNamespace_NullNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(null))
.withMessage("keyNamespace must not be empty");
}
@Test
void setRedisKeyNamespace_NullNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setRedisKeyNamespace(null))
.withMessage("namespace must not be empty");
}
@Test
void setKeyNamespace_EmptyNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setKeyNamespace(" "))
.withMessage("keyNamespace must not be empty");
}
@Test
void setRedisKeyNamespace_EmptyNamespace_ShouldThrowException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setRedisKeyNamespace(" "))
.withMessage("namespace must not be empty");
}
@Test
void setFlushMode_ValidFlushMode_ShouldSetFlushMode() {
this.sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
@@ -185,7 +203,7 @@ class RedisSessionRepositoryTests {
@Test
void save_NewSessionAndCustomKeyNamespace_ShouldSaveSession() {
this.sessionRepository.setKeyNamespace("custom:");
this.sessionRepository.setRedisKeyNamespace("custom");
RedisSession session = this.sessionRepository.createSession();
this.sessionRepository.save(session);
String key = "custom:sessions:" + session.getId();

View File

@@ -791,7 +791,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr2:attrName someAttrValue2
sessionAttr:attrName2 someAttrValue2
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
@@ -814,7 +814,7 @@ HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime
maxInactiveInterval 1800 \
lastAccessedTime 1404360000000 \
sessionAttr:attrName someAttrValue \
sessionAttr2:attrName someAttrValue2
sessionAttr:attrName2 someAttrValue2
----
====
@@ -833,7 +833,7 @@ The second session attribute is named `attrName2`, with a value of `someAttrValu
The `Session` instances managed by `RedisIndexedSessionRepository` keeps track of the properties that have changed and updates only those.
This means that, if an attribute is written once and read many times, we need to write that attribute only once.
For example, assume the `sessionAttr2` session attribute from the lsiting in the preceding section was updated.
For example, assume the `attrName2` session attribute from the lsiting in the preceding section was updated.
The following command would be run upon saving:
====
@@ -1406,11 +1406,8 @@ Spring Session is Open Source software released under the https://www.apache.org
|===
| Name | Location
| Spring Session OrientDB
| https://github.com/maseev/spring-session-orientdb
| Spring Session Infinispan
| https://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session
| https://infinispan.org/infinispan-spring-boot/master/spring_boot_starter.html#_enabling_spring_session_support
|===

View File

@@ -31,7 +31,7 @@ artifacts {
dependencies {
compile project(':spring-session-core')
optional "com.hazelcast:hazelcast:4.0.3"
optional "com.hazelcast:hazelcast:4.1.1"
compile "org.springframework:spring-context"
compile "javax.annotation:javax.annotation-api"
@@ -42,7 +42,7 @@ dependencies {
testRuntime "org.junit.jupiter:junit-jupiter-engine"
integrationTestCompile "org.testcontainers:testcontainers"
integrationTestCompile "com.hazelcast:hazelcast:4.0.3"
integrationTestCompile "com.hazelcast:hazelcast:4.1.1"
integrationTestCompile project(":spring-session-hazelcast")
}

View File

@@ -45,7 +45,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@WebAppConfiguration
class ClientServerHazelcast4IndexedSessionRepositoryITests extends AbstractHazelcast4IndexedSessionRepositoryITests {
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.0.2")
private static GenericContainer container = new GenericContainer<>("hazelcast/hazelcast:4.1.1")
.withExposedPorts(5701).withCopyFileToContainer(MountableFile.forClasspathResource("/hazelcast-server.xml"),
"/opt/hazelcast/hazelcast.xml");

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-4.0.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-4.1.xsd">
<network>
<join>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.12.xsd">
xsi:schemaLocation="http://www.hazelcast.com/schema/config https://www.hazelcast.com/schema/config/hazelcast-config-3.13.xsd">
<network>
<join>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -29,8 +29,15 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -38,6 +45,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.util.ReflectionTestUtils;
@@ -45,6 +53,8 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Base class for {@link JdbcIndexedSessionRepository} integration tests.
@@ -57,15 +67,27 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private DataSource dataSource;
@Autowired
private JdbcIndexedSessionRepository repository;
private JdbcOperations jdbcOperations;
private LobHandler lobHandler;
private SecurityContext context;
private SecurityContext changedContext;
@BeforeEach
void setUp() {
this.jdbcOperations = new JdbcTemplate(this.dataSource);
this.lobHandler = new DefaultLobHandler();
this.context = SecurityContextHolder.createEmptyContext();
this.context.setAuthentication(new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(), "na",
AuthorityUtils.createAuthorityList("ROLE_USER")));
@@ -759,6 +781,32 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
}
@Test // gh-1213
void saveNewSessionAttributeConcurrently() {
JdbcSession session = this.repository.createSession();
this.repository.save(session);
String attributeName = "attribute1";
String attributeValue = "value1";
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
this.jdbcOperations.update("INSERT INTO SPRING_SESSION_ATTRIBUTES VALUES (?, ?, ?)", (ps) -> {
ps.setString(1, (String) ReflectionTestUtils.getField(session, "primaryKey"));
ps.setString(2, attributeName);
lobCreator.setBlobAsBytes(ps, 3, "value2".getBytes());
});
}
session.setAttribute(attributeName, attributeValue);
if (this.applicationContext.getBeansOfType(SessionRepositoryCustomizer.class).isEmpty()) {
// without DB specific upsert configured we're seeing duplicate key error
assertThatExceptionOfType(DuplicateKeyException.class).isThrownBy(() -> this.repository.save(session));
}
else {
// with DB specific upsert configured we're fine
assertThatCode(() -> this.repository.save(session)).doesNotThrowAnyException();
assertThat((String) this.repository.findById(session.getId()).getAttribute(attributeName))
.isEqualTo(attributeValue);
}
}
private String getSecurityName() {
return this.context.getAuthentication().getName();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -17,7 +17,6 @@
package org.springframework.session.jdbc;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.testcontainers.containers.Db2Container;
import org.testcontainers.containers.JdbcDatabaseContainer;
@@ -38,169 +37,42 @@ final class DatabaseContainers {
private DatabaseContainers() {
}
static Db211Container db211() {
return new Db211Container();
static Db2Container db2() {
return new Db2Container("ibmcom/db2:11.5.4.0");
}
static MariaDBContainer mariaDb5() {
return new MariaDb5Container();
static MariaDBContainer<?> mariaDb() {
return new MariaDBContainer<>("mariadb:10.5.7");
}
static MariaDBContainer mariaDb10() {
return new MariaDb10Container();
static MySQLContainer<?> mySql() {
return new MySQLContainer<>("mysql:8.0.22");
}
static MySQLContainer mySql5() {
return new MySql5Container();
static OracleContainer oracle() {
return new OracleContainer() {
@Override
protected void configure() {
this.waitStrategy = new LogMessageWaitStrategy().withRegEx(".*DATABASE IS READY TO USE!.*\\s")
.withStartupTimeout(Duration.ofMinutes(10));
addEnv("ORACLE_PWD", getPassword());
}
@Override
protected void waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this);
}
};
}
static MySQLContainer mySql8() {
return new MySql8Container();
static PostgreSQLContainer<?> postgreSql() {
return new PostgreSQLContainer<>("postgres:13.0");
}
static OracleXeContainer oracleXe() {
return new OracleXeContainer();
}
static PostgreSQLContainer postgreSql9() {
return new PostgreSql9Container();
}
static PostgreSQLContainer postgreSql10() {
return new PostgreSql10Container();
}
static PostgreSQLContainer postgreSql11() {
return new PostgreSql11Container();
}
static MSSQLServerContainer sqlServer2017() {
return new SqlServer2017Container();
}
private static class Db211Container extends Db2Container {
Db211Container() {
super("ibmcom/db2:11.5.0.0a");
}
}
private static class MariaDb5Container extends MariaDBContainer<MariaDb5Container> {
MariaDb5Container() {
super("mariadb:5.5.64");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci",
"--innodb_large_prefix", "--innodb_file_format=barracuda", "--innodb-file-per-table");
}
}
private static class MariaDb10Container extends MariaDBContainer<MariaDb10Container> {
MariaDb10Container() {
super("mariadb:10.4.8");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci");
}
}
private static class MySql5Container extends MySQLContainer<MySql5Container> {
MySql5Container() {
super("mysql:5.7.27");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci");
}
@Override
public String getDriverClassName() {
return "com.mysql.cj.jdbc.Driver";
}
}
private static class MySql8Container extends MySQLContainer<MySql8Container> {
MySql8Container() {
super("mysql:8.0.17");
}
@Override
protected void configure() {
super.configure();
setCommand("mysqld", "--default-authentication-plugin=mysql_native_password");
}
@Override
public String getDriverClassName() {
return "com.mysql.cj.jdbc.Driver";
}
}
private static class OracleXeContainer extends OracleContainer {
@Override
protected void configure() {
super.configure();
this.waitStrategy = new LogMessageWaitStrategy().withRegEx(".*DATABASE IS READY TO USE!.*\\s")
.withStartupTimeout(Duration.of(10, ChronoUnit.MINUTES));
setShmSize(1024L * 1024L * 1024L);
addEnv("ORACLE_PWD", getPassword());
}
@Override
protected void waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this);
}
}
private static class PostgreSql9Container extends PostgreSQLContainer<PostgreSql9Container> {
PostgreSql9Container() {
super("postgres:9.6.15");
}
}
private static class PostgreSql10Container extends PostgreSQLContainer<PostgreSql10Container> {
PostgreSql10Container() {
super("postgres:10.10");
}
}
private static class PostgreSql11Container extends PostgreSQLContainer<PostgreSql11Container> {
PostgreSql11Container() {
super("postgres:11.5");
}
}
private static class SqlServer2017Container extends MSSQLServerContainer<SqlServer2017Container> {
SqlServer2017Container() {
super("mcr.microsoft.com/mssql/server:2017-CU16");
}
static MSSQLServerContainer<?> sqlServer() {
return new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04");
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 database with
* {@link Db2JdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class Db2JdbcIndexedSessionRepositoryCustomizerITests extends Db2JdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
Db2JdbcIndexedSessionRepositoryCustomizer db2JdbcIndexedSessionRepositoryCustomizer() {
return new Db2JdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 11.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class Db211JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class Db2JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
Db2Container databaseContainer() {
Db2Container databaseContainer = DatabaseContainers.db211();
Db2Container databaseContainer = DatabaseContainers.db2();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2014-2019 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.MariaDBContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 10.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDb10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
MariaDBContainer databaseContainer() {
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb10();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB database with
* {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDbJdbcIndexedSessionRepositoryCustomizerITests extends MariaDbJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 5.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDb5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class MariaDbJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
MariaDBContainer databaseContainer() {
MariaDBContainer databaseContainer = DatabaseContainers.mariaDb5();
MariaDBContainer<?> databaseContainer() {
MariaDBContainer<?> databaseContainer = DatabaseContainers.mariaDb();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL database with
* {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySqlJdbcIndexedSessionRepositoryCustomizerITests extends MySqlJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,21 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 8.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySql8JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class MySqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
MySQLContainer databaseContainer() {
MySQLContainer databaseContainer = DatabaseContainers.mySql8();
MySQLContainer<?> databaseContainer() {
MySQLContainer<?> databaseContainer = DatabaseContainers.mySql();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using Oracle database with
* {@link OracleJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class OracleJdbcIndexedSessionRepositoryCustomizerITests extends OracleJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
OracleJdbcIndexedSessionRepositoryCustomizer oracleJdbcIndexedSessionRepositoryCustomizer() {
return new OracleJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -54,7 +54,7 @@ class OracleJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcInde
@Bean
OracleContainer databaseContainer() {
OracleContainer databaseContainer = DatabaseContainers.oracleXe();
OracleContainer databaseContainer = DatabaseContainers.oracle();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2014-2019 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.PostgreSQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 10.x
* database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql10();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.postgreSql();
}
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2014-2019 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.PostgreSQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 11.x
* database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql11JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql11();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.postgreSql();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database
* with {@link PostgreSqlJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSqlJdbcIndexedSessionRepositoryCustomizerITests extends PostgreSqlJdbcIndexedSessionRepositoryITests {
@Configuration
static class CustomizerConfig extends Config {
@Bean
PostgreSqlJdbcIndexedSessionRepositoryCustomizer postgreSqlJdbcIndexedSessionRepositoryCustomizer() {
return new PostgreSqlJdbcIndexedSessionRepositoryCustomizer();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -27,22 +27,21 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 9.x
* database.
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql9JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class PostgreSqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
@Bean
PostgreSQLContainer databaseContainer() {
PostgreSQLContainer databaseContainer = DatabaseContainers.postgreSql9();
PostgreSQLContainer<?> databaseContainer() {
PostgreSQLContainer<?> databaseContainer = DatabaseContainers.postgreSql();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -17,38 +17,30 @@
package org.springframework.session.jdbc;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.MySQLContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 5.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using SQL Server database
* with {@link SqlServerJdbcIndexedSessionRepositoryCustomizer}.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySql5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
class SqlServerJdbcIndexedSessionRepositoryCustomizerITests extends SqlServerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {
static class CustomizerConfig extends Config {
@Bean
MySQLContainer databaseContainer() {
MySQLContainer databaseContainer = DatabaseContainers.mySql5();
databaseContainer.start();
return databaseContainer;
}
@Bean
ResourceDatabasePopulator databasePopulator() {
return DatabasePopulators.mySql();
SqlServerJdbcIndexedSessionRepositoryCustomizer sqlServerJdbcIndexedSessionRepositoryCustomizer() {
return new SqlServerJdbcIndexedSessionRepositoryCustomizer();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -41,8 +41,8 @@ class SqlServerJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcI
static class Config extends BaseContainerConfig {
@Bean
MSSQLServerContainer databaseContainer() {
MSSQLServerContainer databaseContainer = DatabaseContainers.sqlServer2017();
MSSQLServerContainer<?> databaseContainer() {
MSSQLServerContainer<?> databaseContainer = DatabaseContainers.sqlServer();
databaseContainer.start();
return databaseContainer;
}

View File

@@ -1,2 +1,2 @@
ibmcom/db2:11.5.0.0a
mcr.microsoft.com/mssql/server:2017-CU16
ibmcom/db2:11.5.4.0
mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies IBM DB2 specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class Db2JdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA "
+ "USING ( "
+ " VALUES (?, ?, ?) "
+ ") A (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "ON (SA.SESSION_PRIMARY_ID = A.SESSION_PRIMARY_ID and SA.ATTRIBUTE_NAME = A.ATTRIBUTE_NAME) "
+ "WHEN MATCHED THEN "
+ " UPDATE SET ATTRIBUTE_BYTES = A.ATTRIBUTE_BYTES "
+ "WHEN NOT MATCHED THEN "
+ " INSERT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ " VALUES (A.SESSION_PRIMARY_ID, A.ATTRIBUTE_NAME, A.ATTRIBUTE_BYTES)";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -40,6 +40,8 @@ import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.ResultSetExtractor;
@@ -139,55 +141,64 @@ public class JdbcIndexedSessionRepository
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
// @formatter:off
private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) "
private static final String CREATE_SESSION_QUERY = ""
+ "INSERT INTO %TABLE_NAME% (PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "SELECT PRIMARY_ID, ?, ? "
+ "FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "VALUES (?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
private static final String GET_SESSION_QUERY = ""
+ "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "LEFT JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? "
private static final String UPDATE_SESSION_QUERY = ""
+ "UPDATE %TABLE_NAME% "
+ "SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? "
+ "WHERE PRIMARY_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? "
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = ""
+ "UPDATE %TABLE_NAME%_ATTRIBUTES "
+ "SET ATTRIBUTE_BYTES = ? "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES "
private static final String DELETE_SESSION_ATTRIBUTE_QUERY = ""
+ "DELETE FROM %TABLE_NAME%_ATTRIBUTES "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% "
private static final String DELETE_SESSION_QUERY = ""
+ "DELETE FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = ""
+ "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "LEFT JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.PRINCIPAL_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% "
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = ""
+ "DELETE FROM %TABLE_NAME% "
+ "WHERE EXPIRY_TIME < ?";
// @formatter:on
@@ -268,7 +279,7 @@ public class JdbcIndexedSessionRepository
*/
public void setCreateSessionQuery(String createSessionQuery) {
Assert.hasText(createSessionQuery, "Query must not be empty");
this.createSessionQuery = createSessionQuery;
this.createSessionQuery = getQuery(createSessionQuery);
}
/**
@@ -277,7 +288,7 @@ public class JdbcIndexedSessionRepository
*/
public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) {
Assert.hasText(createSessionAttributeQuery, "Query must not be empty");
this.createSessionAttributeQuery = createSessionAttributeQuery;
this.createSessionAttributeQuery = getQuery(createSessionAttributeQuery);
}
/**
@@ -286,7 +297,7 @@ public class JdbcIndexedSessionRepository
*/
public void setGetSessionQuery(String getSessionQuery) {
Assert.hasText(getSessionQuery, "Query must not be empty");
this.getSessionQuery = getSessionQuery;
this.getSessionQuery = getQuery(getSessionQuery);
}
/**
@@ -295,7 +306,7 @@ public class JdbcIndexedSessionRepository
*/
public void setUpdateSessionQuery(String updateSessionQuery) {
Assert.hasText(updateSessionQuery, "Query must not be empty");
this.updateSessionQuery = updateSessionQuery;
this.updateSessionQuery = getQuery(updateSessionQuery);
}
/**
@@ -304,7 +315,7 @@ public class JdbcIndexedSessionRepository
*/
public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) {
Assert.hasText(updateSessionAttributeQuery, "Query must not be empty");
this.updateSessionAttributeQuery = updateSessionAttributeQuery;
this.updateSessionAttributeQuery = getQuery(updateSessionAttributeQuery);
}
/**
@@ -313,7 +324,7 @@ public class JdbcIndexedSessionRepository
*/
public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) {
Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty");
this.deleteSessionAttributeQuery = deleteSessionAttributeQuery;
this.deleteSessionAttributeQuery = getQuery(deleteSessionAttributeQuery);
}
/**
@@ -322,7 +333,7 @@ public class JdbcIndexedSessionRepository
*/
public void setDeleteSessionQuery(String deleteSessionQuery) {
Assert.hasText(deleteSessionQuery, "Query must not be empty");
this.deleteSessionQuery = deleteSessionQuery;
this.deleteSessionQuery = getQuery(deleteSessionQuery);
}
/**
@@ -331,7 +342,7 @@ public class JdbcIndexedSessionRepository
*/
public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) {
Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty");
this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery;
this.listSessionsByPrincipalNameQuery = getQuery(listSessionsByPrincipalNameQuery);
}
/**
@@ -340,7 +351,7 @@ public class JdbcIndexedSessionRepository
*/
public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) {
Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty");
this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery;
this.deleteSessionsByExpiryTimeQuery = getQuery(deleteSessionsByExpiryTimeQuery);
}
/**
@@ -463,30 +474,49 @@ public class JdbcIndexedSessionRepository
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
try {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
lobCreator.setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
}
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
lobCreator.setBlobAsBytes(ps, 3, serialize(session.getAttribute(attributeName)));
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
});
}
catch (DuplicateKeyException ex) {
throw ex;
}
catch (DataIntegrityViolationException ex) {
// parent record not found - we are ignoring this error because we
// assume that a concurrent request has removed the session
}
}
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
lobCreator.setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
try {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
lobCreator.setBlobAsBytes(ps, 3, serialize(session.getAttribute(attributeName)));
});
}
catch (DuplicateKeyException ex) {
throw ex;
}
catch (DataIntegrityViolationException ex) {
// parent record not found - we are ignoring this error because we
// assume that a concurrent request has removed the session
}
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies MySQL specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class MySqlJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "VALUES (?, ?, ?) "
+ "ON DUPLICATE KEY UPDATE ATTRIBUTE_BYTES = VALUES(ATTRIBUTE_BYTES)";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies Oracle specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class OracleJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA "
+ "USING ( "
+ " SELECT ? AS SESSION_PRIMARY_ID, ? AS ATTRIBUTE_NAME, ? AS ATTRIBUTE_BYTES "
+ " FROM DUAL "
+ ") A "
+ "ON (SA.SESSION_PRIMARY_ID = A.SESSION_PRIMARY_ID and SA.ATTRIBUTE_NAME = A.ATTRIBUTE_NAME) "
+ "WHEN MATCHED THEN "
+ " UPDATE SET ATTRIBUTE_BYTES = A.ATTRIBUTE_BYTES "
+ "WHEN NOT MATCHED THEN "
+ " INSERT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ " VALUES (A.SESSION_PRIMARY_ID, A.ATTRIBUTE_NAME, A.ATTRIBUTE_BYTES)";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies PostgreSQL specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class PostgreSqlJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "INSERT INTO %TABLE_NAME%_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "VALUES (?, ?, ?) "
+ "ON CONFLICT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME) "
+ "DO UPDATE SET ATTRIBUTE_BYTES = EXCLUDED.ATTRIBUTE_BYTES";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2014-2020 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.session.jdbc;
import org.springframework.session.config.SessionRepositoryCustomizer;
/**
* A {@link SessionRepositoryCustomizer} implementation that applies SQL Server specific
* optimized SQL statements to {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.5.0
*/
public class SqlServerJdbcIndexedSessionRepositoryCustomizer
implements SessionRepositoryCustomizer<JdbcIndexedSessionRepository> {
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = ""
+ "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA "
+ "USING ( "
+ " VALUES (?, ?, ?) "
+ ") A (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "ON (SA.SESSION_PRIMARY_ID = A.SESSION_PRIMARY_ID and SA.ATTRIBUTE_NAME = A.ATTRIBUTE_NAME) "
+ "WHEN MATCHED THEN "
+ " UPDATE SET ATTRIBUTE_BYTES = A.ATTRIBUTE_BYTES "
+ "WHEN NOT MATCHED THEN "
+ " INSERT (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ " VALUES (A.SESSION_PRIMARY_ID, A.ATTRIBUTE_NAME, A.ATTRIBUTE_BYTES);";
// @formatter:on
@Override
public void customize(JdbcIndexedSessionRepository sessionRepository) {
sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2019 the original author or authors.
* Copyright 2014-2020 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.
@@ -290,9 +290,9 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION ("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -306,9 +306,9 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION ("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(BatchPreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -321,7 +321,7 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -335,7 +335,7 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(BatchPreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -424,7 +424,7 @@ class JdbcIndexedSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -679,7 +679,7 @@ class JdbcIndexedSessionRepositoryTests {
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
String attrName = "someAttribute";
session.setAttribute(attrName, "someValue");
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES ("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}

View File

@@ -1,6 +1,6 @@
dependencyManagement {
imports {
mavenBom 'com.fasterxml.jackson:jackson-bom:2.11.3'
mavenBom 'com.fasterxml.jackson:jackson-bom:2.12.1'
}
dependencies {

View File

@@ -6,7 +6,7 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.springframework.boot:spring-boot-starter-security"
compile "com.hazelcast:hazelcast:4.0.3"
compile "com.hazelcast:hazelcast:4.1.1"
compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect"
compile "org.webjars:bootstrap"
compile "org.webjars:html5shiv"