From ba32d329bb593abff416ddbd9ba6981b3c2798fa Mon Sep 17 00:00:00 2001 From: 62hoon99 Date: Sun, 18 Jan 2026 15:29:49 +0900 Subject: [PATCH] Fix embedded path ColumnInfo access for composite IDs (GH-2201) Signed-off-by: 62hoon99 --- .../MappingJdbcConverterUnitTests.java | 46 +++++++++++++++++++ .../MappingRelationalConverter.java | 38 ++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java index d2ee4ad02..97c009834 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverterUnitTests.java @@ -57,6 +57,7 @@ import org.springframework.data.relational.domain.RowDocument; * * @author Mark Paluch * @author Jens Schauder + * @author Ki Hoon You */ class MappingJdbcConverterUnitTests { @@ -187,6 +188,38 @@ class MappingJdbcConverterUnitTests { } + @Test // GH-2201 + void shouldReadEntityWithClassBasedCompositeIdUsingReadAndResolve() { + + // This test verifies that ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier + // correctly handles embedded composite IDs without throwing "Cannot obtain ColumnInfo for embedded path" + RowDocument rowdocument = new RowDocument(Map.of("UID", "1", "TID", "2", "ALIAS", "test-alias")); + + TenantUserWithClassId result = converter.readAndResolve(TenantUserWithClassId.class, rowdocument); + + assertThat(result).isNotNull(); + assertThat(result.id).isNotNull(); + assertThat(result.id.uid).isEqualTo("1"); + assertThat(result.id.tid).isEqualTo("2"); + assertThat(result.alias).isEqualTo("test-alias"); + } + + @Test // GH-2201 + void shouldReadEntityWithRecordBasedCompositeIdUsingReadAndResolve() { + + // This test verifies that ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier + // correctly handles embedded composite IDs without throwing "Cannot obtain ColumnInfo for embedded path" + RowDocument rowdocument = new RowDocument(Map.of("UID", "1", "TID", "2", "ALIAS", "test-alias")); + + TenantUserWithRecordId result = converter.readAndResolve(TenantUserWithRecordId.class, rowdocument); + + assertThat(result).isNotNull(); + assertThat(result.id()).isNotNull(); + assertThat(result.id().uid()).isEqualTo("1"); + assertThat(result.id().tid()).isEqualTo("2"); + assertThat(result.alias()).isEqualTo("test-alias"); + } + private static void checkReadConversion(SoftAssertions softly, MappingJdbcConverter converter, String propertyName, Object expected) { @@ -279,6 +312,19 @@ class MappingJdbcConverterUnitTests { private record ReferencedByUuid(@Id UUID id) { } + // GH-2201: Test entities for composite ID issue + static class TenantUserWithClassId { + @Id + TenantUserID id; + String alias; + } + + record TenantUserID(String uid, String tid) { + } + + record TenantUserWithRecordId(@Id TenantUserID id, String alias) { + } + static class ByteArrayToUuid implements Converter { @Override public UUID convert(byte[] source) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java index c246df034..73b27de0b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java @@ -1214,14 +1214,33 @@ public class MappingRelationalConverter extends AbstractRelationalConverter @Override public Object getValue(AggregatePath path) { - Object value = document.get(path.getColumnInfo().alias().getReference()); + // Embedded paths (e.g., composite ids) cannot have a single ColumnInfo + // Return null as embedded values are handled through their individual properties + if (path.isEmbedded()) { + return null; + } - return value; + return document.get(path.getColumnInfo().alias().getReference()); } @Override public boolean hasValue(AggregatePath path) { + // Embedded paths (e.g., composite ids) cannot have a single ColumnInfo + // Check if any of the embedded properties have values + if (path.isEmbedded()) { + RelationalPersistentEntity leafEntity = path.getLeafEntity(); + if (leafEntity != null) { + for (RelationalPersistentProperty property : leafEntity) { + AggregatePath propertyPath = path.append(property); + if (hasValue(propertyPath)) { + return true; + } + } + } + return false; + } + Object value = document.get(path.getColumnInfo().alias().getReference()); if (value == null) { @@ -1238,6 +1257,21 @@ public class MappingRelationalConverter extends AbstractRelationalConverter @Override public boolean hasNonEmptyValue(AggregatePath path) { + // Embedded paths (e.g., composite ids) cannot have a single ColumnInfo + // Check if any of the embedded properties have non-empty values + if (path.isEmbedded()) { + RelationalPersistentEntity leafEntity = path.getLeafEntity(); + if (leafEntity != null) { + for (RelationalPersistentProperty property : leafEntity) { + AggregatePath propertyPath = path.append(property); + if (hasNonEmptyValue(propertyPath)) { + return true; + } + } + } + return false; + } + if (!hasValue(path)) { return false; }