Introduce SimplePropertyRowMapper with flexible constructor/property/field mapping

Includes query(Class) method with value and property mapping support on JdbcClient.
JdbcClient's singleColumn/singleValue are declared without a Class parameter now.

Closes gh-26594
See gh-30931
This commit is contained in:
Juergen Hoeller
2023-08-15 13:14:52 +02:00
parent 443e3d5fa6
commit d89e305c87
15 changed files with 619 additions and 97 deletions

View File

@@ -167,6 +167,7 @@ public abstract class AbstractRowMapperTests {
}
else {
given(resultSet.findColumn("birthdate")).willThrow(new SQLException());
given(resultSet.findColumn("birthDate")).willThrow(new SQLException());
given(resultSet.findColumn("birth_date")).willReturn(3);
}
given(resultSet.findColumn("balance")).willReturn(4);

View File

@@ -95,7 +95,7 @@ class DataClassRowMapperTests extends AbstractRowMapperTests {
}
static record RecordPerson(String name, long age, Date birth_date, BigDecimal balance) {
record RecordPerson(String name, long age, Date birth_date, BigDecimal balance) {
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2002-2023 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.jdbc.core;
import java.math.BigDecimal;
import java.util.Date;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.test.ConcretePerson;
import org.springframework.jdbc.core.test.ConstructorPerson;
import org.springframework.jdbc.core.test.ConstructorPersonWithGenerics;
import org.springframework.jdbc.core.test.ConstructorPersonWithSetters;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SimplePropertyRowMapper}.
*
* @author Juergen Hoeller
* @since 6.1
*/
class SimplePropertyRowMapperTests extends AbstractRowMapperTests {
@Test
void staticQueryWithDataClass() throws Exception {
Mock mock = new Mock();
ConstructorPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new SimplePropertyRowMapper<>(ConstructorPerson.class));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void staticQueryWithDataClassAndGenerics() throws Exception {
Mock mock = new Mock();
ConstructorPersonWithGenerics person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new SimplePropertyRowMapper<>(ConstructorPersonWithGenerics.class));
assertThat(person.name()).isEqualTo("Bubba");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
assertThat(person.balance()).containsExactly(new BigDecimal("1234.56"));
mock.verifyClosed();
}
@Test
void staticQueryWithDataClassAndSetters() throws Exception {
Mock mock = new Mock(MockType.FOUR);
ConstructorPersonWithSetters person = mock.getJdbcTemplate().queryForObject(
"select name, age, birthdate, balance from people",
new SimplePropertyRowMapper<>(ConstructorPersonWithSetters.class));
assertThat(person.name()).isEqualTo("BUBBA");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
mock.verifyClosed();
}
@Test
void staticQueryWithPlainSetters() throws Exception {
Mock mock = new Mock();
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new SimplePropertyRowMapper<>(ConcretePerson.class));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void staticQueryWithDataRecord() throws Exception {
Mock mock = new Mock();
RecordPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new SimplePropertyRowMapper<>(RecordPerson.class));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void staticQueryWithDataFields() throws Exception {
Mock mock = new Mock();
FieldPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new SimplePropertyRowMapper<>(FieldPerson.class));
verifyPerson(person);
mock.verifyClosed();
}
@Test
void staticQueryWithIncompleteDataFields() throws Exception {
Mock mock = new Mock();
IncompleteFieldPerson person = mock.getJdbcTemplate().queryForObject(
"select name, age, birth_date, balance from people",
new SimplePropertyRowMapper<>(IncompleteFieldPerson.class));
verifyPerson(person);
mock.verifyClosed();
}
protected void verifyPerson(RecordPerson person) {
assertThat(person.name()).isEqualTo("Bubba");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
verifyPersonViaBeanWrapper(person);
}
protected void verifyPerson(FieldPerson person) {
assertThat(person.name).isEqualTo("Bubba");
assertThat(person.age).isEqualTo(22L);
assertThat(person.birth_date).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
assertThat(person.balance).isEqualTo(new BigDecimal("1234.56"));
}
protected void verifyPerson(IncompleteFieldPerson person) {
assertThat(person.name).isEqualTo("Bubba");
assertThat(person.age).isEqualTo(22L);
assertThat(person.balance).isEqualTo(new BigDecimal("1234.56"));
}
record RecordPerson(String name, long age, Date birth_date, BigDecimal balance) {
}
static class FieldPerson {
String name;
long age;
Date birth_date;
BigDecimal balance;
}
static class IncompleteFieldPerson {
String name;
long age;
BigDecimal balance;
}
}

