Menggunakan Spring Batch JdbcCursorItemReader dengan NamedParameters

JdbcCursorItemReader Batch Musim Semi dapat menerima 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>

Ini berfungsi dengan baik jika sql menggunakan ? sebagai placeholder, seperti pada contoh di atas. Namun, sql kami yang sudah ada sebelumnya menggunakan parameter bernama, mis. PILIH * DARI tes WHERE col1 = :param .

Apakah ada cara agar JdbcCursorItemReader berfungsi dengan NamedPreparedStatementSetter daripada PreparedStatementSetter sederhana?

Terima kasih


person user1052610    schedule 22.04.2014    source sumber
comment
Bisakah Anda memposting kode untuk menunjukkan bagaimana Anda melakukan ini tanpa jobParameters ?   -  person Jeff Cook    schedule 08.07.2018


Jawaban (4)


Anda dapat mencoba dengan jobParameters. Dalam hal ini Anda tidak memerlukan PreparedStatementSetter apa pun.

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

meneruskan nilai saat menjalankan pekerjaan

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

JobExecution execution = jobLauncher.run(job, param);
person Braj    schedule 24.04.2014
comment
Itu tidak berfungsi dalam kasus saya :( stackoverflow.com /pertanyaan/42008631/ - person John Joe; 02.02.2017

Karena kami tidak memiliki solusi resmi dari musim semi, kami dapat memperbaiki masalah ini menggunakan pendekatan sederhana:

  1. Tentukan satu antarmuka untuk menyediakan SqlParameters:
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

public interface SqlParameterSourceProvider { 
    SqlParameterSource getSqlParameterSource();
}
  1. Memperluas JdbcCursorItemReader dan menambahkan fitur bernamaParameter.
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. Membuat kelas konkret yang mengimplementasikan antarmuka SqlParameterSourceProvider dan memiliki status dengan nilai parameter yang diperbarui untuk digunakan dalam kueri Anda.
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. Terakhir, perbarui konfigurasi pegas.
<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
Seperti yang disarankan Michael Minella, saya telah membuat tiket di Spring Batch Jira dan juga membuat cabang kumpulan musim semi di github menambahkan fitur ini. - person Wellington Souza; 05.08.2016
comment
Seperti yang dijelaskan di jira.spring.io/browse/BATCH-2521, ada cara yang lebih sederhana untuk mencapai fitur yang diinginkan tanpa memperkenalkan antarmuka baru (SqlParameterSourceProvider), memperluas JdbcCursorItemReader dan mengimplementasikan SqlParameterSourceProvider khusus. Semoga ini bisa membantu. - person Mahmoud Ben Hassine; 08.11.2018
comment
@MahmoudBenHassine Ini berfungsi ketika kita memiliki param sebagai nilai tunggal. Namun ketika saya memberikan daftar sebagai salah satu nilai, saya melewatkan sesuatu tentang bagaimana saya dapat memanfaatkan solusi yang ditempelkan di tautan Anda di atas. - person Neeraj Singh; 20.02.2019
comment
@NeerajSingh Saya yakin Anda merujuk pada pertanyaan ini: stackoverflow.com/questions/54782690. Seperti yang saya sebutkan dalam jawaban atas pertanyaan itu, solusi yang sama bekerja dengan meratakan parameter atau dengan menggunakan PreparedStatementCreatorFactory seperti yang ditunjukkan di sini: stackoverflow.com/ a/57471284/5019386. Saya menambahkan contoh untuk kedua kasus di repo ini. - person Mahmoud Ben Hassine; 14.02.2020

Saat ini, tidak ada cara untuk melakukan hal ini. JdbcCursorItemReader menggunakan JDBC mentah (PreparedStatement) alih-alih Spring JdbcTemplate (karena tidak ada cara untuk mendapatkan ResultSet yang mendasarinya saat menggunakan JdbcTemplate). Jika Anda ingin menyumbangkan ini sebagai fitur baru, atau memintanya sebagai fitur baru, silakan melakukannya di jira. musim semi.io

person Michael Minella    schedule 22.04.2014
comment
@Michael Minella - Apakah fitur ini telah dikembangkan di Spring Batch versi 4.0.x? Bisakah Anda menunjukkan cuplikan kodenya? - person Jeff Cook; 08.07.2018

solusi asli di https://jira.spring.io/browse/BATCH-2521 , tetapi tidak mendukung klausa id in (:ids).

di sini ada peningkatan.

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

Penggunaan:

@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
Saya ingin tahu bagaimana kita dapat menggunakan ini untuk mengembalikan data sebagai ItemReader - person Vamsi; 09.06.2020