diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java index 9dc583db..15e9b16a 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/AbstractJdbcIndexedSessionRepositoryITests.java @@ -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(); } diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcIndexedSessionRepositoryCustomizerITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcIndexedSessionRepositoryCustomizerITests.java new file mode 100644 index 00000000..227fd4b3 --- /dev/null +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/Db211JdbcIndexedSessionRepositoryCustomizerITests.java @@ -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 11.x database + * with {@link Db2JdbcIndexedSessionRepositoryCustomizer}. + * + * @author Vedran Pavic + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration +class Db211JdbcIndexedSessionRepositoryCustomizerITests extends Db211JdbcIndexedSessionRepositoryITests { + + @Configuration + static class CustomizerConfig extends Config { + + @Bean + Db2JdbcIndexedSessionRepositoryCustomizer db2JdbcIndexedSessionRepositoryCustomizer() { + return new Db2JdbcIndexedSessionRepositoryCustomizer(); + } + + } + +} diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcIndexedSessionRepositoryCustomizerITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcIndexedSessionRepositoryCustomizerITests.java new file mode 100644 index 00000000..c730e262 --- /dev/null +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MariaDb10JdbcIndexedSessionRepositoryCustomizerITests.java @@ -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 10.x database + * with {@link MySqlJdbcIndexedSessionRepositoryCustomizer}. + * + * @author Vedran Pavic + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration +class MariaDb10JdbcIndexedSessionRepositoryCustomizerITests extends MariaDb10JdbcIndexedSessionRepositoryITests { + + @Configuration + static class CustomizerConfig extends Config { + + @Bean + MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() { + return new MySqlJdbcIndexedSessionRepositoryCustomizer(); + } + + } + +} diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcIndexedSessionRepositoryCustomizerITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcIndexedSessionRepositoryCustomizerITests.java new file mode 100644 index 00000000..09cf6f2d --- /dev/null +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/MySql8JdbcIndexedSessionRepositoryCustomizerITests.java @@ -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 8.x database + * with {@link MySqlJdbcIndexedSessionRepositoryCustomizer}. + * + * @author Vedran Pavic + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration +class MySql8JdbcIndexedSessionRepositoryCustomizerITests extends MySql8JdbcIndexedSessionRepositoryITests { + + @Configuration + static class CustomizerConfig extends Config { + + @Bean + MySqlJdbcIndexedSessionRepositoryCustomizer mySqlJdbcIndexedSessionRepositoryCustomizer() { + return new MySqlJdbcIndexedSessionRepositoryCustomizer(); + } + + } + +} diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryCustomizerITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryCustomizerITests.java new file mode 100644 index 00000000..b44bd54e --- /dev/null +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryCustomizerITests.java @@ -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(); + } + + } + +} diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcIndexedSessionRepositoryCustomizerITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcIndexedSessionRepositoryCustomizerITests.java new file mode 100644 index 00000000..c1296e13 --- /dev/null +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSql11JdbcIndexedSessionRepositoryCustomizerITests.java @@ -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 11.x + * database with {@link PostgreSqlJdbcIndexedSessionRepositoryCustomizer}. + * + * @author Vedran Pavic + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration +class PostgreSql11JdbcIndexedSessionRepositoryCustomizerITests extends PostgreSql11JdbcIndexedSessionRepositoryITests { + + @Configuration + static class CustomizerConfig extends Config { + + @Bean + PostgreSqlJdbcIndexedSessionRepositoryCustomizer postgreSqlJdbcIndexedSessionRepositoryCustomizer() { + return new PostgreSqlJdbcIndexedSessionRepositoryCustomizer(); + } + + } + +} diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryCustomizerITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryCustomizerITests.java new file mode 100644 index 00000000..568d09ba --- /dev/null +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryCustomizerITests.java @@ -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 SQL Server database + * with {@link SqlServerJdbcIndexedSessionRepositoryCustomizer}. + * + * @author Vedran Pavic + */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration +class SqlServerJdbcIndexedSessionRepositoryCustomizerITests extends SqlServerJdbcIndexedSessionRepositoryITests { + + @Configuration + static class CustomizerConfig extends Config { + + @Bean + SqlServerJdbcIndexedSessionRepositoryCustomizer sqlServerJdbcIndexedSessionRepositoryCustomizer() { + return new SqlServerJdbcIndexedSessionRepositoryCustomizer(); + } + + } + +} diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/Db2JdbcIndexedSessionRepositoryCustomizer.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/Db2JdbcIndexedSessionRepositoryCustomizer.java new file mode 100644 index 00000000..1f5e37fb --- /dev/null +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/Db2JdbcIndexedSessionRepositoryCustomizer.java @@ -0,0 +1,52 @@ +/* + * 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 { + + // @formatter:off + private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "" + + "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA " + + "USING ( " + + " SELECT PRIMARY_ID, ?, ? " + + " FROM %TABLE_NAME% " + + " WHERE SESSION_ID = ? " + + ") 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); + } + +} diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java index 604e44f5..3610c3c8 100644 --- a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcIndexedSessionRepository.java @@ -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. @@ -268,7 +268,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 +277,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 +286,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 +295,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 +304,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 +313,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 +322,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 +331,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 +340,7 @@ public class JdbcIndexedSessionRepository */ public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) { Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty"); - this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery; + this.deleteSessionsByExpiryTimeQuery = getQuery(deleteSessionsByExpiryTimeQuery); } /** diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/MySqlJdbcIndexedSessionRepositoryCustomizer.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/MySqlJdbcIndexedSessionRepositoryCustomizer.java new file mode 100644 index 00000000..0910879d --- /dev/null +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/MySqlJdbcIndexedSessionRepositoryCustomizer.java @@ -0,0 +1,45 @@ +/* + * 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 { + + // @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 = ? " + + "ON DUPLICATE KEY UPDATE ATTRIBUTE_BYTES = VALUES(ATTRIBUTE_BYTES)"; + // @formatter:on + + @Override + public void customize(JdbcIndexedSessionRepository sessionRepository) { + sessionRepository.setCreateSessionAttributeQuery(CREATE_SESSION_ATTRIBUTE_QUERY); + } + +} diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryCustomizer.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryCustomizer.java new file mode 100644 index 00000000..3f14570a --- /dev/null +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/OracleJdbcIndexedSessionRepositoryCustomizer.java @@ -0,0 +1,52 @@ +/* + * 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 { + + // @formatter:off + private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "" + + "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA " + + "USING ( " + + " SELECT PRIMARY_ID AS SESSION_PRIMARY_ID, ? AS ATTRIBUTE_NAME, ? AS ATTRIBUTE_BYTES " + + " FROM %TABLE_NAME% " + + " WHERE SESSION_ID = ? " + + ") 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); + } + +} diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryCustomizer.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryCustomizer.java new file mode 100644 index 00000000..ce11e691 --- /dev/null +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryCustomizer.java @@ -0,0 +1,46 @@ +/* + * 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 { + + // @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 = ? " + + "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); + } + +} diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryCustomizer.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryCustomizer.java new file mode 100644 index 00000000..9fa09dac --- /dev/null +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/SqlServerJdbcIndexedSessionRepositoryCustomizer.java @@ -0,0 +1,52 @@ +/* + * 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 { + + // @formatter:off + private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "" + + "MERGE INTO %TABLE_NAME%_ATTRIBUTES SA " + + "USING ( " + + " SELECT PRIMARY_ID, ?, ? " + + " FROM %TABLE_NAME% " + + " WHERE SESSION_ID = ? " + + ") 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); + } + +}