JDBCTemplate set nested POJO with BeanPropertyRowMapper JDBCTemplate set nested POJO with BeanPropertyRowMapper postgresql postgresql

JDBCTemplate set nested POJO with BeanPropertyRowMapper


Perhaps you could pass in a custom RowMapper that could map each row of an aggregate join query (between message and user) to a Message and nested User. Something like this:

List<Message> messages = jdbcTemplate.query("SELECT * FROM message m, user u WHERE u.message_id = m.message_id", new RowMapper<Message>() {    @Override    public Message mapRow(ResultSet rs, int rowNum) throws SQLException {        Message message = new Message();        message.setTitle(rs.getString(1));        message.setQuestion(rs.getString(2));        User user = new User();        user.setUserName(rs.getString(3));        user.setDisplayName(rs.getString(4));        message.setUser(user);        return message;    }});


A bit late to the party however I found this when I was googling the same question and I found a different solution that may be favorable for others in the future.

Unfortunately there is not a native way to achieve the nested scenario without making a customer RowMapper. However I will share an easier way to make said custom RowMapper than some of the other solutions here.

Given your scenario you can do the following:

class User {    String user_name;    String display_name;}class Message {    String title;    String question;    User user;}public class MessageRowMapper implements RowMapper<Message> {    @Override    public Message mapRow(ResultSet rs, int rowNum) throws SQLException {        User user = (new BeanPropertyRowMapper<>(User.class)).mapRow(rs,rowNum);        Message message = (new BeanPropertyRowMapper<>(Message.class)).mapRow(rs,rowNum);        message.setUser(user);        return message;     }}

The key thing to remember with BeanPropertyRowMapper is that you have to follow the naming of your columns and the properties of your class members to the letter with the following exceptions (see Spring Documentation):

  • column names are aliased exactly
  • column names with underscores will be converted into "camel" case (ie. MY_COLUMN_WITH_UNDERSCORES == myColumnWithUnderscores)


Spring introduced a new AutoGrowNestedPaths property into the BeanMapper interface.

As long as the SQL query formats the column names with a . separator (as before) then the Row mapper will automatically target inner objects.

With this, I created a new generic row mapper as follows:

QUERY:

SELECT title AS "message.title", question AS "message.question", user_name AS "user.user_name", display_name AS "user.display_name" FROM message, user WHERE user_id = message_id

ROW MAPPER:

package nested_row_mapper;import org.springframework.beans.*;import org.springframework.jdbc.core.RowMapper;import org.springframework.jdbc.support.JdbcUtils;import java.sql.ResultSet;import java.sql.ResultSetMetaData;import java.sql.SQLException;public class NestedRowMapper<T> implements RowMapper<T> {  private Class<T> mappedClass;  public NestedRowMapper(Class<T> mappedClass) {    this.mappedClass = mappedClass;  }  @Override  public T mapRow(ResultSet rs, int rowNum) throws SQLException {    T mappedObject = BeanUtils.instantiate(this.mappedClass);    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);    bw.setAutoGrowNestedPaths(true);    ResultSetMetaData meta_data = rs.getMetaData();    int columnCount = meta_data.getColumnCount();    for (int index = 1; index <= columnCount; index++) {      try {        String column = JdbcUtils.lookupColumnName(meta_data, index);        Object value = JdbcUtils.getResultSetValue(rs, index, Class.forName(meta_data.getColumnClassName(index)));        bw.setPropertyValue(column, value);      } catch (TypeMismatchException | NotWritablePropertyException | ClassNotFoundException e) {         // Ignore      }    }    return mappedObject;  }}