diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 53767226d..6fcdcd76d 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -325,6 +325,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { */ private static class TupleBackedMap implements Map { + public static final String UNMODIFIABLE_MESSAGE = "A TupleBackedMap cannot be modified."; private final Tuple tuple; TupleBackedMap(Tuple tuple) { @@ -341,9 +342,22 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { return tuple.getElements().isEmpty(); } + /** + * If the key is not a {@code String} or not a key of the backing {@link Tuple} this returns {@code false}. + * Otherwise this returns {@code true} even when the value from the backing {@code Tuple} is {@code null}. + * + * @param key the key for which to get the value from the map. + * @return wether the key is an element of the backing tuple. + */ @Override public boolean containsKey(Object key) { - return key instanceof String && tuple.get((String) key) != null; + + try { + tuple.get((String) key); + return true; + } catch (IllegalArgumentException e) { + return false; + } } @Override @@ -351,29 +365,46 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { return Arrays.stream(tuple.toArray()).anyMatch(v -> v.equals(value)); } + /** + * If the key is not a {@code String} or not a key of the backing {@link Tuple} this returns {@code null}. + * Otherwise the value from the backing {@code Tuple} is returned, which also might be {@code null}. + * + * @param key the key for which to get the value from the map. + * @return the value of the backing {@link Tuple} for that key or {@code null}. + */ @Override + @Nullable public Object get(Object key) { - return key instanceof String ? tuple.get((String) key) : null; + + if (!(key instanceof String)) { + return null; + } + + try { + return tuple.get((String) key); + } catch (IllegalArgumentException e) { + return null; + } } @Override public Object put(String key, Object value) { - throw new UnsupportedOperationException("A TupleBakcedMap cannot be modified"); + throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE); } @Override public Object remove(Object key) { - throw new UnsupportedOperationException("A TupleBakcedMap cannot be modified"); + throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE); } @Override public void putAll(Map m) { - throw new UnsupportedOperationException("A TupleBakcedMap cannot be modified"); + throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE); } @Override public void clear() { - throw new UnsupportedOperationException("A TupleBakcedMap cannot be modified"); + throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE); } @Override diff --git a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java index 3858c73ad..93a39b0be 100644 --- a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java @@ -103,6 +103,14 @@ public class EclipseLinkNamespaceUserRepositoryTests extends NamespaceUserReposi @Test // DATAJPA-1248 public void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() {} + /** + * Ignored until https://bugs.eclipse.org/bugs/show_bug.cgi?id=525319 is fixed. + */ + @Ignore + @Override + @Test // DATAJPA-1301 + public void returnsNullValueInMap() {} + /** * TODO: Remove, once https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 is fixed. */ diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index f41bb7f09..c2e32464f 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -51,8 +52,6 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException; 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; @@ -61,6 +60,7 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; +import org.springframework.data.domain.ExampleMatcher.*; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.sample.Address; import org.springframework.data.jpa.domain.sample.Role; @@ -2173,6 +2173,31 @@ public class UserRepositoryTests { }); } + @Test // DATAJPA-1301 + public void returnsNullValueInMap() { + + firstUser.setLastname(null); + flushTestUsers(); + + Map map = repository.findMapWithNullValues(); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(map.keySet()).containsExactlyInAnyOrder("firstname", "lastname"); + + softly.assertThat(map.containsKey("firstname")).isTrue(); + softly.assertThat(map.containsKey("lastname")).isTrue(); + + softly.assertThat(map.get("firstname")).isEqualTo("Oliver"); + softly.assertThat(map.get("lastname")).isNull(); + + softly.assertThat(map.get("non-existent")).isNull(); + + softly.assertThat(map.get(new Object())).isNull(); + + softly.assertAll(); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index d847f6dc9..6f9b45618 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -18,6 +18,7 @@ package org.springframework.data.jpa.repository.sample; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -539,6 +540,10 @@ public interface UserRepository // DATAJPA-1273 List findByNamedQueryWithAliasInInvertedOrder(); + // DATAJPA-1301 + @Query("select firstname as firstname, lastname as lastname from User u where u.firstname = 'Oliver'") + Map findMapWithNullValues(); + interface RolesAndFirstname { String getFirstname();