Skip to content

Commit 1e6a8e6

Browse files
Added ResultSetMapping for Non-Entity classes in Spring JPA
1 parent 72a347f commit 1e6a8e6

File tree

5 files changed

+148
-7
lines changed

5 files changed

+148
-7
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
import jakarta.persistence.TupleElement;
2424
import jakarta.persistence.TypedQuery;
2525

26-
import java.util.Arrays;
27-
import java.util.Collection;
28-
import java.util.HashMap;
29-
import java.util.List;
30-
import java.util.Map;
31-
import java.util.Set;
26+
import java.lang.reflect.Constructor;
27+
import java.lang.reflect.InvocationTargetException;
28+
import java.lang.reflect.Method;
29+
import java.util.*;
30+
import java.util.concurrent.ConcurrentHashMap;
3231
import java.util.function.UnaryOperator;
3332
import java.util.stream.Collectors;
3433

34+
import org.hibernate.jpa.spi.NativeQueryTupleTransformer;
3535
import org.springframework.core.convert.converter.Converter;
3636
import org.springframework.data.jpa.provider.PersistenceProvider;
3737
import org.springframework.data.jpa.repository.EntityGraph;
@@ -51,6 +51,7 @@
5151
import org.springframework.jdbc.support.JdbcUtils;
5252
import org.springframework.lang.Nullable;
5353
import org.springframework.util.Assert;
54+
import org.springframework.util.ReflectionUtils;
5455

