From de16c304eadd1b220753f268c92bffa403f60f18 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Tue, 21 Aug 2018 05:43:06 +0200 Subject: [PATCH] Add support using JDBC repository without transactions Closes gh-1046 --- .../jdbc/JdbcOperationsSessionRepository.java | 30 ++++++- .../JdbcOperationsSessionRepositoryTests.java | 88 ++++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java index 936a3efb..c95685a4 100644 --- a/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java +++ b/spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java @@ -52,7 +52,9 @@ import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionOperations; import org.springframework.transaction.support.TransactionTemplate; @@ -189,10 +191,17 @@ public class JdbcOperationsSessionRepository implements private final JdbcOperations jdbcOperations; - private final TransactionOperations transactionOperations; - private final ResultSetExtractor> extractor = new SessionResultSetExtractor(); + private TransactionOperations transactionOperations = new TransactionOperations() { + + @Override + public T execute(TransactionCallback action) throws TransactionException { + return action.doInTransaction(null); + } + + }; + /** * The name of database table used by Spring Session to store sessions. */ @@ -229,14 +238,29 @@ public class JdbcOperationsSessionRepository implements /** * Create a new {@link JdbcOperationsSessionRepository} instance which uses the * provided {@link JdbcOperations} to manage sessions. + *

+ * The created instance will execute all data access operations in a transaction with + * propagation level of {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}. * @param jdbcOperations the {@link JdbcOperations} to use * @param transactionManager the {@link PlatformTransactionManager} to use */ public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, PlatformTransactionManager transactionManager) { + this(jdbcOperations); + Assert.notNull(transactionManager, "TransactionManager must not be null"); + this.transactionOperations = createTransactionTemplate(transactionManager); + } + + /** + * Create a new {@link JdbcOperationsSessionRepository} instance which uses the + * provided {@link JdbcOperations} to manage sessions. + *

+ * The created instance will not execute data access operations in a transaction. + * @param jdbcOperations the {@link JdbcOperations} to use + */ + public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) { Assert.notNull(jdbcOperations, "JdbcOperations must not be null"); this.jdbcOperations = jdbcOperations; - this.transactionOperations = createTransactionTemplate(transactionManager); this.conversionService = createDefaultConversionService(); prepareQueries(); } diff --git a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java index 9a41c4ef..de091b02 100644 --- a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java +++ b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java @@ -42,7 +42,10 @@ import org.springframework.transaction.TransactionDefinition; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.endsWith; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.startsWith; @@ -88,7 +91,7 @@ public class JdbcOperationsSessionRepositoryTests { assertThatThrownBy( () -> new JdbcOperationsSessionRepository(this.jdbcOperations, null)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Property 'transactionManager' is required"); + .hasMessage("TransactionManager must not be null"); } @Test @@ -688,6 +691,89 @@ public class JdbcOperationsSessionRepositoryTests { assertThat(session.getAttributeNames()).isEmpty(); } + @Test + public void saveNewWithoutTransaction() { + this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations); + JdbcOperationsSessionRepository.JdbcSession session = this.repository + .createSession(); + + this.repository.save(session); + + verify(this.jdbcOperations, times(1)).update( + startsWith("INSERT INTO SPRING_SESSION"), + isA(PreparedStatementSetter.class)); + verifyZeroInteractions(this.jdbcOperations); + verifyZeroInteractions(this.transactionManager); + } + + @Test + public void saveUpdatedWithoutTransaction() { + this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations); + JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession( + "primaryKey", new MapSession()); + session.setLastAccessedTime(Instant.now()); + + this.repository.save(session); + + verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION"), + isA(PreparedStatementSetter.class)); + verifyZeroInteractions(this.jdbcOperations); + verifyZeroInteractions(this.transactionManager); + } + + @Test + @SuppressWarnings("unchecked") + public void findByIdWithoutTransaction() { + given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class), + any(ResultSetExtractor.class))).willReturn(Collections.emptyList()); + this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations); + this.repository.findById("testSessionId"); + + verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.SESSION_ID = ?"), + isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)); + verifyZeroInteractions(this.jdbcOperations); + verifyZeroInteractions(this.transactionManager); + } + + @Test + public void deleteByIdWithoutTransaction() { + this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations); + this.repository.deleteById("testSessionId"); + + verify(this.jdbcOperations, times(1)).update( + eq("DELETE FROM SPRING_SESSION WHERE SESSION_ID = ?"), anyString()); + verifyZeroInteractions(this.jdbcOperations); + verifyZeroInteractions(this.transactionManager); + } + + @Test + @SuppressWarnings("unchecked") + public void findByIndexNameAndIndexValueWithoutTransaction() { + given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class), + any(ResultSetExtractor.class))).willReturn(Collections.emptyList()); + this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations); + this.repository.findByIndexNameAndIndexValue( + FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, + "testIndexValue"); + + verify(this.jdbcOperations, times(1)).query( + endsWith("WHERE S.PRINCIPAL_NAME = ?"), + isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)); + verifyZeroInteractions(this.jdbcOperations); + verifyZeroInteractions(this.transactionManager); + } + + @Test + public void cleanUpExpiredSessionsWithoutTransaction() { + this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations); + this.repository.cleanUpExpiredSessions(); + + verify(this.jdbcOperations, times(1)).update( + eq("DELETE FROM SPRING_SESSION WHERE EXPIRY_TIME < ?"), anyLong()); + verifyZeroInteractions(this.jdbcOperations); + verifyZeroInteractions(this.transactionManager); + } + private void assertPropagationRequiresNew() { ArgumentCaptor argument = ArgumentCaptor.forClass(TransactionDefinition.class);