JDBCTemplate set nested POJO with BeanPropertyRowMapper

2022-09-02 11:27:24

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

class User {
    String user_name;
    String display_name;
}

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

人们可以很容易地查询数据库(在我的例子中是postgres),并使用BeanPropertyRowMapper填充Messeg类列表,其中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用例涉及具有数十个字段的多个表。


答案 1

也许您可以传入一个自定义,该自定义可以将聚合联接查询的每一行(消息和用户之间)映射到 和 嵌套 。像这样:RowMapperMessageUser

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

答案 2

派对有点晚了,但是当我在谷歌上搜索同一个问题时,我发现了这一点,我发现了一个不同的解决方案,将来可能对其他人有利。

遗憾的是,没有一种本机方法可以在不创建客户 RowMapper 的情况下实现嵌套方案。但是,我将分享一种比此处的其他一些解决方案更简单的方法来制作所述自定义RowMapper。

根据您的方案,您可以执行以下操作:

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

要记住的关键是,您必须遵循列的命名和类成员的属性,但以下情况除外(请参阅Spring文档):BeanPropertyRowMapper

  • 列名完全别名
  • 带有下划线的列名将转换为“骆驼”大小写(即。MY_COLUMN_WITH_UNDERSCORES == myColumnWithUnderscores)

推荐