Spring RowMapper 接口究竟是如何工作的?

Posted

技术标签:

【中文标题】Spring RowMapper 接口究竟是如何工作的?【英文标题】:How exactly work the Spring RowMapper interface? 【发布时间】:2015-02-19 21:53:48 【问题描述】:

我正在学习 Spring Core 认证,我对 Spring 如何处理 JDBC 查询有一些疑问:

所以我知道我可以根据我期望获得的数据类型以各种方式从我的数据库表中获取数据:

1) 查询简单类型(作为int、long或String):我使用jdbcTemplatequeryForObject()方法strong> 类,类似的东西:

String sql = "SELECT count(*) FROM T_REWARD";
int rowsNumber = jdbcTemplate.queryForObject(sql, Integer.class);

因此,为了获得一个简单的对象作为 int 值,我使用 queryForObject() 方法将 sql stattment 和我希望接收的对象类型传递给它方法的输出。

好的,这很简单,我认为没问题。

2) 查询放入 Map 对象的整个表行:所以如果我不需要单个值(可以放入特定的单个列中)表的行或类似前面的示例)我可以通过这些方式使用 queryForMap(..)queryForList() 方法:

2.1) queryForMap():如果我希望将 单行 放入 单 Map 对象,其中每个列的值是映射到我的地图中,类似于:

String sql = "select * from T_REWARD where CONFIRMATION_NUMBER = ?";
Map<String, Object> values = jdbcTemplate.queryForMap(sql,confirmation.getConfirmationNumber());

2.2) queryForList():如果我期望更多行作为查询的输出,我会使用它。因此,我将获得一个Map 对象列表,其中每个 Map 对象代表查询输出的特定行。类似的东西:

String sql = “select * from PERSON”;
return jdbcTemplate.queryForList(sql);

我认为这也很清楚。

然后我可以使用 JdbcTemplate 将 ResultSet 映射到 域对象,这对我来说不是很清楚。

阅读文档被告知 JdbcTemplate 使用回调方法支持这一点。这种回调方法究竟是什么意思?

我知道 Spring 提供了一个 RowMapper 接口,用于将 ResultSet 的单行映射到对象

public interface RowMapper<T> 
    T mapRow(ResultSet rs, int rowNum)
    throws SQLException;

我通过这种方法编写了以下示例,它使用新的 RestaurandRowMapper 对象作为 queryForObject() 方法的返回对象:

public Restaurant findByMerchantNumber(String merchantNumber) 
    String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE, BENEFIT_AVAILABILITY_POLICY from T_RESTAURANT where MERCHANT_NUMBER = ?";

    return jdbcTemplate.queryForObject(sql, new RestaurantRowMapper(), merchantNumber);

还有这个内部类

class RestaurantRowMapper implements RowMapper<Restaurant> 
    public Restaurant mapRow(ResultSet rs, int i) throws SQLException 
        return mapRestaurant(rs);
    

使用此私有方法创建映射:

private Restaurant mapRestaurant(ResultSet rs) throws SQLException 
    // get the row column data
    String name = rs.getString("NAME");
    String number = rs.getString("MERCHANT_NUMBER");
    Percentage benefitPercentage = Percentage.valueOf(rs.getString("BENEFIT_PERCENTAGE"));
    // map to the object
    Restaurant restaurant = new Restaurant(number, name);
    restaurant.setBenefitPercentage(benefitPercentage);
    restaurant.setBenefitAvailabilityPolicy(mapBenefitAvailabilityPolicy(rs));
    return restaurant;

所以我很难理解所有这些东西是如何工作的。

我的主要疑问是:我知道使用 queryForObject() 方法作为输入参数传递给它,我期望作为输出的对象的类型(例如整数或长整数)。

如果我希望获得一个代表整行表的域对象(例如映射到 Restaurand 对象的 Restaurant 表 的一行),我想我应该使用这个对象(作为 Restaurant 对象),但在前面的示例中,我使用 **row mapper 对象 而不是 domain 对象

