diff --git a/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java b/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java index a1cd158..ec2025f 100644 --- a/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java +++ b/mapper/src/main/java/tw/com/softleader/data/jpa/spec/ReflectionDatabind.java @@ -69,6 +69,15 @@ static List of(@NonNull Object target, @NonNull SkippingStrategy skipp return of(target, skippingStrategy, ReflectionDatabind::new, ReflectionDatabind::new); } + /** + * Builds the field lookup for {@code target}. + * + *

Local-fields-only contract: this uses {@link ReflectionUtils#doWithLocalFields}, + * which visits only fields declared directly on {@code target}'s concrete class. Fields inherited + * from a superclass (e.g. a shared base criteria POJO) are not included and therefore + * are never databound. Switching to {@link ReflectionUtils#doWithFields} to also include + * inherited fields would be a deliberate behavior change. + */ // Visible for testing static List of( @NonNull Object target, diff --git a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java index 180ba22..a2e0d19 100644 --- a/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java +++ b/mapper/src/test/java/tw/com/softleader/data/jpa/spec/ReflectionDatabindTest.java @@ -33,6 +33,7 @@ import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; +import tw.com.softleader.data.jpa.spec.annotation.Spec; class ReflectionDatabindTest { @@ -81,6 +82,22 @@ void fireOnlyOnce() { }); } + @Test + void inheritedFieldsAreNotDatabound() { + + // Pins the current doWithLocalFields contract: fields inherited from a superclass criteria + // POJO are silently excluded from the field lookup. If this ever needs to change (e.g. to + // support inherited criteria fields via doWithFields), this test must be updated + // deliberately. + var object = new ChildCriteria("childValue", "parentValue"); + + var databind = ReflectionDatabind.of(object, new DefaultSkippingStrategy()); + + assertThat(databind) + .extracting(bind -> bind.getField().getName()) + .containsExactly("@type", "child"); + } + @AllArgsConstructor static class MyObject { @@ -89,4 +106,19 @@ static class MyObject { Optional c; Collection d; } + + static class ParentCriteria { + + @Spec String parent; + } + + static class ChildCriteria extends ParentCriteria { + + String child; + + ChildCriteria(String child, String parent) { + this.child = child; + this.parent = parent; + } + } }