Использование Spring Batch JdbcCursorItemReader с NamedParameters

Spring Batch JdbcCursorItemReader может принимать preparedStatementSetter:

<bean id="reader" class="org.springframework.batch.item.database.JdbcCursorItemReader">
   <property name="dataSource" ref="..." />
   <property name="sql" value="SELECT * FROM test WHERE col1 = ?">
   <property name="rowMapper" ref="..." />
   <property name="preparedStatementSetter" ref="..." />
</bean>

Это хорошо работает, если sql использует ? в качестве заполнителей, как в приведенном выше примере. Однако наш ранее существовавший sql использует именованные параметры, например. SELECT * FROM test WHERE col1 = :param .

Есть ли способ заставить JdbcCursorItemReader работать с NamedPreparedStatementSetter, а не с простым PreparedStatementSetter?

Спасибо


person user1052610    schedule 22.04.2014    source источник
comment
Не могли бы вы опубликовать код, чтобы показать, как вы это сделали без jobParameters?   -  person Jeff Cook    schedule 08.07.2018


Ответы (4)


Вы можете попробовать с jobParameters. В этом случае вам не нужны никакие PreparedStatementSetter.

<bean id="reader" class="org.springframework.batch.item.database.JdbcCursorItemReader">
   <property name="dataSource" ref="..." />
   <property name="sql" value="SELECT * FROM test WHERE col1 = #{jobParameters['col1']">
   <property name="rowMapper" ref="..." />
   <property name="preparedStatementSetter" ref="..." />
</bean>

передать значение при запуске задания

JobParameters param = new JobParametersBuilder().addString("col1", "value1").toJobParameters();