return jdbcTemplate.queryForObject(sql, new RestaurantRowMapper(), merchantNumber);

这个内部类只包含返回预期的域对象

ma​​pRow()方法
class RestaurantRowMapper implements RowMapper<Restaurant> 
    public Restaurant mapRow(ResultSet rs, int i) throws SQLException 
        return mapRestaurant(rs);
    

所以我认为 Spring 会自动调用 ma​​pRow() 方法,该方法返回 Restaurand 域对象,该方法会自动替换为 queryForObject() 方法或类似的东西.但我不太确定它是否真的有效。

我错过了什么?你能解释一下后台到底发生了什么吗?

Tnx

【问题讨论】:

【参考方案1】:

queryForObject 方法如下所示:

public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException 
    List<T> results = query(sql, args, new RowMapperResultSetExtractor<T>(rowMapper, 1));
    return DataAccessUtils.requiredSingleResult(results);

queryForObject-方法在内部调用 JdbcTemplate 对象上的方法 queryquery-方法定义为:

public <T> T query(
        PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
        throws DataAccessException;

如您所见,ResultSetExtractor&lt;T&gt; 被传递给query 方法,Spring 方便地将您的RowMapper&lt;T&gt; 转换为new RowMapperResultSetExtractor&lt;T&gt;(rowMapper, 1) 类型的对象。 RowMapperResultSetExtractor 是持有魔法钥匙的对象。当对象被调用时,它会按照这个 sn-p 迭代所有行:

public List<T> extractData(ResultSet rs) throws SQLException 
    List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());
    int rowNum = 0;
    while (rs.next()) 
        results.add(this.rowMapper.mapRow(rs, rowNum++));
    
    return results;

因此,您原来的RowMapper 是为每一行调用的回调。此外,正如您在此处看到的,您的 RowMapper 会针对所有匹配结果调用,并且您创建的 Restaurant-object 将添加到结果列表中。但是,由于您只查询 一个 对象,因此以下语句最终用于返回您的单个 Restaurant 对象。

        return DataAccessUtils.requiredSingleResult(results);

所以,总结一下:JdbcTempalte 实现了Strategy Pattern(类似于Template method pattern)。通过提供策略接口RowMapper),您可以让JdbcTemplate 为您完成繁重的工作(处理异常、连接等)。 RowMapper 将每个命中映射为 POJO(Restaurant),所有命中都收集到 List。方法queryForObject 然后从List 中取出第一行并将其返回给调用者。返回值基于RowMapper 的泛型类型,在您的情况下为Restaurant

【讨论】:

【参考方案2】:

使用 jdbcTemplate.queryForObject 它会像这样解决你的问题

public YourPojo getDatabaseDetails(int masterId) 

    sql = "Select * FROM <table_name> where ID=?";
    YourPojo pojo = (YourPojo) jdbcTemplate.queryForObject(sql,
            new Object[]  masterId , new RowMapper<YourPojo>() 
                @Override
                public <YourPojo> mapRow(ResultSet rs, int rowNum)
                        throws SQLException 
                    YourPojo pojo2 = new YourPojo();
                    pojo2.setAccountId(rs.getString("AccountId"));
                    pojo2.setAccountName(rs.getString("AccountName"));
                    pojo2.setAccountCRN(rs.getString("AccountCRN"));
                    pojo2.setAccountStatus(rs.getString("AccountStatus"));
                    return pojo2;
                
            );
    return pojo;

【讨论】:

以上是关于Spring RowMapper 接口究竟是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

Spring BeanPostProcessor 究竟是如何工作的?

Spring DI applicationContext.xml xsi:schemaLocation 究竟是如何使用的?

RESTful 应用程序的 @ResponseStatus Spring 注释究竟是如何工作的?

Spring Security 拦截 URL 究竟是如何工作的?

Android究竟是如何判断是不是在线的?

这个在 Spring 中处理 POST 请求的 REST 方法究竟是如何工作的?