Skip to content

Commit e4862f2

Browse files
committed
Provide database specific JdbcIndexedSessionRepository customizers
This commit provides JdbcIndexedSessionRepository customizers for the following SQL dialects: - Standard SQL (used by IBM DB2, Oracle, SQL Server) - PostgreSQL - MySQL (also used by MariaDB) 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
1 parent b722b12 commit e4862f2

11 files changed

+458
-11
lines changed

spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2019 the original author or authors.
2+
* Copyright 2014-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,22 +29,32 @@
2929
import org.junit.jupiter.api.Test;
3030

3131
import org.springframework.beans.factory.annotation.Autowired;
32+
import org.springframework.context.ApplicationContext;
3233
import org.springframework.context.annotation.Bean;
34+
import org.springframework.dao.DuplicateKeyException;
35+
import org.springframework.jdbc.core.JdbcOperations;
36+
import org.springframework.jdbc.core.JdbcTemplate;
3337
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
38+
import org.springframework.jdbc.support.lob.DefaultLobHandler;
39+
import org.springframework.jdbc.support.lob.LobCreator;
40+
import org.springframework.jdbc.support.lob.LobHandler;
3441
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
3542
import org.springframework.security.core.Authentication;
3643
import org.springframework.security.core.authority.AuthorityUtils;
3744
import org.springframework.security.core.context.SecurityContext;
3845
import org.springframework.security.core.context.SecurityContextHolder;
3946
import org.springframework.session.FindByIndexNameSessionRepository;
4047
import org.springframework.session.MapSession;
48+
import org.springframework.session.config.SessionRepositoryCustomizer;
4149
import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
4250
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
4351
import org.springframework.test.util.ReflectionTestUtils;
4452
import org.springframework.transaction.PlatformTransactionManager;
4553
import org.springframework.transaction.annotation.Transactional;
4654

4755
import static org.assertj.core.api.Assertions.assertThat;
56+
import static org.assertj.core.api.Assertions.assertThatCode;
57+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4858