View File

@@ -108,7 +108,7 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForListWithIndexedParamAndSingleRowAndColumn() throws Exception {
public void testQueryForListWithIndexedParamAndSingleRow() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
@@ -123,14 +123,14 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForListWithIndexedParamAndIntegerElementAndSingleRowAndColumn() throws Exception {
public void testQueryForListWithIndexedParamAndSingleColumn() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(11);
given(resultSet.getObject(1)).willReturn(11);
List<Integer> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < ?")
.param(1, 3)
.query().singleColumn(Integer.class);
.query().singleColumn();
assertThat(li.size()).as("All rows returned").isEqualTo(1);
assertThat(li.get(0)).as("First row is Integer").isEqualTo(11);
@@ -139,7 +139,7 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForMapWithIndexedParamAndSingleRowAndColumn() throws Exception {
public void testQueryForMapWithIndexedParamAndSingleRow() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
@@ -185,11 +185,11 @@ public class JdbcClientQueryTests {
public void testQueryForObjectWithIndexedParamAndInteger() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
given(resultSet.getObject(1)).willReturn(22);
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?")
.param(1, 3)
.query().singleValue(Integer.class);
.query().singleValue();
assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -200,11 +200,11 @@ public class JdbcClientQueryTests {
public void testQueryForIntWithIndexedParam() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
given(resultSet.getObject(1)).willReturn(22);
int i = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?")
.param(1, 3)
.query().singleValue(Integer.class);
.query().singleValue();
assertThat(i).as("Return of an int").isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -246,7 +246,7 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForListWithNamedParamAndSingleRowAndColumn() throws Exception {
public void testQueryForListWithNamedParamAndSingleRow() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
@@ -262,14 +262,14 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForListWithNamedParamAndIntegerElementAndSingleRowAndColumn() throws Exception {
public void testQueryForListWithNamedParamAndSingleColumn() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(11);
given(resultSet.getObject(1)).willReturn(11);
List<Integer> li = client.sql("SELECT AGE FROM CUSTMR WHERE ID < :id")
.param("id", 3)
.query().singleColumn(Integer.class);
.query().singleColumn();
assertThat(li.size()).as("All rows returned").isEqualTo(1);
assertThat(li.get(0)).as("First row is Integer").isEqualTo(11);
@@ -278,7 +278,7 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForMapWithNamedParamAndSingleRowAndColumn() throws Exception {
public void testQueryForMapWithNamedParamAndSingleRow() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(11);
@@ -308,14 +308,63 @@ public class JdbcClientQueryTests {
}
@Test
public void testQueryForObjectWithNamedParamAndInteger() throws Exception {
public void testQueryForObjectWithNamedParamAndMappedSimpleValue() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.param("id", 3)
.query().singleValue(Integer.class);
.query(Integer.class)
.single();
assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
verify(preparedStatement).setObject(1, 3);
}
@Test
public void testQueryForObjectWithNamedParamAndMappedRecord() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.findColumn("age")).willReturn(1);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
AgeRecord value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.param("id", 3)
.query(AgeRecord.class)
.single();
assertThat(value.age()).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
verify(preparedStatement).setObject(1, 3);
}
@Test
public void testQueryForObjectWithNamedParamAndMappedFieldHolder() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
AgeFieldHolder value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.param("id", 3)
.query(AgeFieldHolder.class)
.single();
assertThat(value.age).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
verify(preparedStatement).setObject(1, 3);
}
@Test
public void testQueryForObjectWithNamedParamAndInteger() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getObject(1)).willReturn(22);
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.param("id", 3)
.query().singleValue();
assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -326,11 +375,11 @@ public class JdbcClientQueryTests {
public void testQueryForObjectWithNamedParamAndList() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
given(resultSet.getObject(1)).willReturn(22);
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)")
.param("ids", Arrays.asList(3, 4))
.query().singleValue(Integer.class);
.query().singleValue();
assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)");
@@ -341,14 +390,14 @@ public class JdbcClientQueryTests {
public void testQueryForObjectWithNamedParamAndListOfExpressionLists() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
given(resultSet.getObject(1)).willReturn(22);
List<Object[]> l1 = new ArrayList<>();
l1.add(new Object[] {3, "Rod"});
l1.add(new Object[] {4, "Juergen"});
Integer value = client.sql("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN (:multiExpressionList)")
.param("multiExpressionList", l1)
.query().singleValue(Integer.class);
.query().singleValue();
assertThat(value).isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE (ID, NAME) IN ((?, ?), (?, ?))");
@@ -359,11 +408,11 @@ public class JdbcClientQueryTests {
public void testQueryForIntWithNamedParam() throws Exception {
given(resultSet.getMetaData()).willReturn(resultSetMetaData);
given(resultSet.next()).willReturn(true, false);
given(resultSet.getInt(1)).willReturn(22);
given(resultSet.getObject(1)).willReturn(22);
int i = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.param("id", 3)
.query().singleValue(Integer.class);
.query().singleValue();
assertThat(i).as("Return of an int").isEqualTo(22);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -378,7 +427,7 @@ public class JdbcClientQueryTests {
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.paramSource(new ParameterBean(3))
.query().singleValue(Long.class);
.query(Long.class).single();
assertThat(l).as("Return of a long").isEqualTo(87);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -393,7 +442,7 @@ public class JdbcClientQueryTests {
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID IN (:ids)")
.paramSource(new ParameterCollectionBean(3, 5))
.query().singleValue(Long.class);
.query(Long.class).single();
assertThat(l).as("Return of a long").isEqualTo(87);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID IN (?, ?)");
@@ -409,7 +458,7 @@ public class JdbcClientQueryTests {
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.paramSource(new ParameterRecord(3))
.query().singleValue(Long.class);
.query(Long.class).single();
assertThat(l).as("Return of a long").isEqualTo(87);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -424,7 +473,7 @@ public class JdbcClientQueryTests {
long l = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
.paramSource(new ParameterFieldHolder(3))
.query().singleValue(Long.class);
.query(Long.class).single();
assertThat(l).as("Return of a long").isEqualTo(87);
verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?");
@@ -473,4 +522,14 @@ public class JdbcClientQueryTests {
public int id;
}
record AgeRecord(int age) {
}
static class AgeFieldHolder {
public int age;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -29,15 +29,15 @@ public class ConstructorPersonWithGenerics {
private final long age;
private final Date birthDate;
private final Date bd;
private final List<BigDecimal> balance;
public ConstructorPersonWithGenerics(String name, long age, Date birth_date, List<BigDecimal> balance) {
public ConstructorPersonWithGenerics(String name, long age, Date birthDate, List<BigDecimal> balance) {
this.name = name;
this.age = age;
this.birthDate = birth_date;
this.bd = birthDate;
this.balance = balance;
}
@@ -51,7 +51,7 @@ public class ConstructorPersonWithGenerics {
}
public Date birthDate() {
return this.birthDate;
return this.bd;
}
public List<BigDecimal> balance() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -33,20 +33,19 @@ public class ConstructorPersonWithSetters {
private BigDecimal balance;
public ConstructorPersonWithSetters(String name, long age, Date birthDate, BigDecimal balance) {
public ConstructorPersonWithSetters(String name, long age, BigDecimal balance) {
this.name = name.toUpperCase();
this.age = age;
this.birthDate = birthDate;
this.balance = balance;
}
public void setName(String name) {
this.name = name;
throw new UnsupportedOperationException();
}
public void setAge(long age) {
this.age = age;
throw new UnsupportedOperationException();
}
public void setBirthDate(Date birthDate) {
@@ -54,7 +53,7 @@ public class ConstructorPersonWithSetters {
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
throw new UnsupportedOperationException();
}
public String name() {