Ignore fields with Optional.empty when performing Query by Example.

The Auditable interface introduces Optional getters, which when combined with Query by Example results in cryptic errors. By ignoring a probe's field that contains an Optional.empty, Query by Example works properly.

NOTE: This fix actually tests outside the originally detected scope of Auditable, verifying that ALL Optional.empty() fields are properly handled.

Closes #2176
Original pull request #2401
This commit is contained in:
Greg L. Turnquist
2022-01-07 09:25:19 -06:00
committed by Jens Schauder
parent 2f13a5eb92
commit a7fd176643
5 changed files with 162 additions and 0 deletions

View File

@@ -56,6 +56,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Oliver Gierke
* @author Jens Schauder
* @author Greg Turnquist
* @since 1.10
*/
public class QueryByExamplePredicateBuilder {
@@ -146,6 +147,10 @@ public class QueryByExamplePredicateBuilder {
Object attributeValue = optionalValue.get();
if (attributeValue == Optional.empty()) {
continue;
}
if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED)
|| (isAssociation(attribute) && !(from instanceof From))) {

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2008-2022 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.jpa.domain.sample;
import lombok.Data;
import java.util.Optional;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import org.springframework.lang.Nullable;
/**
* @author Greg Turnquist
*/
@Entity
@Data
public class UserWithOptionalField {
@Id @GeneratedValue private Long id;
private String name;
private String role;
public UserWithOptionalField() {
this.id = null;
this.name = null;
this.role = null;
}
public UserWithOptionalField(String name, @Nullable String role) {
this();
this.name = name;
this.role = role;
}
public Optional<String> getRole() {
return Optional.ofNullable(this.role);
}
public void setRole(Optional<String> role) {
this.role = role.orElse(null);
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2008-2022 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.jpa.domain.sample;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author Greg Turnquist
*/
public interface UserWithOptionalFieldRepository extends JpaRepository<UserWithOptionalField, Long> {}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2008-2022 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.jpa.domain.support;
import static org.assertj.core.api.Assertions.*;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.domain.Example;
import org.springframework.data.jpa.domain.sample.UserWithOptionalField;
import org.springframework.data.jpa.domain.sample.UserWithOptionalFieldRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* Integration test for {@link org.springframework.data.repository.query.QueryByExampleExecutor} involving
* {@link Optional#empty()}.
*
* @author Greg Turnquist
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class QueryByExampleWithOptionalEmptyTests {
@Autowired UserWithOptionalFieldRepository repository;
UserWithOptionalField user;
@Test
void queryByExampleTreatsEmptyOptionalsLikeNulls() {
// given
UserWithOptionalField user = new UserWithOptionalField();
user.setName("Greg");
repository.saveAndFlush(user);
// when
UserWithOptionalField probe = new UserWithOptionalField();
probe.setName("Greg");
Example<UserWithOptionalField> example = Example.of(probe);
// then
List<UserWithOptionalField> results = repository.findAll(example);
assertThat(results).hasSize(1);
assertThat(results).extracting(UserWithOptionalField::getName).containsExactly("Greg");
}
@Configuration
@EnableJpaRepositories(basePackageClasses = UserWithOptionalFieldRepository.class)
@ImportResource("classpath:infrastructure.xml")
static class JpaRepositoryConfig {}
}

View File

@@ -45,6 +45,7 @@
<class>org.springframework.data.jpa.domain.sample.Site</class>
<class>org.springframework.data.jpa.domain.sample.SpecialUser</class>
<class>org.springframework.data.jpa.domain.sample.User</class>
<class>org.springframework.data.jpa.domain.sample.UserWithOptionalField</class>
<class>org.springframework.data.jpa.domain.sample.VersionedUser</class>
<class>org.springframework.data.jpa.domain.sample.Dummy</class>
<class>org.springframework.data.jpa.domain.sample.SampleWithIdClassIncludingEntity</class>