From 8e2fba36c29bc3acfa6c5fe9373571dc67548df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20=C3=96berg?= Date: Tue, 6 Oct 2026 15:46:45 +0200 Subject: [PATCH] Order JDBC principal session rows by primary key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep each session's attribute rows adjacent for SessionResultSetExtractor. Add a PostgreSQL regression that compares principal lookups with findById after attributes are added in later saves. Closes gh-3452 Signed-off-by: Alexander Öberg --- ...SqlJdbcIndexedSessionRepositoryITests.java | 55 +++++++++++++++++++ .../jdbc/JdbcIndexedSessionRepository.java | 1 + 2 files changed, 56 insertions(+) diff --git a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryITests.java b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryITests.java index 38abe8fd9..83e2116e2 100644 --- a/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryITests.java +++ b/spring-session-jdbc/src/integration-test/java/org/springframework/session/jdbc/PostgreSqlJdbcIndexedSessionRepositoryITests.java @@ -16,16 +16,30 @@ package org.springframework.session.jdbc; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testcontainers.containers.JdbcDatabaseContainer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; +import static org.assertj.core.api.Assertions.assertThat; + /** * Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL database. * @@ -36,6 +50,47 @@ @ContextConfiguration class PostgreSqlJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests { + @Autowired + private JdbcIndexedSessionRepository repository; + + @Autowired + private DataSource dataSource; + + @Test // gh-3452 + void findByPrincipalNameWhenAttributesAddedLaterThenReturnsCompleteSessions() { + String principalName = "principal-" + UUID.randomUUID(); + String largeValue = "x".repeat(1200); + List ids = new ArrayList<>(); + for (int i = 0; i < 120; i++) { + JdbcSession session = this.repository.createSession(); + session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principalName); + session.setAttribute("first", largeValue + i); + session.setAttribute("second", largeValue + i); + session.setAttribute("third", largeValue + i); + this.repository.save(session); + ids.add(session.getId()); + } + for (int i = 0; i < ids.size(); i += 3) { + JdbcSession session = this.repository.findById(ids.get(i)); + session.setAttribute("later", "value"); + this.repository.save(session); + } + new JdbcTemplate(this.dataSource).execute("ANALYZE SPRING_SESSION, SPRING_SESSION_ATTRIBUTES"); + + Map sessions = this.repository.findByPrincipalName(principalName); + + assertThat(sessions).containsOnlyKeys(ids); + for (String id : ids) { + JdbcSession expected = this.repository.findById(id); + assertThat(expected.getAttributeNames()).contains("first", "second", "third"); + JdbcSession actual = sessions.get(id); + assertThat(actual.getAttributeNames()).isEqualTo(expected.getAttributeNames()); + for (String name : expected.getAttributeNames()) { + assertThat(actual.getAttribute(name)).isEqualTo(expected.getAttribute(name)); + } + } + } + @Configuration static class Config extends BaseContainerConfig { 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 98b46de85..62e2ba984 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 @@ -200,6 +200,7 @@ public class JdbcIndexedSessionRepository implements FROM %TABLE_NAME% S LEFT JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID WHERE S.PRINCIPAL_NAME = ? + ORDER BY S.PRIMARY_ID """; private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = """