diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index eba4466bb7..3670c2f1f0 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -76,6 +76,7 @@ * @author Greg Turnquist * @author Diego Krupitza * @author Jędrzej Biedrzycki + * @author Anton Molganov */ public abstract class QueryUtils { @@ -806,6 +807,16 @@ private static T getAnnotationProperty(Attribute attribute, String pro return join; } } + + for (Fetch fetch : from.getFetches()) { + + Join join = (Join) fetch; + if (joinType == join.getJoinType() + && join.getAttribute().getName().equals(attribute)) { + return join; + } + } + return from.join(attribute, joinType); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index fcf384fac7..6da910e7ba 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -56,6 +56,8 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; +import org.springframework.data.domain.ExampleMatcher.StringMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; 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..23bc7a115a --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/domain/support/QueryByExampleWithOptionalEmptyTests.java @@ -0,0 +1,70 @@ +/* + * 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() { + + UserWithOptionalField user = new UserWithOptionalField(); + user.setName("Greg"); + repository.saveAndFlush(user); + + UserWithOptionalField probe = new UserWithOptionalField(); + probe.setName("Greg"); + Example example = Example.of(probe); + + 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 {} + +}