Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 720e033

Browse files
mipo256Михаил Поливаха
authored and
Михаил Поливаха
committed
GH-2099 GH-1986 Consider Embedded properties in QBE
Signed-off-by: mipo256 <mikhailpolivakha@gmail.com> Signed-off-by: Михаил Поливаха <mikhail.polivakha@gazprombank.ru>
1 parent 38f2af0 commit 720e033

File tree

3 files changed

+250
-41
lines changed

3 files changed

+250
-41
lines changed

‎spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,11 @@ public boolean isOrdered() {
262262

263263
@Override
264264
public boolean isEmbedded() {
265-
return isEmbedded || (isIdProperty() && isEntity());
265+
return isEmbedded || isCompositeId();
266+
}
267+
268+
private boolean isCompositeId() {
269+
return isIdProperty() && isEntity();
266270
}
267271

268272
@Override

‎spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalExampleMapper.java‎

Lines changed: 144 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@
2222
import java.util.List;
2323
import java.util.Optional;
2424

25+
import org.jspecify.annotations.NonNull;
26+
import org.jspecify.annotations.Nullable;
27+
2528
import org.springframework.data.domain.Example;
29+
import org.springframework.data.domain.ExampleMatcher;
2630
import org.springframework.data.mapping.PersistentPropertyAccessor;
2731
import org.springframework.data.mapping.PropertyHandler;
32+
import org.springframework.data.mapping.PropertyPath;
2833
import org.springframework.data.mapping.context.MappingContext;
2934
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3035
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@@ -39,6 +44,7 @@
3944
* @since 2.2
4045
* @author Greg Turnquist
4146
* @author Jens Schauder
47+
* @author Mikhail Polivakha
4248
*/
4349
public class RelationalExampleMapper {
4450

@@ -64,92 +70,193 @@ public <T> Query getMappedExample(Example<T> example) {
6470
* {@link Query}.
6571
*
6672
* @param example
67-
* @param entity
73+
* @param persistentEntity
6874
* @return query
6975
*/
70-
private <T> Query getMappedExample(Example<T> example, RelationalPersistentEntity<?> entity) {
76+
private <T> Query getMappedExample(Example<T> example, RelationalPersistentEntity<?> persistentEntity) {
7177

7278
Assert.notNull(example, "Example must not be null");
73-
Assert.notNull(entity, "RelationalPersistentEntity must not be null");
79+
Assert.notNull(persistentEntity, "RelationalPersistentEntity must not be null");
7480

75-
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(example.getProbe());
81+
PersistentPropertyAccessor<T> probePropertyAccessor = persistentEntity.getPropertyAccessor(example.getProbe());
7682
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
7783

78-
final List<Criteria> criteriaBasedOnProperties = new ArrayList<>();
84+
final List<Criteria> criteriaBasedOnProperties = buildCriteria( //
85+
persistentEntity, //
86+
matcherAccessor, //
87+
probePropertyAccessor //
88+
);
7989

80-
entity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
90+
// Criteria, assemble!
91+
Criteria criteria = Criteria.empty();
8192

82-
if (property.isCollectionLike() || property.isMap()) {
83-
return;
84-
}
93+
for (Criteria propertyCriteria : criteriaBasedOnProperties) {
8594

86-
if (matcherAccessor.isIgnoredPath(property.getName())) {
87-
return;
95+
if (example.getMatcher().isAllMatching()) {
96+
criteria = criteria.and(propertyCriteria);
97+
} else {
98+
criteria = criteria.or(propertyCriteria);
8899
}
100+
}
101+
102+
return Query.query(criteria);
103+
}
104+
105+
private <T> List<Criteria> buildCriteria( //
106+
RelationalPersistentEntity<?> persistentEntity, //
107+
ExampleMatcherAccessor matcherAccessor, //
108+
PersistentPropertyAccessor<T> probePropertyAccessor //
109+
) {
110+
final List<Criteria> criteriaBasedOnProperties = new ArrayList<>();
111+
112+
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
113+
potentiallyEnrichCriteria(
114+
null,
115+
matcherAccessor, //
116+
probePropertyAccessor, //
117+
property, //
118+
criteriaBasedOnProperties //
119+
);
120+
});
121+
return criteriaBasedOnProperties;
122+
}
123+
124+
/**
125+
* Analyzes the incoming {@code property} and potentially enriches the {@code criteriaBasedOnProperties} with the new
126+
* {@link Criteria} for this property.
127+
* <p>
128+
* This algorithm is recursive in order to take the embedded properties into account. The caller can expect that the result
129+
* of this method call is fully processed subtree of an aggreagte where the passed {@code property} serves as the root.
130+
*
131+
* @param propertyPath the {@link PropertyPath} of the passed {@code property}.
132+
* @param matcherAccessor the accessor for the original {@link ExampleMatcher}.
133+
* @param entityPropertiesAccessor the accessor for the properties of the current entity that holds the given {@code property}
134+
* @param property the property under analysis
135+
* @param criteriaBasedOnProperties the {@link List} of criteria objects that potentially gets enriched as a
136+
* result of the incoming {@code property} processing
137+
*/
138+
private <T> void potentiallyEnrichCriteria(
139+
@Nullable PropertyPath propertyPath,
140+
ExampleMatcherAccessor matcherAccessor, //
141+
PersistentPropertyAccessor<T> entityPropertiesAccessor, //
142+
RelationalPersistentProperty property, //
143+
List<Criteria> criteriaBasedOnProperties //
144+
) {
145+
146+
// QBE do not support queries on Child aggregates yet
147+
if (property.isCollectionLike() || property.isMap()) {
148+
return;
149+
}
150+
151+
PropertyPath currentPropertyPath = resolveCurrentPropertyPath(propertyPath, property);
152+
String currentPropertyDotPath = currentPropertyPath.toDotPath();
153+
154+
if (matcherAccessor.isIgnoredPath(currentPropertyDotPath)) {
155+
return;
156+
}
89157

158+
Object actualPropertyValue = entityPropertiesAccessor.getProperty(property);
159+
160+
if (property.isEmbedded() && actualPropertyValue != null) {
161+
processEmbeddedRecursively( //
162+
matcherAccessor, //
163+
actualPropertyValue,
164+
property, //
165+
criteriaBasedOnProperties, //
166+
currentPropertyPath //
167+
);
168+
} else {
90169
Optional<?> optionalConvertedPropValue = matcherAccessor //
91-
.getValueTransformerForPath(property.getName()) //
92-
.apply(Optional.ofNullable(propertyAccessor.getProperty(property)));
170+
.getValueTransformerForPath(currentPropertyDotPath) //
171+
.apply(Optional.ofNullable(actualPropertyValue));
93172

94173
// If the value is empty, don't try to match against it
95-
if (!optionalConvertedPropValue.isPresent()) {
174+
if (optionalConvertedPropValue.isEmpty()) {
96175
return;
97176
}
98177

99178
Object convPropValue = optionalConvertedPropValue.get();
100-
boolean ignoreCase = matcherAccessor.isIgnoreCaseForPath(property.getName());
179+
boolean ignoreCase = matcherAccessor.isIgnoreCaseForPath(currentPropertyDotPath);
101180

102181
String column = property.getName();
103182

104-
switch (matcherAccessor.getStringMatcherForPath(property.getName())) {
183+
switch (matcherAccessor.getStringMatcherForPath(currentPropertyDotPath)) {
105184
case DEFAULT:
106185
case EXACT:
107-
criteriaBasedOnProperties.add(includeNulls(example) //
186+
criteriaBasedOnProperties.add(includeNulls(matcherAccessor) //
108187
? Criteria.where(column).isNull().or(column).is(convPropValue).ignoreCase(ignoreCase)
109188
: Criteria.where(column).is(convPropValue).ignoreCase(ignoreCase));
110189
break;
111190
case ENDING:
112-
criteriaBasedOnProperties.add(includeNulls(example) //
191+
criteriaBasedOnProperties.add(includeNulls(matcherAccessor) //
113192
? Criteria.where(column).isNull().or(column).like("%" + convPropValue).ignoreCase(ignoreCase)
114193
: Criteria.where(column).like("%" + convPropValue).ignoreCase(ignoreCase));
115194
break;
116195
case STARTING:
117-
criteriaBasedOnProperties.add(includeNulls(example) //
196+
criteriaBasedOnProperties.add(includeNulls(matcherAccessor) //
118197
? Criteria.where(column).isNull().or(column).like(convPropValue + "%").ignoreCase(ignoreCase)
119198
: Criteria.where(column).like(convPropValue + "%").ignoreCase(ignoreCase));
120199
break;
121200
case CONTAINING:
122-
criteriaBasedOnProperties.add(includeNulls(example) //
201+
criteriaBasedOnProperties.add(includeNulls(matcherAccessor) //
123202
? Criteria.where(column).isNull().or(column).like("%" + convPropValue + "%").ignoreCase(ignoreCase)
124203
: Criteria.where(column).like("%" + convPropValue + "%").ignoreCase(ignoreCase));
125204
break;
126205
default:
127-
throw new IllegalStateException(example.getMatcher().getDefaultStringMatcher() + " is not supported");
206+
throw new IllegalStateException(matcherAccessor.getDefaultStringMatcher() + " is not supported");
128207
}
129-
});
208+
}
130209

131-
// Criteria, assemble!
132-
Criteria criteria = Criteria.empty();
210+
}
133211

134-
for (Criteria propertyCriteria : criteriaBasedOnProperties) {
212+
/**
213+
* Processes an embedded entity's properties recursively.
214+
*
215+
* @param matcherAccessor the input matcher on the {@link Example#getProbe() original probe}.
216+
* @param value the actual embedded object.
217+
* @param property the embedded property.
218+
* @param criteriaBasedOnProperties collection of {@link Criteria} objects to potentially enrich.
219+
* @param currentPropertyPath the dot-separated path of the passed {@code property}.
220+
*/
221+
private void processEmbeddedRecursively(
222+
ExampleMatcherAccessor matcherAccessor,
223+
Object value,
224+
RelationalPersistentProperty property,
225+
List<Criteria> criteriaBasedOnProperties,
226+
PropertyPath currentPropertyPath
227+
) {
228+
RelationalPersistentEntity<?> embeddedPersistentEntity = mappingContext.getPersistentEntity(property.getTypeInformation());
135229

136-
if (example.getMatcher().isAllMatching()) {
137-
criteria = criteria.and(propertyCriteria);
138-
} else {
139-
criteria = criteria.or(propertyCriteria);
140-
}
141-
}
230+
PersistentPropertyAccessor<?> embeddedEntityPropertyAccessor = embeddedPersistentEntity.getPropertyAccessor(value);
142231

143-
return Query.query(criteria);
232+
embeddedPersistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) embeddedProperty ->
233+
potentiallyEnrichCriteria(
234+
currentPropertyPath,
235+
matcherAccessor,
236+
embeddedEntityPropertyAccessor,
237+
embeddedProperty,
238+
criteriaBasedOnProperties
239+
)
240+
);
241+
}
242+
243+
private static PropertyPath resolveCurrentPropertyPath(@Nullable PropertyPath propertyPath, RelationalPersistentProperty property) {
244+
PropertyPath currentPropertyPath;
245+
246+
if (propertyPath == null) {
247+
currentPropertyPath = PropertyPath.from(property.getName(), property.getOwner().getTypeInformation());
248+
} else {
249+
currentPropertyPath = propertyPath.nested(property.getName());
250+
}
251+
return currentPropertyPath;
144252
}
145253

146254
/**
147-
* Does this {@link Example} need to include {@literal NULL} values in its {@link Criteria}?
255+
* Does this {@link ExampleMatcherAccessor} need to include {@literal NULL} values in its {@link Criteria}?
148256
*
149-
* @param example
150-
* @return whether or not to include nulls.
257+
* @return whether to include nulls.
151258
*/
152-
private static <T> boolean includeNulls(Example<T> example) {
153-
return example.getMatcher().getNullHandler() == NullHandler.INCLUDE;
259+
private static <T> boolean includeNulls(ExampleMatcherAccessorexampleMatcher) {
260+
return exampleMatcher.getNullHandler() == NullHandler.INCLUDE;
154261
}
155262
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /