【问题标题】:Spring: Generic RowMapper for dynamic queriesSpring:用于动态查询的通用 RowMapper
【发布时间】:2023-03-16 07:24:01
【问题描述】:

我正在使用 SpringBatch 从 Oracle 读取数据并写入 ElasticSearch。

我的代码适用于静态查询。 示例:select emp_id, emp_name from employee_table 我有一个 RowMapper 类,它将 resultSet 中的值映射到 Employee POJO。

我的要求是

查询将由用户输入。所以查询可能如下

  1. select emp_id, emp_name from employee_table
  2. select cust_id, cust_name, cust_age from customer_table
  3. select door_no, street_name, loc_name, city from address_table
  4. Similar queries

我的问题是

  1. 有没有办法根据用户给出的查询动态创建POJO?
  2. 如果查询像我的情况一样不断变化,RowMapper 概念会起作用吗?
  3. 是否有类似通用行映射器的东西?

示例代码将不胜感激。

【问题讨论】:

  • 您可以在这里使用 ORM,将查询作为本机查询运行以获取 JPA 实体

标签: java spring spring-batch


【解决方案1】:

如果您有需要映射到的对象...

考虑使用RowMapper 的自定义实现为您的 SQL 设置别名以匹配您的对象字段名称,该实现实际上扩展了BeanWrapperFieldSetMapper

如果你的 POJO 看起来像这样:

public class Employee {

    private String employeeId;
    private String employeeName;

    ...
    // getters and setters

}

那么你的 SQL 可以如下所示:

SELECT emp_id employeeId, emp_name employeeName from employee_table

那么你包裹的RowMapper 看起来像这样:

import org.springframework.jdbc.core.RowMapper
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper

public class BeanWrapperRowMapper<T> extends BeanWrapperFieldSetMapper<T> implements RowMapper<T> {

    @Override
    public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
        final FieldSet fs = getFieldSet(rs);
        try {
            return super.mapFieldSet(fs);
        } catch (final BindException e) {
            throw new IllegalArgumentException("Could not bind bean to FieldSet", e);
        }
    }

    private FieldSet getFieldSet(final ResultSet rs) throws SQLException {
        final ResultSetMetaData metaData = rs.getMetaData();
        final int columnCount = metaData.getColumnCount();

        final List<String> tokens = new ArrayList<>();
        final List<String> names = new ArrayList<>();

        for (int i = 1; i <= columnCount; i++) {
            tokens.add(rs.getString(i));
            names.add(metaData.getColumnName(i));
        }

        return new DefaultFieldSet(tokens.toArray(new String[0]), names.toArray(new String[0]));    
    }

}

或者...

如果您没有要映射到的 POJO,请使用开箱即用的 ColumnMapRowMapper 获取列名的映射 (Map&lt;String,Object&gt;)(我们称它们为 COL_A、COL_B、COL_C)到价值观。然后,如果您的作者类似于JdbcBatchItemWriter,您可以将命名参数设置为:

INSERT TO ${schema}.TARGET_TABLE (COL_1, COL_2, COL_3) values (:COL_A, :COL_B, :COL_C)

然后您的 ItemSqlParameterSourceProvider 实现可能如下所示:

public class MapItemSqlParameterSourceProvider implements
        ItemSqlParameterSourceProvider<Map<String, Object>> {

    public SqlParameterSource createSqlParameterSource(Map<String, Object> item) {
        return new MapSqlParameterSource(item);
    }

}

【讨论】:

  • 这非常有用。谢谢。 :)
  • @Dean Clark 真的很喜欢这个实现,并感谢您提供它。我正在尝试查找 BeanWrapperFieldSetMapper 的导入。你能提供吗?提前谢谢你!
  • @Nelda.techspiress - 添加在上面!
【解决方案2】:

回答您的问题:

  1. 有没有一种方法可以根据用户的查询动态创建 POJO - 即使有,我也不确定它会有多大帮助。对于您的用例,我建议只使用Map
  2. 如果查询不断变化,RowMapper 概念是否有效 - 如果使用 Map,您可以使用列名作为键,使用列值作为值。您应该能够创建一个可以执行此操作的 RowMapper 实现。
  3. 是否有类似通用 RowMapper 的东西 - 有,但它是为 POJO 设计的,因此您需要为此创建自己的。

【讨论】:

  • 嗨,迈克尔。谢谢回答。你能详细说明第二点吗?我不太明白使用地图如何解决我的用例。一些代码 sn-ps 会有很大帮助。感谢您的宝贵时间。
  • 我相信他指的是ColumnMapRowMapper。在我上面的答案中有更多信息,但列名(或别名)将是键,值将是返回映射中的值。
【解决方案3】:

我通过使用 Spring 的 ColumnMapRowMapper 找到了解决问题的方法。请从 xml 配置文件中找到一个 sn-p。我没有生成任何 POJO 类。我使用 Map 进行管理,并将其插入到 ES 中。地图的键名应与索引中的字段名匹配。

<step id="slave" xmlns="http://www.springframework.org/schema/batch">
    <tasklet>
        <chunk reader="pagingItemReader" writer="elasticSearcItemWriter"
            processor="itemProcessor" commit-interval="10" />
    </tasklet>
</step>



<bean id="pagingItemReader"
        class="org.springframework.batch.item.database.JdbcPagingItemReader"
        scope="step">
        <property name="dataSource" ref="dataSource" />
        <property name="queryProvider">
            <bean
                class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
                <property name="dataSource" ref="dataSource" />
                <property name="selectClause" value="*******" />
                <property name="fromClause" value="*******" />

                <property name="whereClause" value="*******" />
                <property name="sortKey" value="*******" />
            </bean>

        </property>
        <!-- Inject via the ExecutionContext in rangePartitioner -->
        <property name="parameterValues">
            <map>
                <entry key="fromId" value="#{stepExecutionContext[fromId]}" />
                <entry key="toId" value="#{stepExecutionContext[toId]}" />
            </map>
        </property>
        <property name="pageSize" value="10" />
        <property name="rowMapper">
            <bean class="org.springframework.jdbc.core.ColumnMapRowMapper" />
        </property>
    </bean>

在我的 elasticSearcItemWriter 类中......

public class ElasticSearchItemWriter<T> extends AbstractItemStreamItemWriter<T>
        implements ResourceAwareItemWriterItemStream<T>, InitializingBean {
....
....
....
@Override
    public void write(List<? extends T> items) throws Exception {


        client = jestClient.getJestClient();
        if (items.size() > 0) {
            for (Object item : items) {
                @SuppressWarnings("unchecked")
                Map<String, Object> map = (Map<String, Object>) item;
                // Asynch index
                Index index = new Index.Builder(map).index(Start.prop.getProperty(Constants.ES_INDEX_NAME))
                    .type(Start.prop.getProperty(Constants.ES_INDEX_TYPE)).build();


                client.executeAsync(index, new JestResultHandler<JestResult>() {
                    public void failed(Exception ex) {
                    }

                    public void completed(JestResult result) {
                    }
                });
            }
        }
    }
.....
....
}

【讨论】:

    【解决方案4】:

    你可以像下面这样简单地做到这一点,

    SettingsDto settings = SettingsDao.getById(1, new BeanPropertyRowMapper<>(SettingsDto.class));
    

    一般来说,您可以传递您的 DTO 类,但请注意您必须使用与 SQL 列相同的名称,或者必须根据 DTO 在 SQL 查询中使用 ALIAS。

    @Data
    public class SettingsDto {
    
        private int id;
    
        private int retryCount;
    
        private int batchSize;
    
        private int retryPeriod;
    
        private int statusInitialDelay;
    
    }
    

    我的dao方法如下

    SettingsDto getById(int id, final RowMapper<OMoneySettingsDto> mapper);
    

    它的实现如下,

     @Override
        public SettingsDto getById(final int id, final RowMapper<OMoneySettingsDto> mapper) {
            return new JdbcTemplate(YourDataSource).queryForObject(QUERY_SETTINGS_BY_ID,new Object[]{id}, mapper);
    }
    

    SQL 在这里,如下你必须在 DTO 中使用相同的名称

    private static final String OMONEY_SETTINGS_BY_ID = "SELECT AS id,retry_count AS retryCount FROM setttings WHERE id = ?";
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-06
      • 2012-07-12
      • 2012-01-12
      • 1970-01-01
      • 1970-01-01
      • 2017-01-03
      • 1970-01-01
      相关资源
      最近更新 更多