From 7998ec79e13d928bb62bd3001c725a7b86395424 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 3 Dec 2024 10:53:35 +0100 Subject: [PATCH] Fix Native Query projections mapping to DTOs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now provide the target type to createNativeQuery(…) if we detect that a native query result type is a projection and not an interface. Closes #2757 --- .../jpa/repository/query/NativeJpaQuery.java | 13 +++++++++--- .../jpa/repository/UserRepositoryTests.java | 14 +++++++++++++ .../jpa/repository/sample/NameOnlyRecord.java | 20 +++++++++++++++++++ .../jpa/repository/sample/UserRepository.java | 3 +++ 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java index 61c4c6ef1..c664af9f1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java @@ -93,8 +93,15 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { return result; } - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // - ? Tuple.class - : result; + if (returnedType.isProjecting()) { + + if (returnedType.getReturnedType().isInterface()) { + return Tuple.class; + } + + return returnedType.getReturnedType(); + } + + return result; } } 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 225336983..caf40e1d9 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 @@ -67,6 +67,7 @@ import org.springframework.data.jpa.domain.sample.QUser; import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.SpecialUser; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.repository.sample.NameOnlyRecord; import org.springframework.data.jpa.repository.sample.SampleEvaluationContextExtension.SampleSecurityContextHolder; import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.jpa.repository.sample.UserRepository.NameOnly; @@ -2979,6 +2980,19 @@ class UserRepositoryTests { assertThat(result.getLastname()).isEqualTo(user.getLastname()); } + @Test // GH-2757 + void supportsRecordsWithNativeQueries() { + + flushTestUsers(); + + User user = repository.findAll().get(0); + + NameOnlyRecord result = repository.findRecordProjectionByNativeQuery(user.getId()); + + assertThat(result.firstname()).isEqualTo(user.getFirstname()); + assertThat(result.lastname()).isEqualTo(user.getLastname()); + } + @Test // DATAJPA-1248 void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java new file mode 100644 index 000000000..d8dd5fa1b --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/NameOnlyRecord.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018-2024 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.repository.sample; + +public record NameOnlyRecord(String firstname, String lastname) { + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index e8eb6ae75..76896ece7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -561,6 +561,9 @@ public interface UserRepository extends JpaRepository, JpaSpecifi @Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true) NameOnly findByNativeQuery(Integer id); + @NativeQuery("SELECT firstname, lastname FROM SD_User WHERE id = ?1") + NameOnlyRecord findRecordProjectionByNativeQuery(Integer id); + // GH-3155 @NativeQuery(value = "SELECT emailaddress, secondary_email_address FROM SD_User WHERE id = ?1", sqlResultSetMapping = "emailDto")