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 612f2527e..b801672e5 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 @@ -907,8 +907,9 @@ private void save() { }); } else { - JdbcIndexedSessionRepository.this.transactionOperations.executeWithoutResult((status) -> { - if (JdbcSession.this.changed) { + List deltaActions = JdbcSession.this.changed ? new ArrayList<>(4) : new ArrayList<>(); + if (JdbcSession.this.changed) { + deltaActions.add(() -> { Map indexes = JdbcIndexedSessionRepository.this.indexResolver .resolveIndexesFor(JdbcSession.this); JdbcIndexedSessionRepository.this.jdbcOperations @@ -920,32 +921,43 @@ private void save() { ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME)); ps.setString(6, JdbcSession.this.primaryKey); }); - } - List addedAttributeNames = JdbcSession.this.delta.entrySet() - .stream() - .filter((entry) -> entry.getValue() == DeltaValue.ADDED) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - if (!addedAttributeNames.isEmpty()) { - insertSessionAttributes(JdbcSession.this, addedAttributeNames); - } - List updatedAttributeNames = JdbcSession.this.delta.entrySet() - .stream() - .filter((entry) -> entry.getValue() == DeltaValue.UPDATED) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - if (!updatedAttributeNames.isEmpty()) { - updateSessionAttributes(JdbcSession.this, updatedAttributeNames); - } - List removedAttributeNames = JdbcSession.this.delta.entrySet() - .stream() - .filter((entry) -> entry.getValue() == DeltaValue.REMOVED) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - if (!removedAttributeNames.isEmpty()) { - deleteSessionAttributes(JdbcSession.this, removedAttributeNames); - } - }); + }); + } + + List addedAttributeNames = JdbcSession.this.delta.entrySet() + .stream() + .filter((entry) -> entry.getValue() == DeltaValue.ADDED) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + if (!addedAttributeNames.isEmpty()) { + deltaActions.add(() -> insertSessionAttributes(JdbcSession.this, addedAttributeNames)); + } + + List updatedAttributeNames = JdbcSession.this.delta.entrySet() + .stream() + .filter((entry) -> entry.getValue() == DeltaValue.UPDATED) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + if (!updatedAttributeNames.isEmpty()) { + deltaActions.add(() -> updateSessionAttributes(JdbcSession.this, updatedAttributeNames)); + } + + List removedAttributeNames = JdbcSession.this.delta.entrySet() + .stream() + .filter((entry) -> entry.getValue() == DeltaValue.REMOVED) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + if (!removedAttributeNames.isEmpty()) { + deltaActions.add(() -> deleteSessionAttributes(JdbcSession.this, removedAttributeNames)); + } + + if (!deltaActions.isEmpty()) { + JdbcIndexedSessionRepository.this.transactionOperations.executeWithoutResult((status) -> { + for (Runnable action : deltaActions) { + action.run(); + } + }); + } } clearChangeFlags(); } diff --git a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java index 7b60c5a4d..9bc4dc93c 100644 --- a/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java +++ b/spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcIndexedSessionRepositoryTests.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Consumer; import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; @@ -48,10 +49,13 @@ import org.springframework.session.SaveMode; import org.springframework.session.Session; import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionOperations; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; @@ -59,6 +63,7 @@ import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -78,12 +83,26 @@ class JdbcIndexedSessionRepositoryTests { @Mock private JdbcOperations jdbcOperations; + @Mock + private TransactionOperations transactionOperations; + private JdbcIndexedSessionRepository repository; @BeforeEach void setUp() { - this.repository = new JdbcIndexedSessionRepository(this.jdbcOperations, - TransactionOperations.withoutTransaction()); + // Mock transaction callbacks to the real consumer + lenient().doAnswer((answer) -> { + answer.getArgument(0, Consumer.class).accept(mock(TransactionStatus.class)); + return null; + }).when(this.transactionOperations).executeWithoutResult(any()); + + lenient() + .doAnswer((answer) -> answer.getArgument(0, TransactionCallback.class) + .doInTransaction(mock(TransactionStatus.class))) + .when(this.transactionOperations) + .execute(any()); + + this.repository = new JdbcIndexedSessionRepository(this.jdbcOperations, this.transactionOperations); } @Test @@ -467,6 +486,7 @@ void saveUpdatedAddAndRemoveAttribute() { assertThat(session.isNew()).isFalse(); verifyNoMoreInteractions(this.jdbcOperations); + verifyNoMoreInteractions(this.transactionOperations); } @Test // gh-1070