Using Spring Batch JdbcCursorItemReader with NamedParameters

前端 未结 4 702
滥情空心
滥情空心 2021-01-05 13:34

The Spring Batch JdbcCursorItemReader can accept a preparedStatementSetter:



        
相关标签:
4条回答
  • 2021-01-05 13:48

    original solution in https://jira.spring.io/browse/BATCH-2521, but which does not support id in (:ids) clause.

    here is an enhancement.

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

    Usage:

    @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);
        }
    }
    
    0 讨论(0)
  • 2021-01-05 13:52

    Currently, there is not a way to do this. The JdbcCursorItemReader uses raw JDBC (PreparedStatement) instead of the Spring JdbcTemplate under the hood (since there is no way to get the underlying ResultSet when using JdbcTemplate). If you'd like to contribute this as a new feature, or request it as a new feature, feel free to do so at jira.spring.io

    0 讨论(0)
  • 2021-01-05 13:53

    Once we don't have an official solution from spring, we can fix this problem using a simple approach:

    1. Define one interface to provide the SqlParameters:
    import org.springframework.jdbc.core.namedparam.SqlParameterSource;
    
    public interface SqlParameterSourceProvider { 
        SqlParameterSource getSqlParameterSource();
    }
    
    1. Extending the JdbcCursorItemReader and adding the namedParameter features.
    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. Creating the concrete class that implements the interface SqlParameterSourceProvider and has the state with the updated value of the parameters to be used in your query.
    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. Finally, update the spring configuration.
    <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>
    
    0 讨论(0)
  • 2021-01-05 13:58

    You can try with jobParameters. In this case you don't need any 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>
    

    pass the value when running the job

    JobParameters param = new JobParametersBuilder().addString("col1", "value1").toJobParameters();
    
    JobExecution execution = jobLauncher.run(job, param);
    
    0 讨论(0)
提交回复
热议问题