5556
/**
5657
* Abstract base class to implement {@link RepositoryQuery}s.
@@ -150,10 +151,55 @@ private Object doExecute(JpaQueryExecution execution, Object[] values) {
150151

151152
JpaParametersParameterAccessor accessor = obtainParameterAccessor(values);
152153
Object result = execution.execute(this, accessor);
153-
154154
ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);
155+
Class<?> clazz = method.getReturnedObjectType();
156+
Method[] methods = ReflectionUtils.getDeclaredMethods(clazz);
157+
Map<String, Method> setterMethodsMap = new ConcurrentHashMap<>();
158+
for(Method method : methods)
159+
{
160+
String methodName = method.getName();
161+
if(methodName.startsWith("set") && method.getParameterCount() == 1)
162+
{
163+
String fieldName = methodName.substring(3).toLowerCase();
164+
setterMethodsMap.put(fieldName, method);
165+
}
166+
}
167+
try {
168+
Constructor constructor = clazz.getConstructor();
169+
if(result instanceof Collection<?>)
170+
{
171+
Collection resultList = result instanceof List<?> ? new ArrayList<>() : new HashSet();
172+
for(var item : (Iterable) result)
173+
{
174+
var res = getResultObject((Tuple) item, constructor, setterMethodsMap);
175+
resultList.add(res);
176+
}
177+
return resultList;
178+
}
179+
else return getResultObject((Tuple) result, constructor, setterMethodsMap);
180+
}
181+
catch (Exception e) {}
182+
155183
return withDynamicProjection.processResult(result,
156184
new TupleConverter(withDynamicProjection.getReturnedType(), method.isNativeQuery()));
185+
186+
}
187+
188+
189+
@Nullable
190+
private static Object getResultObject(Tuple item, Constructor constructor, Map<String, Method> setterMethodsMap)
191+
throws InstantiationException, IllegalAccessException, InvocationTargetException {
192+
var res = constructor.newInstance();
193+
for(var field : item.getElements())
194+
{
195+
String fieldName = field.getAlias().toLowerCase();
196+
if(setterMethodsMap.containsKey(fieldName))
197+
{
198+
Object val = item.get(fieldName);
199+
setterMethodsMap.get(fieldName).invoke(res, val);
200+
}
201+
}
202+
return res;
157203
}
158204

159205
private JpaParametersParameterAccessor obtainParameterAccessor(Object[] values) {

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Customer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,15 @@ public class Customer {
2828
@Id Long id;
2929

3030
String name;
31+
32+
public Customer()
33+
{
34+
35+
}
36+
37+
public Customer(Long id, String name) {
38+
this.id = id;
39+
this.name = name;
40+
}
41+
3142
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.springframework.data.jpa.domain.sample;
2+
3+
import jakarta.persistence.Id;
4+
5+
public class CustomerDTO {
6+
7+
Long id;
8+
9+
String name;
10+
11+
public void setId(Long id) {
12+
this.id = id;
13+
}
14+
15+
public void setName(String name) {
16+
this.name = name;
17+
}
18+
19+
public Long getId() {
20+
return id;
21+
}
22+
23+
public String getName() {
24+
return name;
25+
}
26+
27+
@Override
28+
public String toString() {
29+
return "CustomerDTO{" +
30+
"id=" + id +
31+
", name='" + name + '\'' +
32+
'}';
33+
}
34+
}
35+
36+
//public record CustomerDTO(Long id, String name) {
37+
//}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@
3737
import org.springframework.data.domain.Slice;
3838
import org.springframework.data.domain.Sort;
3939
import org.springframework.data.domain.Window;
40+
import org.springframework.data.jpa.domain.sample.Customer;
41+
import org.springframework.data.jpa.domain.sample.CustomerDTO;
4042
import org.springframework.data.jpa.domain.sample.Role;
4143
import org.springframework.data.jpa.domain.sample.User;
4244
import org.springframework.data.jpa.provider.PersistenceProvider;
45+
import org.springframework.data.jpa.repository.sample.CustomerRepository;
4346
import org.springframework.data.jpa.repository.sample.RoleRepository;
4447
import org.springframework.data.jpa.repository.sample.UserRepository;
4548
import org.springframework.data.jpa.repository.sample.UserRepository.IdOnly;
@@ -68,6 +71,7 @@ class UserRepositoryFinderTests {
6871
@Autowired UserRepository userRepository;
6972
@Autowired RoleRepository roleRepository;
7073
@Autowired EntityManager em;
74+
@Autowired CustomerRepository customerRepository;
7175

7276
PersistenceProvider provider;
7377

@@ -77,6 +81,8 @@ class UserRepositoryFinderTests {
7781
private Role drummer;
7882
private Role guitarist;
7983
private Role singer;
84+
private Customer customer1;
85+
private Customer customer2;
8086

8187
@BeforeEach
8288
void setUp() {
@@ -88,15 +94,38 @@ void setUp() {
8894
dave = userRepository.save(new User("Dave", "Matthews", "[email protected]", singer));
8995
carter = userRepository.save(new User("Carter", "Beauford", "[email protected]", singer, drummer));
9096
oliver = userRepository.save(new User("Oliver August", "Matthews", "[email protected]"));
97+
customer1 = customerRepository.save(new Customer(1L,"Raja"));
98+
customer2 = customerRepository.save(new Customer(2L, "Raja"));
9199

92100
provider = PersistenceProvider.fromEntityManager(em);
93101
}
94102

103+
104+
@Test
105+
void testMapping()
106+
{
107+
List<CustomerDTO> customerDTOList = customerRepository.findByNameNativeQuery2("Raja");
108+
assertThat(customerDTOList.size()).isEqualTo(2);
109+
System.out.println(customerDTOList);
110+
}
111+
112+
@Test
113+
void testMapping2()
114+
{
115+
CustomerDTO customerDTO = customerRepository.findByNameNativeQuery3("Raja");
116+
System.out.println(customerDTO);
117+
assertThat(customerDTO.getId()).isEqualTo(1);
118+
assertThat(customerDTO.getName()).isEqualTo("Raja");
119+
}
120+
121+
122+
95123
@AfterEach
96124
void clearUp() {
97125

98126
userRepository.deleteAll();
99127
roleRepository.deleteAll();
128+
customerRepository.deleteAll();
100129
}
101130

102131
/**
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.springframework.data.jpa.repository.sample;
2+
3+
import org.springframework.data.jpa.domain.sample.Customer;
4+
import org.springframework.data.jpa.domain.sample.CustomerDTO;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Query;
7+
import org.springframework.data.repository.query.Param;
8+
import java.util.List;
9+
10+
public interface CustomerRepository extends JpaRepository<Customer, Long> {
11+
12+
@Query(value = "select id as Id, name as name from Customer", nativeQuery = true)
13+
List<CustomerDTO> findByNameNativeQuery2(@Param("name") String name);
14+
15+
@Query(value = "select id as Id, name as name from Customer where rownum=1", nativeQuery = true)
16+
CustomerDTO findByNameNativeQuery3(@Param("name") String name);
17+
18+
}

0 commit comments

Comments
 (0)