4959
/**
5060
* Base class for {@link JdbcIndexedSessionRepository} integration tests.
@@ -57,15 +67,27 @@ abstract class AbstractJdbcIndexedSessionRepositoryITests {
5767

5868
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
5969

70+
@Autowired
71+
private ApplicationContext applicationContext;
72+
73+
@Autowired
74+
private DataSource dataSource;
75+
6076
@Autowired
6177
private JdbcIndexedSessionRepository repository;
6278

79+
private JdbcOperations jdbcOperations;
80+
81+
private LobHandler lobHandler;
82+
6383
private SecurityContext context;
6484

6585
private SecurityContext changedContext;
6686

6787
@BeforeEach
6888
void setUp() {
89+
this.jdbcOperations = new JdbcTemplate(this.dataSource);
90+
this.lobHandler = new DefaultLobHandler();
6991
this.context = SecurityContextHolder.createEmptyContext();
7092
this.context.setAuthentication(new UsernamePasswordAuthenticationToken("username-" + UUID.randomUUID(), "na",
7193
AuthorityUtils.createAuthorityList("ROLE_USER")));
@@ -759,6 +781,32 @@ void saveWithLargeAttribute() {
759781
assertThat((byte[]) session.getAttribute(attributeName)).hasSize(arraySize);
760782
}
761783

784+
@Test // gh-1213
785+
void saveNewSessionAttributeConcurrently() {
786+
JdbcSession session = this.repository.createSession();
787+
this.repository.save(session);
788+
String attributeName = "attribute1";
789+
String attributeValue = "value1";
790+
try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
791+
this.jdbcOperations.update("INSERT INTO SPRING_SESSION_ATTRIBUTES VALUES (?, ?, ?)", (ps) -> {
792+
ps.setString(1, (String) ReflectionTestUtils.getField(session, "primaryKey"));
793+
ps.setString(2, attributeName);
794+
lobCreator.setBlobAsBytes(ps, 3, "value2".getBytes());
795+
});
796+
}
797+
session.setAttribute(attributeName, attributeValue);
798+
if (this.applicationContext.getBeansOfType(SessionRepositoryCustomizer.class).isEmpty()) {
799+
// without DB specific upsert configured we're seeing duplicate key error
800+
assertThatExceptionOfType(DuplicateKeyException.class).isThrownBy(() -> this.repository.save(session));
801+
}
802+
else {
803+
// with DB specific upsert configured we're fine
804+
assertThatCode(() -> this.repository.save(session)).doesNotThrowAnyException();
805+
assertThat((String) this.repository.findById(session.getId()).getAttribute(attributeName))
806+
.isEqualTo(attributeValue);
807+
}
808+
}
809+
762810
private String getSecurityName() {
763811
return this.context.getAuthentication().getName();
764812
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2014-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc;
18+
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
22+
/**
23+
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 11.x database
24+
* with {@link StandardSqlJdbcIndexedSessionRepositoryCustomizer}.
25+
*
26+
* @author Vedran Pavic
27+
*/
28+
class Db211JdbcIndexedSessionRepositoryCustomizerITests extends Db211JdbcIndexedSessionRepositoryITests {
29+
30+
@Configuration
31+
static class CustomizerConfig extends Config {
32+
33+
@Bean
34+
StandardSqlJdbcIndexedSessionRepositoryCustomizer standardSqlJdbcIndexedSessionRepositoryCustomizer() {
35+
return new StandardSqlJdbcIndexedSessionRepositoryCustomizer();
36+
}
37+
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2014-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc;
18+
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.test.context.ContextConfiguration;
24+
import org.springframework.test.context.junit.jupiter.SpringExtension;
25+
import org.springframework.test.context.web.WebAppConfiguration;
26+
27+
/**
28+
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 10.x database
29+
* with {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
30+
*
31+
* @author Vedran Pavic
32+
*/
33+
@ExtendWith(SpringExtension.class)
34+
@WebAppConfiguration
35+
@ContextConfiguration
36+
class MariaDb10JdbcIndexedSessionRepositoryCustomizerITests extends MariaDb10JdbcIndexedSessionRepositoryITests {
37+
38+
@Configuration
39+
static class CustomizerConfig extends Config {
40+
41+
@Bean
42+
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
43+
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
44+
}
45+
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2014-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc;
18+
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.test.context.ContextConfiguration;
24+
import org.springframework.test.context.junit.jupiter.SpringExtension;
25+
import org.springframework.test.context.web.WebAppConfiguration;
26+
27+
/**
28+
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 8.x database
29+
* with {@link MySqlJdbcIndexedSessionRepositoryCustomizer}.
30+
*
31+
* @author Vedran Pavic
32+
*/
33+
@ExtendWith(SpringExtension.class)
34+
@WebAppConfiguration
35+
@ContextConfiguration
36+
class MySql8JdbcIndexedSessionRepositoryCustomizerITests extends MySql8JdbcIndexedSessionRepositoryITests {
37+
38+
@Configuration
39+
static class CustomizerConfig extends Config {
40+
41+
@Bean
42+
MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() {
43+
return new MySqlJdbcIndexedSessionRepositoryCustomizer();
44+
}
45+
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2014-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc;
18+
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
22+
/**
23+
* Integration tests for {@link JdbcIndexedSessionRepository} using Oracle database with
24+
* {@link StandardSqlJdbcIndexedSessionRepositoryCustomizer}.
25+
*
26+
* @author Vedran Pavic
27+
*/
28+
class OracleJdbcIndexedSessionRepositoryCustomizerITests extends OracleJdbcIndexedSessionRepositoryITests {
29+
30+
@Configuration
31+
static class CustomizerConfig extends Config {
32+
33+
@Bean
34+
StandardSqlJdbcIndexedSessionRepositoryCustomizer standardSqlJdbcIndexedSessionRepositoryCustomizer() {
35+
return new StandardSqlJdbcIndexedSessionRepositoryCustomizer();
36+
}
37+
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2014-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc;
18+
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
22+
/**
23+
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 11.x
24+
* database with {@link PostgreSqlJdbcIndexedSessionRepositoryCustomizer}.
25+
*
26+
* @author Vedran Pavic
27+
*/
28+
class PostgreSql11JdbcIndexedSessionRepositoryCustomizerITests extends PostgreSql11JdbcIndexedSessionRepositoryITests {
29+
30+
@Configuration
31+
static class CustomizerConfig extends Config {
32+
33+
@Bean
34+
PostgreSqlJdbcIndexedSessionRepositoryCustomizer postgreSqlJdbcIndexedSessionRepositoryCustomizer() {
35+
return new PostgreSqlJdbcIndexedSessionRepositoryCustomizer();
36+
}
37+
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2014-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session.jdbc;
18+
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
22+
/**
23+
* Integration tests for {@link JdbcIndexedSessionRepository} using SQL Server database
24+
* with {@link StandardSqlJdbcIndexedSessionRepositoryCustomizer}.
25+
*
26+
* @author Vedran Pavic
27+
*/
28+
class SqlServerJdbcIndexedSessionRepositoryCustomizerITests extends OracleJdbcIndexedSessionRepositoryITests {
29+
30+
@Configuration
31+
static class CustomizerConfig extends Config {
32+
33+
@Bean
34+
StandardSqlJdbcIndexedSessionRepositoryCustomizer standardSqlJdbcIndexedSessionRepositoryCustomizer() {
35+
return new StandardSqlJdbcIndexedSessionRepositoryCustomizer();
36+
}
37+
38+
}
39+
40+
}

0 commit comments

Comments
 (0)