diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java index 316297d74f7..1d61b7a43a1 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -250,11 +250,9 @@ public class BeanPropertyRowMapper implements RowMapper { for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) { if (pd.getWriteMethod() != null) { - String lowerCaseName = lowerCaseName(pd.getName()); - this.mappedProperties.put(lowerCaseName, pd); - String underscoreName = underscoreName(pd.getName()); - if (!lowerCaseName.equals(underscoreName)) { - this.mappedProperties.put(underscoreName, pd); + Set mappedNames = mappedNames(pd); + for (String mappedName : mappedNames) { + this.mappedProperties.put(mappedName, pd); } this.mappedPropertyNames.add(pd.getName()); } @@ -273,6 +271,29 @@ public class BeanPropertyRowMapper implements RowMapper { } } + /** + * Determine the mapped names for the given property. + *

Subclasses may override this method to customize the mapped names, + * adding to or removing from the set determined by this base method + * (which returns the property name in lower-case and underscore-based + * form), or replacing the set completely. + * @param pd the property descriptor discovered on initialization + * @return a set of mapped names + * @since 6.1.4 + * @see #lowerCaseName + * @see #underscoreName + */ + protected Set mappedNames(PropertyDescriptor pd) { + Set mappedNames = new HashSet<>(4); + String lowerCaseName = lowerCaseName(pd.getName()); + mappedNames.add(lowerCaseName); + String underscoreName = underscoreName(pd.getName()); + if (!lowerCaseName.equals(underscoreName)) { + mappedNames.add(underscoreName); + } + return mappedNames; + } + /** * Convert the given name to lower case. *

By default, conversions will happen within the US locale. diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java index b83219be06f..f7108e3e46b 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -16,6 +16,12 @@ package org.springframework.jdbc.core; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Date; +import java.util.Set; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -155,6 +161,16 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests { mock.verifyClosed(); } + @Test + void queryWithCustomNameMatchOnBirthDate() throws Exception { + Mock mock = new Mock(MockType.FOUR); + Person person = mock.getJdbcTemplate().queryForObject( + "select name, age, birthdate, balance from people", + new CustomBeanPropertyRowMapper()); + verifyPerson(person); + mock.verifyClosed(); + } + @Test void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception { Mock mock = new Mock(); @@ -179,4 +195,36 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests { assertThat(mapper.underscoreName(input)).isEqualTo(expected); } + + @Retention(RetentionPolicy.RUNTIME) + @interface MyColumnName { + + String value(); + } + + private static class CustomPerson extends Person { + + @MyColumnName("birthdate") + public void setBirth_date(Date date) { + super.setBirth_date(date); + } + } + + private static class CustomBeanPropertyRowMapper extends BeanPropertyRowMapper { + + public CustomBeanPropertyRowMapper() { + super(CustomPerson.class); + } + + @Override + protected Set mappedNames(PropertyDescriptor pd) { + Set mappedNames = super.mappedNames(pd); + MyColumnName customName = pd.getWriteMethod().getAnnotation(MyColumnName.class); + if (customName != null) { + mappedNames.add(customName.value()); + } + return mappedNames; + } + } + }