JobExecution execution = jobLauncher.run(job, param);
person Braj    schedule 24.04.2014
comment
В моем случае это не работает :( stackoverflow.com /вопросы/42008631/ - person John Joe; 02.02.2017

Поскольку у нас нет официального решения от spring, мы можем решить эту проблему, используя простой подход:

  1. Определите один интерфейс для предоставления SqlParameters:
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

public interface SqlParameterSourceProvider { 
    SqlParameterSource getSqlParameterSource();
}
  1. Расширение JdbcCursorItemReader и добавление функций namedParameter.
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.jdbc.core.StatementCreatorUtils;
import org.springframework.jdbc.core.namedparam.*;
import org.springframework.util.Assert;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;


public class NamedParameterJdbcCursorItemReader<T> extends JdbcCursorItemReader<T> {

    private SqlParameterSourceProvider parameterSourceProvider;
    private String paramedSql;

    public NamedParameterJdbcCursorItemReader(SqlParameterSourceProvider parameterSourceProvider) {
        this.parameterSourceProvider = parameterSourceProvider;
    }

    @Override
    public void setSql(String sql) {
        Assert.notNull(parameterSourceProvider, "You have to set parameterSourceProvider before the SQL statement");
        Assert.notNull(sql, "sql must not be null");
        paramedSql = sql;
        super.setSql(NamedParameterUtils.substituteNamedParameters(sql, parameterSourceProvider.getSqlParameterSource()));
    }

    @Override
    protected void applyStatementSettings(PreparedStatement stmt) throws SQLException {
        final ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(paramedSql);

        final List<?> parameters = Arrays.asList(NamedParameterUtils.buildValueArray(parsedSql, parameterSourceProvider.getSqlParameterSource(), null));
        for (int i = 0; i < parameters.size(); i++) {
            StatementCreatorUtils.setParameterValue(stmt, i + 1, SqlTypeValue.TYPE_UNKNOWN, parameters.get(i));
        }
    }
}
  1. Создание конкретного класса, реализующего интерфейс SqlParameterSourceProvider и имеющего состояние с обновленным значением параметров, которые будут использоваться в вашем запросе.
public class MyCustomSqlParameterSourceProvider implements SqlParameterSourceProvider {

    private Map<String, Object> params;

    public void updateParams(Map<String, Object> params) {
        this.params = params;
    }

    @Override
    public SqlParameterSource getSqlParameterSource() {
        final MapSqlParameterSource paramSource = new MapSqlParameterSource();
        paramSource.addValues(params);
        return paramSource;
    }
}
  1. Наконец, обновите конфигурацию spring.
<bean id="reader" class="org.wisecoding.stackoverflow.NamedParameterJdbcCursorItemReader">
    <constructor-arg ref="sqlParameterSourceProvider"/>        
    <property name="dataSource" ref="..." />
    <property name="sql" value=SELECT * FROM test WHERE col1 = :param" />
    <property name="rowMapper" ref="..." />
    <property name="preparedStatementSetter" ref="..." />
</bean>

<bean id="sqlParameterSourceProvider" class="org.wisecoding.stackoverflow.MyCustomSqlParameterSourceProvider">
</bean>
person Wellington Souza    schedule 28.07.2016
comment
Как предложил Майкл Минелла, я создал тикет в Spring Batch Jira, а также создал < href="https://github.com/wbsouza/spring-batch" rel="nofollow noreferrer">ветка весеннего пакета на github, добавляющая эту функцию. - person Wellington Souza; 05.08.2016
comment
Как поясняется в jira.spring.io/browse/BATCH-2521, существует более простой способ достижения желаемой функции без введения нового интерфейса (SqlParameterSourceProvider), расширения JdbcCursorItemReader и реализации пользовательского SqlParameterSourceProvider. Надеюсь, поможет. - person Mahmoud Ben Hassine; 08.11.2018
comment
@MahmoudBenHassine Это работает, когда у нас есть параметр как одно значение. Но когда я передаю список в качестве одного из значений, я что-то упускаю из того, как я могу использовать решение, вставленное в вашу ссылку выше. - person Neeraj Singh; 20.02.2019
comment
@NeerajSingh Я полагаю, вы имеете в виду этот вопрос: stackoverflow.com/questions/54782690. Как я уже упоминал в ответе на этот вопрос, одно и то же решение работает путем выравнивания параметров или использования PreparedStatementCreatorFactory, как показано здесь: stackoverflow.com/ а/57471284/5019386. Я добавил примеры для обоих случаев в этот репозиторий. - person Mahmoud Ben Hassine; 14.02.2020

В настоящее время нет способа сделать это. JdbcCursorItemReader использует необработанный JDBC (PreparedStatement) вместо Spring JdbcTemplate под капотом (поскольку нет способа получить базовый ResultSet при использовании JdbcTemplate). Если вы хотите внести свой вклад в качестве новой функции или запросить ее в качестве новой функции, не стесняйтесь делать это на странице jira. весна.ио

person Michael Minella    schedule 22.04.2014
comment
@Michael Minella - эта функция была разработана в Spring Batch версии 4.0.x? Не могли бы вы показать фрагмент кода? - person Jeff Cook; 08.07.2018

исходное решение в https://jira.spring.io/browse/BATCH-2521 , но не поддерживает предложение id in (:ids).

вот это усиление.

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterUtils;

import java.util.Map;

@Slf4j
public class NamedParameterJdbcCursorItemReader<T> extends JdbcCursorItemReader<T> {

    protected void setNamedParametersSql(String sql, Map<String, Object> parameters) {
        val parsedSql = NamedParameterUtils.parseSqlStatement(sql);
        val paramSource = new MapSqlParameterSource(parameters);

        val sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
        val declaredParams = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
        val params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, null);
        val pscf = new PreparedStatementCreatorFactory(sql, declaredParams);
        val pss = pscf.newPreparedStatementSetter(params);

        log.info("sql: {}", sqlToUse);
        log.info("parameters: {}", parameters);

        setSql(sqlToUse);
        setPreparedStatementSetter(pss);
    }
}

Использование:

@Slf4j
public class UserItemJdbcReader extends NamedParameterJdbcCursorItemReader<UserEntity> {

    @PostConstruct
    public void init() {
        val sql = "SELECT * FROM users WHERE id IN (:ids)";

        val parameters = new HashMap<String, Object>(4);
        parameters.put("ids", Arrays.asList(1,2,3));

        setDataSource(dataSource);
        setRowMapper(new UserRowMapper());
        setNamedParametersSql(sql, parameters);
    }
}
person Sub    schedule 13.08.2019
comment
Я хотел бы знать, как мы можем использовать это, чтобы вернуть данные как ItemReader - person Vamsi; 09.06.2020