From d57ad7165a019675b299e532ea6ae267859f5a29 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Fri, 7 Jan 2022 09:25:19 -0600 Subject: [PATCH] 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 --- .../QueryByExamplePredicateBuilder.java | 5 ++ .../domain/sample/UserWithOptionalField.java | 60 +++++++++++++++ .../UserWithOptionalFieldRepository.java | 23 ++++++ .../QueryByExampleWithOptionalEmptyTests.java | 73 +++++++++++++++++++ src/test/resources/META-INF/persistence.xml | 1 + 5 files changed, 162 insertions(+) create mode 100644 src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java create mode 100644 src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java create mode 100644 src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java diff --git a/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java b/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java index be9b899d80..a21726af93 100644 --- a/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java +++ b/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java @@ -56,6 +56,7 @@ * @author Mark Paluch * @author Oliver Gierke * @author Jens Schauder + * @author Greg Turnquist * @since 1.10 */ public class QueryByExamplePredicateBuilder { @@ -146,6 +147,10 @@ static List getPredicates(String path, CriteriaBuilder cb, Path fr Object attributeValue = optionalValue.get(); + if (attributeValue == Optional.empty()) { + continue; + } + if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED) || (isAssociation(attribute) && !(from instanceof From))) { diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java b/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java new file mode 100644 index 0000000000..0c8f7593fb --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java @@ -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 javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.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 getRole() { + return Optional.ofNullable(this.role); + } + + public void setRole(Optional role) { + this.role = role.orElse(null); + } +} diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java b/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java new file mode 100644 index 0000000000..edf5f9d2ea --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalFieldRepository.java @@ -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 {} diff --git a/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java b/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java new file mode 100644 index 0000000000..59d6080227 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java @@ -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 example = Example.of(probe); + + // then + List 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 {} + +} diff --git a/src/test/resources/META-INF/persistence.xml b/src/test/resources/META-INF/persistence.xml index d5fbd08bc8..4932fb86a9 100644 --- a/src/test/resources/META-INF/persistence.xml +++ b/src/test/resources/META-INF/persistence.xml @@ -45,6 +45,7 @@ org.springframework.data.jpa.domain.sample.Site org.springframework.data.jpa.domain.sample.SpecialUser org.springframework.data.jpa.domain.sample.User + org.springframework.data.jpa.domain.sample.UserWithOptionalField org.springframework.data.jpa.domain.sample.VersionedUser org.springframework.data.jpa.domain.sample.Dummy org.springframework.data.jpa.domain.sample.SampleWithIdClassIncludingEntity