Skip to content

Commit 4fe3ca1

Browse files
committed
DataClassRowMapper suppresses setter method calls for constructor-bound properties
Closes gh-26569
1 parent c45c46d commit 4fe3ca1

File tree

5 files changed

+181
-15
lines changed

5 files changed

+181
-15
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,40 @@ protected void initialize(Class<T> mappedClass) {
225225

226226
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
227227
if (pd.getWriteMethod() != null) {
228-
this.mappedFields.put(lowerCaseName(pd.getName()), pd);
229-
String underscoredName = underscoreName(pd.getName());
230-
if (!lowerCaseName(pd.getName()).equals(underscoredName)) {
231-
this.mappedFields.put(underscoredName, pd);
228+
String lowerCaseName = lowerCaseName(pd.getName());
229+
this.mappedFields.put(lowerCaseName, pd);
230+
String underscoreName = underscoreName(pd.getName());
231+
if (!lowerCaseName.equals(underscoreName)) {
232+
this.mappedFields.put(underscoreName, pd);
232233
}
233234
this.mappedProperties.add(pd.getName());
234235
}
235236
}
236237
}
237238

239+
/**
240+
* Remove the specified property from the mapped fields.
241+
* @param propertyName the property name (as used by property descriptors)
242+
* @since 5.3.9
243+
*/
244+
protected void suppressProperty(String propertyName) {
245+
if (this.mappedFields != null) {
246+
this.mappedFields.remove(lowerCaseName(propertyName));
247+
this.mappedFields.remove(underscoreName(propertyName));
248+
}
249+
}
250+
251+
/**
252+
* Convert the given name to lower case.
253+
* By default, conversions will happen within the US locale.
254+
* @param name the original name
255+
* @return the converted name
256+
* @since 4.2
257+
*/
258+
protected String lowerCaseName(String name) {
259+
return name.toLowerCase(Locale.US);
260+
}
261+
238262
/**
239263
* Convert a name in camelCase to an underscored name in lower case.
240264
* Any upper case letters are converted to lower case with a preceding underscore.
@@ -261,17 +285,6 @@ protected String underscoreName(String name) {
261285
return result.toString();
262286
}
263287

264-
/**
265-
* Convert the given name to lower case.
266-
* By default, conversions will happen within the US locale.
267-
* @param name the original name
268-
* @return the converted name
269-
* @since 4.2
270-
*/
271-
protected String lowerCaseName(String name) {
272-
return name.toLowerCase(Locale.US);
273-
}
274-
275288

276289
/**
277290
* Extract the values for all columns in the current row.

spring-jdbc/src/main/java/org/springframework/jdbc/core/DataClassRowMapper.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ protected void initialize(Class<T> mappedClass) {
8080
int paramCount = this.mappedConstructor.getParameterCount();
8181
if (paramCount > 0) {
8282
this.constructorParameterNames = BeanUtils.getParameterNames(this.mappedConstructor);
83+
for (String name : this.constructorParameterNames) {
84+
suppressProperty(name);
85+
}
8386
this.constructorParameterTypes = new TypeDescriptor[paramCount];
8487
for (int i = 0; i < paramCount; i++) {
8588
this.constructorParameterTypes[i] = new TypeDescriptor(new MethodParameter(this.mappedConstructor, i));

spring-jdbc/src/test/java/org/springframework/jdbc/core/DataClassRowMapperTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.jdbc.core.test.ConstructorPerson;
2727
import org.springframework.jdbc.core.test.ConstructorPersonWithGenerics;
28+
import org.springframework.jdbc.core.test.ConstructorPersonWithSetters;
2829

2930
import static org.assertj.core.api.Assertions.assertThat;
3031

@@ -62,4 +63,20 @@ public void testStaticQueryWithDataClassAndGenerics() throws Exception {
6263
mock.verifyClosed();
6364
}
6465

66+
@Test
67+
public void testStaticQueryWithDataClassAndSetters() throws Exception {
68+
Mock mock = new Mock();
69+
List<ConstructorPersonWithSetters> result = mock.getJdbcTemplate().query(
70+
"select name, age, birth_date, balance from people",
71+
new DataClassRowMapper<>(ConstructorPersonWithSetters.class));
72+
assertThat(result.size()).isEqualTo(1);
73+
ConstructorPersonWithSetters person = result.get(0);
74+
assertThat(person.name()).isEqualTo("BUBBA");
75+
assertThat(person.age()).isEqualTo(22L);
76+
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
77+
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
78+
79+
mock.verifyClosed();
80+
}
81+
6582
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.core.test;
18+
19+
import java.math.BigDecimal;
20+
import java.util.Date;
21+
22+
/**
23+
* @author Juergen Hoeller
24+
*/
25+
public class ConstructorPersonWithSetters {
26+
27+
private String name;
28+
29+
private long age;
30+
31+
private Date birth_date;
32+
33+
private BigDecimal balance;
34+
35+
36+
public ConstructorPersonWithSetters(String name, long age, Date birth_date, BigDecimal balance) {
37+
this.name = name.toUpperCase();
38+
this.age = age;
39+
this.birth_date = birth_date;
40+
this.balance = balance;
41+
}
42+
43+
44+
public void setName(String name) {
45+
this.name = name;
46+
}
47+
48+
public void setAge(long age) {
49+
this.age = age;
50+
}
51+
52+
public void setBirth_date(Date birth_date) {
53+
this.birth_date = birth_date;
54+
}
55+
56+
public void setBalance(BigDecimal balance) {
57+
this.balance = balance;
58+
}
59+
60+
public String name() {
61+
return this.name;
62+
}
63+
64+
public long age() {
65+
return this.age;
66+
}
67+
68+
public Date birth_date() {
69+
return this.birth_date;
70+
}
71+
72+
public BigDecimal balance() {
73+
return this.balance;
74+
}
75+
76+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.core
18+
19+
import org.assertj.core.api.Assertions
20+
import org.junit.jupiter.api.Test
21+
import org.springframework.jdbc.core.test.ConstructorPerson
22+
import java.math.BigDecimal
23+
import java.util.*
24+
25+
class KotlinDataClassRowMapperTests : AbstractRowMapperTests() {
26+
27+
@Test
28+
fun testStaticQueryWithDataClass() {
29+
val mock = Mock()
30+
val result = mock.jdbcTemplate.query(
31+
"select name, age, birth_date, balance from people",
32+
DataClassRowMapper(ConstructorPerson::class.java)
33+
)
34+
Assertions.assertThat(result.size).isEqualTo(1)
35+
verifyPerson(result[0])
36+
mock.verifyClosed()
37+
}
38+
39+
@Test
40+
fun testInitPropertiesAreNotOverridden() {
41+
val mock = Mock()
42+
val result = mock.jdbcTemplate.query(
43+
"select name, age, birth_date, balance from people",
44+
DataClassRowMapper(KotlinPerson::class.java)
45+
)
46+
Assertions.assertThat(result.size).isEqualTo(1)
47+
Assertions.assertThat(result[0].name).isEqualTo("Bubba appended by init")
48+
}
49+
50+
51+
data class KotlinPerson(var name: String, val age: Long, val birth_date: Date, val balance: BigDecimal) {
52+
init {
53+
name += " appended by init"
54+
}
55+
}
56+
57+
}

0 commit comments

Comments
 (0)