Spring batch 在SpringBatch中,如何在ItemReader之后处理逻辑上相关的行? 脚本

Spring batch 在SpringBatch中,如何在ItemReader之后处理逻辑上相关的行? 脚本,spring-batch,Spring Batch,为了简单起见,假设我有一个ItemReader,它返回我25行 前10行属于学生A 接下来的5个属于学生B 剩下的10个属于学生C 我想将它们逻辑地聚合在一起,比如说通过studentId,然后将它们展平,最终每个学生一行。 问题 如果我理解正确,将提交间隔设置为5将执行以下操作: 将5行发送给处理器(处理器将聚合它们或执行我告诉它的任何业务逻辑) 处理后将写入5行 然后,它将在接下来的5行中再次执行此操作,以此类推 如果这是真的,那么在接下来的五年中,我将不得不检查已经编写的代码,将它们汇总到

为了简单起见,假设我有一个ItemReader,它返回我25行

  • 前10行属于学生A

  • 接下来的5个属于学生B

  • 剩下的10个属于学生C

  • 我想将它们逻辑地聚合在一起,比如说通过studentId,然后将它们展平,最终每个学生一行。

    问题 如果我理解正确,将提交间隔设置为5将执行以下操作:

  • 将5行发送给处理器(处理器将聚合它们或执行我告诉它的任何业务逻辑)
  • 处理后将写入5行
  • 然后,它将在接下来的5行中再次执行此操作,以此类推
  • 如果这是真的,那么在接下来的五年中,我将不得不检查已经编写的代码,将它们汇总到当前正在处理的代码中,然后再次编写

    我个人不喜欢这样

  • 在春季批次中处理这种情况的最佳实践是什么
  • 可供替代的 有时我觉得编写一个常规的SpringJDBC主程序要容易得多,这样我就可以完全控制我想做的事情。然而,我想利用作业存储库对作业的状态监控,能够重新启动、跳过、作业和步骤侦听器

    我的Spring批处理代码 My模块context.xml

       <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:batch="http://www.springframework.org/schema/batch"
        xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
        <description>Example job to get you started. It provides a skeleton for a typical batch application.</description>
    
        <batch:job id="job1">
            <batch:step id="step1"  >           
                <batch:tasklet transaction-manager="transactionManager" start-limit="100" >             
                     <batch:chunk reader="attendanceItemReader"
                                  processor="attendanceProcessor" 
                                  writer="attendanceItemWriter" 
                                  commit-interval="10" 
                     />
    
                </batch:tasklet>
            </batch:step>
        </batch:job> 
    
        <bean id="attendanceItemReader" class="org.springframework.batch.item.database.JdbcCursorItemReader"> 
            <property name="dataSource">
                <ref bean="sourceDataSource"/>
            </property> 
            <property name="sql"                                                    
                      value="select s.student_name ,s.student_id ,fas.attendance_days ,fas.attendance_value from K12INTEL_DW.ftbl_attendance_stumonabssum fas inner join k12intel_dw.dtbl_students s on fas.student_key = s.student_key inner join K12INTEL_DW.dtbl_schools ds on fas.school_key = ds.school_key inner join k12intel_dw.dtbl_school_dates dsd on fas.school_dates_key = dsd.school_dates_key where dsd.rolling_local_school_yr_number = 0 and ds.school_code = ? and s.student_activity_indicator = 'Active' and fas.LOCAL_GRADING_PERIOD = 'G1' and s.student_current_grade_level = 'Gr 9' order by s.student_id"/>
            <property name="preparedStatementSetter" ref="attendanceStatementSetter"/>           
            <property name="rowMapper" ref="attendanceRowMapper"/> 
        </bean> 
    
        <bean id="attendanceStatementSetter" class="edu.kdc.visioncards.preparedstatements.AttendanceStatementSetter"/>
    
        <bean id="attendanceRowMapper" class="edu.kdc.visioncards.rowmapper.AttendanceRowMapper"/>
    
        <bean id="attendanceProcessor" class="edu.kdc.visioncards.AttendanceProcessor" />  
    
        <bean id="attendanceItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter"> 
            <property name="resource" value="file:target/outputs/passthrough.txt"/> 
            <property name="lineAggregator"> 
                <bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator" /> 
            </property> 
        </bean> 
    
    </beans>
    
    还有一个划船制图器

    package edu.kdc.visioncards.rowmapper;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.core.RowMapper;
    
    import edu.kdc.visioncards.dto.AttendanceDTO;
    
    public class AttendanceRowMapper<T> implements RowMapper<AttendanceDTO> {
    
        public static final String STUDENT_NAME = "STUDENT_NAME";
        public static final String STUDENT_ID = "STUDENT_ID";
        public static final String ATTENDANCE_DAYS = "ATTENDANCE_DAYS";
        public static final String ATTENDANCE_VALUE = "ATTENDANCE_VALUE";
    
        public AttendanceDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
    
            AttendanceDTO dto = new AttendanceDTO();
            dto.setStudentId(rs.getString(STUDENT_ID));
            dto.setStudentName(rs.getString(STUDENT_NAME));
            dto.setAttDays(rs.getInt(ATTENDANCE_DAYS));
            dto.setAttValue(rs.getInt(ATTENDANCE_VALUE));
    
            return dto;
        }
    }
    
    包edu.kdc.visioncards.rowmapper;
    导入java.sql.ResultSet;
    导入java.sql.SQLException;
    导入org.springframework.jdbc.core.RowMapper;
    导入edu.kdc.visioncards.dto.AttendanceDTO;
    公共类owmapper实现了RowMapper{
    公共静态最终字符串STUDENT\u NAME=“STUDENT\u NAME”;
    公共静态最终字符串STUDENT\u ID=“STUDENT\u ID”;
    公共静态最终字符串考勤天数=“考勤天数”;
    公共静态最终字符串考勤值=“考勤值”;
    公共AttendanceDTO mapRow(结果集rs,int rowNum)引发SQLException{
    AttendanceDTO dto=新AttendanceDTO();
    dto.setStudentId(rs.getString(STUDENT_ID));
    dto.setStudentName(rs.getString(学生名称));
    dto.setAttDays(rs.getInt(出勤日));
    dto.setAttValue(rs.getInt(考勤值));
    返回dto;
    }
    }
    
    我的处理器

    package edu.kdc.visioncards;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.batch.item.ItemProcessor;
    
    import edu.kdc.visioncards.dto.AttendanceDTO;
    
    public class AttendanceProcessor implements ItemProcessor<AttendanceDTO, Map<Integer, AttendanceDTO>> {
    
        private Map<Integer, AttendanceDTO> map = new HashMap<Integer, AttendanceDTO>();
    
        public Map<Integer, AttendanceDTO> process(AttendanceDTO dto) throws Exception {
    
            if(map.containsKey(new Integer(dto.getStudentId()))){
    
                AttendanceDTO attDto = (AttendanceDTO)map.get(new Integer(dto.getStudentId()));
                attDto.setAttDays(attDto.getAttDays() + dto.getAttDays());
                attDto.setAttValue(attDto.getAttValue() + dto.getAttValue());
    
            }else{
                map.put(new Integer(dto.getStudentId()), dto);
            }
            return map;
        }
    
    }
    
    包edu.kdc.visioncards;
    导入java.util.HashMap;
    导入java.util.Map;
    导入org.springframework.batch.item.ItemProcessor;
    导入edu.kdc.visioncards.dto.AttendanceDTO;
    公共类AttendanceProcessor实现ItemProcessor{
    私有映射映射=新的HashMap();
    公共映射进程(AttendanceDTO dto)引发异常{
    if(map.containsKey(新整数(dto.getStudentId())){
    AttendanceDTO attDto=(AttendanceDTO)map.get(新整数(dto.getStudentId());
    attDto.setAttDays(attDto.getAttDays()+dto.getAttDays());
    attDto.setAttValue(attDto.getAttValue()+dto.getAttValue());
    }否则{
    put(新整数(dto.getStudentId()),dto);
    }
    返回图;
    }
    }
    
    我对上面代码的关注 在处理器中,我创建了一个HashMap,在处理行时,我检查映射中是否已经有该学生,如果没有,我将其添加。如果它已经在那里,我抓取它,得到我感兴趣的值,并将它们添加到我当前正在处理的行中

    之后,Spring批处理框架根据我的配置写入一个文件

    我的问题如下:

  • 我不想把它交给作者。我想处理所有剩余的行。如何将我在内存中创建的映射保存到下一组需要通过同一处理器的行中?每次通过AttendanceProcessor处理一行时,地图都会初始化。我应该将映射初始化放在静态块中吗

  • 基本上,您讨论的是具有更改ID(1)的批处理,其中批处理必须跟踪更改

    对于spring/spring批次,我们讨论:

    • ItemWriter,用于检查项目列表中的id更改
    • 更改前,项目存储在临时数据存储(2)(列表、地图等)中,不会被写出
    • 当id更改时,聚合/扁平化业务代码在数据存储中的项目上运行,并且应该写入一个项目,现在数据存储可以用于具有下一个id的下一个项目
    • 这个概念需要一个阅读器,它告诉步骤“我筋疲力尽”,以便正确地刷新项目末尾(文件/数据库)上的临时数据存储
    下面是一个粗略而简单的代码示例

    @覆盖
    
    因为你改变了你的问题,我添加了一个新的答案

    如果学生已排序,则不需要列表/映射,您可以在处理器上仅使用一个studentObject来保持“当前”并在其上聚合,直到有一个新的(读取:id更改)

    如果学生没有被排序,你将永远不知道某个特定的学生何时“完成”,你将不得不把所有的学生放在一张地图上,直到完整的阅读顺序结束后才能写

    注意:

    • 处理器需要知道读卡器何时耗尽
    • 任何提交率和“id”概念都很难让它正常工作如果你聚合的项目在某种程度上是相同的,那么处理器就无法知道当前处理的项目是否是最后一个
    • 基本上,用例要么在读者级别完全解决,要么在作者级别解决(参见其他答案)
    private SimpleItem currentItem;
    私人分步执行分步执行;
    @凌驾
    公共SimpleItem进程(SimpleItem newItem)引发异常{
    SimpleItem returnItem=null;
    如果(currentItem==null){
    currentItem=newSimpleItem(newItem.getId(),newItem.getValue());
    }else if(currentItem.getId()==new
    
    package edu.kdc.visioncards;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.batch.item.ItemProcessor;
    
    import edu.kdc.visioncards.dto.AttendanceDTO;
    
    public class AttendanceProcessor implements ItemProcessor<AttendanceDTO, Map<Integer, AttendanceDTO>> {
    
        private Map<Integer, AttendanceDTO> map = new HashMap<Integer, AttendanceDTO>();
    
        public Map<Integer, AttendanceDTO> process(AttendanceDTO dto) throws Exception {
    
            if(map.containsKey(new Integer(dto.getStudentId()))){
    
                AttendanceDTO attDto = (AttendanceDTO)map.get(new Integer(dto.getStudentId()));
                attDto.setAttDays(attDto.getAttDays() + dto.getAttDays());
                attDto.setAttValue(attDto.getAttValue() + dto.getAttValue());
    
            }else{
                map.put(new Integer(dto.getStudentId()), dto);
            }
            return map;
        }
    
    }
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.batch.item.ReaderNotOpenException;
    import org.springframework.batch.item.database.JdbcCursorItemReader;
    import org.springframework.jdbc.core.RowMapper;
    
    /**
     * A JdbcCursorItemReader that uses a {@link CollectingRowMapper}.
     * Like the superclass this reader is not thread-safe.
     * 
     * @author Pino Navato
     **/
    public class CollectingJdbcCursorItemReader<T> extends JdbcCursorItemReader<T> {
    
        private CollectingRowMapper<T> rowMapper;
        private boolean firstRead = true;
    
    
        /**
         * Accepts a {@link CollectingRowMapper} only.
         **/
        @Override
        public void setRowMapper(RowMapper<T> rowMapper) {
            this.rowMapper = (CollectingRowMapper<T>)rowMapper;
            super.setRowMapper(rowMapper);
         }
    
    
        /**
         * Read next row and map it to item.
         **/
        @Override
        protected T doRead() throws Exception {
            if (rs == null) {
                throw new ReaderNotOpenException("Reader must be open before it can be read.");
            }
    
            try {
                if (firstRead) {
                    if (!rs.next()) {  //Subsequent calls to next() will be executed by rowMapper
                        return null;
                    }
                    firstRead = false;
                } else if (!rowMapper.hasNext()) {
                    return null;
                }
                T item = readCursor(rs, getCurrentItemCount());
                return item;
            }
            catch (SQLException se) {
                throw getExceptionTranslator().translate("Attempt to process next row failed", getSql(), se);
            }
        }
    
        @Override
        protected T readCursor(ResultSet rs, int currentRow) throws SQLException {
            T result = super.readCursor(rs, currentRow);
            setCurrentItemCount(rs.getRow());
            return result;
        }
    
    }