@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.relational.repository.query;
|
||||
|
||||
import static org.springframework.data.domain.ExampleMatcher.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.support.ExampleMatcherAccessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Transform an {@link Example} into a {@link Query}.
|
||||
*
|
||||
* @since 2.2
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class RelationalExampleMapper {
|
||||
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
|
||||
|
||||
public RelationalExampleMapper(
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the {@link Example} to extract a {@link Query}.
|
||||
*
|
||||
* @param example
|
||||
* @return query
|
||||
*/
|
||||
public <T> Query getMappedExample(Example<T> example) {
|
||||
return getMappedExample(example, mappingContext.getRequiredPersistentEntity(example.getProbeType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform each property of the {@link Example}'s probe into a {@link Criteria} and assemble them into a
|
||||
* {@link Query}.
|
||||
*
|
||||
* @param example
|
||||
* @param entity
|
||||
* @return query
|
||||
*/
|
||||
private <T> Query getMappedExample(Example<T> example, RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Assert.notNull(example, "Example must not be null!");
|
||||
Assert.notNull(entity, "RelationalPersistentEntity must not be null!");
|
||||
|
||||
PersistentPropertyAccessor<T> propertyAccessor = entity.getPropertyAccessor(example.getProbe());
|
||||
ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
|
||||
|
||||
final List<Criteria> criteriaBasedOnProperties = new ArrayList<>();
|
||||
|
||||
entity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
|
||||
|
||||
if (matcherAccessor.isIgnoredPath(property.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<?> optionalConvertedPropValue = matcherAccessor //
|
||||
.getValueTransformerForPath(property.getName()) //
|
||||
.apply(Optional.ofNullable(propertyAccessor.getProperty(property)));
|
||||
|
||||
// If the value is empty, don't try to match against it
|
||||
if (!optionalConvertedPropValue.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object convPropValue = optionalConvertedPropValue.get();
|
||||
boolean ignoreCase = matcherAccessor.isIgnoreCaseForPath(property.getName());
|
||||
|
||||
String column = property.getName();
|
||||
|
||||
switch (matcherAccessor.getStringMatcherForPath(property.getName())) {
|
||||
case DEFAULT:
|
||||
case EXACT:
|
||||
criteriaBasedOnProperties.add(includeNulls(example) //
|
||||
? Criteria.where(column).isNull().or(column).is(convPropValue).ignoreCase(ignoreCase)
|
||||
: Criteria.where(column).is(convPropValue).ignoreCase(ignoreCase));
|
||||
break;
|
||||
case ENDING:
|
||||
criteriaBasedOnProperties.add(includeNulls(example) //
|
||||
? Criteria.where(column).isNull().or(column).like("%" + convPropValue).ignoreCase(ignoreCase)
|
||||
: Criteria.where(column).like("%" + convPropValue).ignoreCase(ignoreCase));
|
||||
break;
|
||||
case STARTING:
|
||||
criteriaBasedOnProperties.add(includeNulls(example) //
|
||||
? Criteria.where(column).isNull().or(column).like(convPropValue + "%").ignoreCase(ignoreCase)
|
||||
: Criteria.where(column).like(convPropValue + "%").ignoreCase(ignoreCase));
|
||||
break;
|
||||
case CONTAINING:
|
||||
criteriaBasedOnProperties.add(includeNulls(example) //
|
||||
? Criteria.where(column).isNull().or(column).like("%" + convPropValue + "%").ignoreCase(ignoreCase)
|
||||
: Criteria.where(column).like("%" + convPropValue + "%").ignoreCase(ignoreCase));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(example.getMatcher().getDefaultStringMatcher() + " is not supported!");
|
||||
}
|
||||
});
|
||||
|
||||
// Criteria, assemble!
|
||||
Criteria criteria = Criteria.empty();
|
||||
|
||||
for (Criteria propertyCriteria : criteriaBasedOnProperties) {
|
||||
|
||||
if (example.getMatcher().isAllMatching()) {
|
||||
criteria = criteria.and(propertyCriteria);
|
||||
} else {
|
||||
criteria = criteria.or(propertyCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
return Query.query(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this {@link Example} need to include {@literal NULL} values in its {@link Criteria}?
|
||||
*
|
||||
* @param example
|
||||
* @return whether or not to include nulls.
|
||||
*/
|
||||
private static <T> boolean includeNulls(Example<T> example) {
|
||||
return example.getMatcher().getNullHandler() == NullHandler.INCLUDE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.relational.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.*;
|
||||
import static org.springframework.data.domain.ExampleMatcher.StringMatcher.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
|
||||
/**
|
||||
* Verify that the {@link RelationalExampleMapper} properly turns {@link Example}s into {@link Query}'s.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class RelationalExampleMapperTests {
|
||||
|
||||
RelationalExampleMapper exampleMapper;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
exampleMapper = new RelationalExampleMapper(new RelationalMappingContext());
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithId() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setId("id1");
|
||||
|
||||
Example<Person> example = Example.of(person);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Objects::toString) //
|
||||
.hasValue("(id = 'id1')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstname() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
|
||||
Example<Person> example = Example.of(person);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname = 'Frodo')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameAndLastname() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
person.setLastname("Baggins");
|
||||
|
||||
Example<Person> example = Example.of(person);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname = 'Frodo') AND (lastname = 'Baggins')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithNullMatchingLastName() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setLastname("Baggins");
|
||||
|
||||
ExampleMatcher matcher = matching().withIncludeNullValues();
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(lastname IS NULL OR lastname = 'Baggins')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithNullMatchingFirstnameAndLastname() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Bilbo");
|
||||
person.setLastname("Baggins");
|
||||
|
||||
ExampleMatcher matcher = matching().withIncludeNullValues();
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname IS NULL OR firstname = 'Bilbo') AND (lastname IS NULL OR lastname = 'Baggins')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameAndLastnameIgnoringFirstname() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
person.setLastname("Baggins");
|
||||
|
||||
ExampleMatcher matcher = matching().withIgnorePaths("firstname");
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(lastname = 'Baggins')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameAndLastnameWithNullMatchingIgnoringFirstName() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
person.setLastname("Baggins");
|
||||
|
||||
ExampleMatcher matcher = matching().withIncludeNullValues().withIgnorePaths("firstname");
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(lastname IS NULL OR lastname = 'Baggins')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingAtTheBeginning() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Fro");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(STARTING);
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE 'Fro%')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingOnTheEnding() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(ENDING);
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE '%do')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingContaining() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(CONTAINING);
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE '%do%')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingRegEx() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(ExampleMatcher.StringMatcher.REGEX);
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
assertThatIllegalStateException().isThrownBy(() -> exampleMapper.getMappedExample(example))
|
||||
.withMessageContaining("REGEX is not supported!");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithFieldSpecificStringMatcherEndsWith() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withMatcher("firstname", endsWith());
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE '%do')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithFieldSpecificStringMatcherStartsWith() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Fro");
|
||||
|
||||
ExampleMatcher matcher = matching().withMatcher("firstname", startsWith());
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE 'Fro%')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithFieldSpecificStringMatcherContains() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withMatcher("firstname", contains());
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE '%do%')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingAtTheBeginningIncludingNull() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Fro");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(STARTING).withIncludeNullValues();
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname IS NULL OR firstname LIKE 'Fro%')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingOnTheEndingIncludingNull() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(ENDING).withIncludeNullValues();
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname IS NULL OR firstname LIKE '%do')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameIgnoreCaseFieldLevel() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("fro");
|
||||
|
||||
ExampleMatcher matcher = matching().withMatcher("firstname", startsWith().ignoreCase());
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname LIKE 'fro%')");
|
||||
|
||||
assertThat(example.getMatcher().getPropertySpecifiers().getForPath("firstname").getIgnoreCase()).isTrue();
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameWithStringMatchingContainingIncludingNull() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("do");
|
||||
|
||||
ExampleMatcher matcher = matching().withStringMatcher(CONTAINING).withIncludeNullValues();
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname IS NULL OR firstname LIKE '%do%')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameIgnoreCase() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
|
||||
ExampleMatcher matcher = matching().withIgnoreCase(true);
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname = 'Frodo')");
|
||||
|
||||
assertThat(example.getMatcher().isIgnoreCaseEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleWithFirstnameOrLastname() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
person.setLastname("Baggins");
|
||||
|
||||
ExampleMatcher matcher = matchingAny();
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname = 'Frodo') OR (lastname = 'Baggins')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleEvenHandlesInvisibleFields() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
person.setSecret("I have the ring!");
|
||||
|
||||
Example<Person> example = Example.of(person);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname = 'Frodo') AND (secret = 'I have the ring!')");
|
||||
}
|
||||
|
||||
@Test // #929
|
||||
void queryByExampleSupportsPropertyTransforms() {
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Frodo");
|
||||
person.setLastname("Baggins");
|
||||
person.setSecret("I have the ring!");
|
||||
|
||||
ExampleMatcher matcher = matching() //
|
||||
.withTransformer("firstname", o -> {
|
||||
if (o.isPresent()) {
|
||||
return o.map(o1 -> ((String) o1).toUpperCase());
|
||||
}
|
||||
return o;
|
||||
}) //
|
||||
.withTransformer("lastname", o -> {
|
||||
if (o.isPresent()) {
|
||||
return o.map(o1 -> ((String) o1).toLowerCase());
|
||||
}
|
||||
return o;
|
||||
});
|
||||
|
||||
Example<Person> example = Example.of(person, matcher);
|
||||
|
||||
Query query = exampleMapper.getMappedExample(example);
|
||||
|
||||
assertThat(query.getCriteria()) //
|
||||
.map(Object::toString) //
|
||||
.hasValue("(firstname = 'FRODO') AND (lastname = 'baggins') AND (secret = 'I have the ring!')");
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
static class Person {
|
||||
|
||||
@Id String id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
String secret;
|
||||
|
||||
// Override default visibility of getting the secret.
|
||||
private String getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user