Skip to content

Commit 0058752

Browse files
committed
Add support for Query by Example.
Resolves #929.
1 parent 802e4a7 commit 0058752

File tree

2 files changed

+588
-0
lines changed

2 files changed

+588
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.relational.repository.query;
18+
19+
import static org.springframework.data.domain.ExampleMatcher.*;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.Optional;
24+
25+
import org.springframework.data.domain.Example;
26+
import org.springframework.data.mapping.PersistentPropertyAccessor;
27+
import org.springframework.data.mapping.PropertyHandler;
28+
import org.springframework.data.mapping.context.MappingContext;
29+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
30+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
31+
import org.springframework.data.relational.core.query.Criteria;
32+
import org.springframework.data.relational.core.query.Query;
33+
import org.springframework.data.support.ExampleMatcherAccessor;
34+
import org.springframework.util.Assert;
35+
36+
/**
37+
* Transform an {@link Example} into a {@link Query}.
38+
*
39+
* @since 2.2
40+
* @author Greg Turnquist
41+
*/
42+
public class RelationalExampleMapper {
43+
44+
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
45+
46+
public RelationalExampleMapper(
47+
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext) {
48+
this.mappingContext = mappingContext;
49+
}
50+
51+
/**
52+
* Use the {@link Example} to extract a {@link Query}.
53+
*
54+
* @param example
55+
* @return query
56+
*/
57+
public <T> Query getMappedExample(Example<T> example) {
58+
return getMappedExample(example, mappingContext.getRequiredPersistentEntity(example.getProbeType()));
59+
}
60+
61+
/**
62+
* Transform each property of the {@link Example}'s probe into a {@link Criteria} and assemble them into a
63+
* {@link Query}.
64+
*
65+
* @param example
66+
* @param entity
67+
* @return query
68+
*/
69+
private <T> Query getMappedExample(Example<T> example, RelationalPersistentEntity<?> entity) {
70+
71+
Assert.notNull(example, "Example must not be null!");
72+
Assert.notNull(entity, "RelationalPersistentEntity must not be null!");
73+
74+
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(example.getProbe());
75+
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
76+
77+
final List<Criteria> criteriaBasedOnProperties = new ArrayList<>();
78+
79+
entity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
80+
81+
if (matcherAccessor.isIgnoredPath(property.getName())) {
82+
return;
83+
}
84+
85+
Optional<?> optionalConvertedPropValue = matcherAccessor //
86+
.getValueTransformerForPath(property.getName()) //
87+
.apply(Optional.ofNullable(propertyAccessor.getProperty(property)));
88+
89+
// If the value is empty, don't try to match against it
90+
if (!optionalConvertedPropValue.isPresent()) {
91+
return;
92+
}
93+
94+
Object convPropValue = optionalConvertedPropValue.get();
95+
boolean ignoreCase = matcherAccessor.isIgnoreCaseForPath(property.getName());
96+
97+
String column = property.getName();
98+
99+
switch (matcherAccessor.getStringMatcherForPath(property.getName())) {
100+
case DEFAULT:
101+
case EXACT:
102+
criteriaBasedOnProperties.add(includeNulls(example) //
103+
? Criteria.where(column).isNull().or(column).is(convPropValue).ignoreCase(ignoreCase)
104+
: Criteria.where(column).is(convPropValue).ignoreCase(ignoreCase));
105+
break;
106+
case ENDING:
107+
criteriaBasedOnProperties.add(includeNulls(example) //
108+
? Criteria.where(column).isNull().or(column).like("%" + convPropValue).ignoreCase(ignoreCase)
109+
: Criteria.where(column).like("%" + convPropValue).ignoreCase(ignoreCase));
110+
break;
111+
case STARTING:
112+
criteriaBasedOnProperties.add(includeNulls(example) //
113+
? Criteria.where(column).isNull().or(column).like(convPropValue + "%").ignoreCase(ignoreCase)
114+
: Criteria.where(column).like(convPropValue + "%").ignoreCase(ignoreCase));
115+
break;
116+
case CONTAINING:
117+
criteriaBasedOnProperties.add(includeNulls(example) //
118+
? Criteria.where(column).isNull().or(column).like("%" + convPropValue + "%").ignoreCase(ignoreCase)
119+
: Criteria.where(column).like("%" + convPropValue + "%").ignoreCase(ignoreCase));
120+
break;
121+
default:
122+
throw new IllegalStateException(example.getMatcher().getDefaultStringMatcher() + " is not supported!");
123+
}
124+
});
125+
126+
// Criteria, assemble!
127+
Criteria criteria = Criteria.empty();
128+
129+
for (Criteria propertyCriteria : criteriaBasedOnProperties) {
130+
131+
if (example.getMatcher().isAllMatching()) {
132+
criteria = criteria.and(propertyCriteria);
133+
} else {
134+
criteria = criteria.or(propertyCriteria);
135+
}
136+
}
137+
138+
return Query.query(criteria);
139+
}
140+
141+
/**
142+
* Does this {@link Example} need to include {@literal NULL} values in its {@link Criteria}?
143+
*
144+
* @param example
145+
* @return whether or not to include nulls.
146+
*/
147+
private static <T> boolean includeNulls(Example<T> example) {
148+
return example.getMatcher().getNullHandler() == NullHandler.INCLUDE;
149+
}
150+
}

0 commit comments

Comments
 (0)