JDBCTemplate 使用 BeanPropertyRowMapper 设置嵌套的 POJO

新手上路,请多包涵

给定以下 POJO 示例:(假设所有属性都有 Getters 和 Setters)

 class User {
    String user_name;
    String display_name;
}

class Message {
    String title;
    String question;
    User user;
}

可以轻松查询数据库(在我的例子中是 postgres)并使用 BeanPropertyRowMapper 填充 Message 类列表,其中 db 字段与 POJO 中的属性相匹配:(假设 DB 表具有与 POJO 属性对应的字段)。

 NamedParameterDatbase.query("SELECT * FROM message", new BeanPropertyRowMapper(Message.class));

我想知道 - 是否有一种方便的方法来构建单个查询和/或创建行映射器,以便在消息中填充内部“用户”POJO 的属性。

也就是说,一些语法魔术,其中查询中的每个结果行:

 SELECT * FROM message, user WHERE user_id = message_id

生成包含相关用户的消息列表


用例:

最终,这些类作为序列化对象从 Spring Controller 传回,这些类是嵌套的,因此生成的 JSON / XML 具有合适的结构。

目前,通过执行两个查询并在循环中手动设置每条消息的用户属性来解决这种情况。可用,但我认为应该有一种更优雅的方式。


更新:使用的解决方案 -

感谢@Will Keeling 使用自定义行映射器获得答案的灵感——我的解决方案添加了 bean 属性映射,以便自动执行字段分配。

需要注意的是构建查询,以便为相关表名添加前缀(但是没有标准约定来执行此操作,因此查询是通过编程方式构建的):

 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

自定义行映射器然后创建几个 bean 映射并根据列的前缀设置它们的属性:(使用元数据获取列名)。

 public Object mapRow(ResultSet rs, int i) throws SQLException {

    HashMap<String, BeanMap> beans_by_name = new HashMap();

    beans_by_name.put("message", BeanMap.create(new Message()));
    beans_by_name.put("user", BeanMap.create(new User()));

    ResultSetMetaData resultSetMetaData = rs.getMetaData();

    for (int colnum = 1; colnum <= resultSetMetaData.getColumnCount(); colnum++) {

        String table = resultSetMetaData.getColumnName(colnum).split("\\.")[0];
        String field = resultSetMetaData.getColumnName(colnum).split("\\.")[1];

        BeanMap beanMap = beans_by_name.get(table);

        if (rs.getObject(colnum) != null) {
            beanMap.put(field, rs.getObject(colnum));
        }
    }

    Message m = (Task)beans_by_name.get("message").getBean();
    m.setUser((User)beans_by_name.get("user").getBean());

    return m;
}

同样,这对于两类连接来说似乎有点矫枉过正,但 IRL 用例涉及具有数十个字段的多个表。

原文由 nclord 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 580
2 个回答

Spring 在 BeanMapper 接口中引入了一个新的 AutoGrowNestedPaths 属性。

只要 SQL 查询使用 .分隔符(和以前一样),那么行映射器将自动定位内部对象。

有了这个,我创建了一个新的通用行映射器,如下所示:

询问:

 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


行映射器:

 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;
  }
}

原文由 nclord 发布,翻译遵循 CC BY-SA 3.0 许可协议

也许你可以传入一个自定义 RowMapper 可以将聚合连接查询的每一行(消息和用户之间)映射到 Message 和嵌套 User 是这样的:

 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;
    }
});

原文由 Will Keeling 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题