Обработка исключений пакета Spring, отправленная как ResponseEntity

Я новичок в Spring boot, я тренируюсь в небольшом проекте с пакетом Spring , чтобы получить опыт. Вот мой контекст: у меня есть 2 файла csv , один содержит сотрудников , другой содержит всех менеджеров компании. Мне нужно читать файлы, а затем добавлять каждую запись в базу данных . Чтобы сделать это простым, мне просто нужно вызвать конечную точку с моего контроллера, загрузить мой CSV-файл (многочастный файл), после чего задание начнется. Я действительно смог это сделать, моя проблема заключается в следующем.

Мне нужно управлять несколькими типами проверки (я использую проверку jsr 380 для своих объектов, и я также должен проверить бизнес-исключение ). Типом бизнес-исключения может быть следующее правило: Сотрудник находится под контролем менеджера своего отдела ( сотрудник не может контролироваться менеджером, если он не находится в том же отделе, в противном случае должно быть выдано исключение ). Таким образом, для ошибочных записей с некоторым недопустимым или «нелогичным» вводом я должен пропустить их (не сохранять в базе данных), но сохранить их на карте или в списке , которые должны быть отправлены клиенту как сущность ответов .. Следовательно, клиент будет знать, какую строку нужно исправить. Я полагаю, мне нужно взглянуть на ** Слушатели **, но я действительно не могу хранить исключения на карте или в списке, а затем отправлять их как ResponseEntity. Ниже пример того, чего я хочу достичь.

Скриншоты моих CSV-файлов

СотрудникBatchConfig.java

          @Configuration
    @EnableBatchProcessing
    @AllArgsConstructor
    public class EmployeeBatchConfig {

      private JobBuilderFactory jobBuilderFactory;
      private StepBuilderFactory stepBuilderFactory;
      private EmployeeRepository employeeRepository;
      private EmployeeItemWriter employeeItemWriter;


     @Bean
     @StepScope
     public FlatFileItemReader<EmployeeDto> itemReader(@Value("# 
     {jobParameters[fullPathFileName]}") final String pathFile) {
         FlatFileItemReader<EmployeeDto> flatFileItemReader = new 
         FlatFileItemReader<>();
         flatFileItemReader.setResource(new FileSystemResource(new 
         File(pathFile)));
         flatFileItemReader.setName("CSV-Reader");
         flatFileItemReader.setLinesToSkip(1);
         flatFileItemReader.setLineMapper(lineMapper());
         return flatFileItemReader;
     }

     private LineMapper<EtudiantDto> lineMapper() {
        DefaultLineMapper<EtudiantDto> lineMapper = new DefaultLineMapper<> 
        ();
        DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
        lineTokenizer.setDelimiter(",");
        lineTokenizer.setStrict(false);
        lineTokenizer.setNames("Username", "lastName", "firstName", 
        "departement", "supervisor");
        BeanWrapperFieldSetMapper<EmployeeDto> fieldSetMapper = new 
        BeanWrapperFieldSetMapper<>();
        fieldSetMapper.setTargetType(EmployeeDto.class);
        lineMapper.setLineTokenizer(lineTokenizer);
        lineMapper.setFieldSetMapper(fieldSetMapper);

      return lineMapper;
    }

    @Bean
    public EmployeeProcessor processor() {
      return new EmployeeProcessor(); /*Create a bean processor to skip 
        invalid rows*/
    }

    @Bean
    public RepositoryItemWriter<Employee> writer() {
      RepositoryItemWriter<Employee> writer = new RepositoryItemWriter<>();
      writer.setRepository(employeeRepository);
      writer.setMethodName("save");
      return writer;
    }


    @Bean
    public Step step1(FlatFileItemReader<EmployeeDto> itemReader) {
        return stepBuilderFactory.get("slaveStep").<EmployeeDto, 
        Employee>chunk(5)
                .reader(itemReader)
                .processor(processor())
                .writer(employeeItemWriter)
                .faultTolerant()
                .listener(skipListener())
                .skip(SkipException.class)
                .skipLimit(10)
                .skipPolicy(skipPolicy())
                .build();
     }


     @Bean
     @Qualifier("executeJobEmployee")
     public Job runJob(FlatFileItemReader<Employee> itemReader) {
        return jobBuilderFactory
                .get("importEmployee")
                .flow(step1(itemReader))
                .end()
                .build();
     }


    @Bean
    public SkipPolicy skipPolicy(){
        return new ExceptionSkipPolicy();
    }

    @Bean
    public SkipListener<EmployeeDto, Employee> skipListener(){
        return new StepSkipListener();
    }

    /*@Bean
    public ExecutionContext executionContext(){
       return new ExecutionContext();
    }*/
 }

СотрудникПроцессор.java

      public class EmployeeProcessor implements ItemProcessor<EmployeeDto, 
Employee>{

   @Autowired
   private SupervisorService managerService;

   @Override
   public Employee process(@Valid EmployeeDto item) throws Exception, 
   SkipException {          
      ManagerDto manager = 
      SupervisorService.findSupervisorById(item.getSupervisor());    
      //retrieve the manager of the employee    and compare departement
      if(!(manager.getDepartement().equals(item.getDepartement()))) {
        throw new SkipException("Manager Invalid", item);
        //return null;
      }
      return ObjectMapperUtils.map(item, Employee.class);
   }
}

MySkipPolicy.java

      public class MySkipPolicy implements SkipPolicy {

  @Override
  public boolean shouldSkip(Throwable throwable, int i) throws 
  SkipLimitExceededException {
    return true;
  }
}

StepSkipListenerPolicy.java

      public class StepSkipListener implements SkipListener<EmployeeDto, 
Number> {

  @Override // item reader
  public void onSkipInRead(Throwable throwable) {
    System.out.println("In OnSkipReader");
  }

  @Override // item writter
  public void onSkipInWrite(Number item, Throwable throwable) { 
    System.out.println("Nooooooooo ");
  }

  //@SneakyThrows
  @Override // item processor
  public void onSkipInProcess(@Valid EmployeeDto employee, Throwable 
  throwable){
    System.out.println("Process... ");
   /* I guess this is where I should work, but how do I deal with the
   exception occur? How do I know which exception I would get ? */
  }
}

SkipException.java

      public class SkipException extends Exception {

  private Map<String, EmployeeDto> errors = new HashMap<>();

  public SkipException(String errorMessage, EmployeeDto employee) {
    super();
    this.errors.put(errorMessage, employee);
  } 

  public Map<String, EmployeeDto> getErrors() {
    return this.errors;
  }
}

JobController.java

      @RestController
@RequestMapping("/upload")
public class JobController {

  @Autowired    
  private JobLauncher jobLauncher;

  @Autowired
  @Qualifier("executeJobEmployee")
  private Job job;  

  private final String EMPLOYEE_FOLDER = "C:/Users/Project/Employee/";

  @PostMapping("/employee")
  public ResponseEntity<Object> importEmployee(@RequestParam("file") 
   MultipartFile multipartFile) throws JobInterruptedException, 
   SkipException, IllegalStateException, IOException, 
   FlatFileParseException{         
        
   try {
      String fileName = multipartFile.getOriginalFilename();
      File fileToImport= new File(EMPLOYEE_FOLDER + fileName);
      multipartFile.transferTo(fileToImport);
    
      JobParameters jobParameters = new JobParametersBuilder()
        .addString("fullPathFileName", EMPLOYEE_FOLDER + fileName)
        .addLong("startAt", System.currentTimeMillis())
        .toJobParameters();

      JobExecution jobExecution = this.jobLauncher.run(job, 
       jobParameters);

      ExecutionContext executionContext = 
       jobExecution.getExecutionContext();

      System.out.println("My Skiped items : " + 
     executionContext.toString());

  } catch (ConstraintViolationException | FlatFileParseException | 
           JobRestartException | JobInstanceAlreadyCompleteException | 
           JobParametersInvalidException | 
           JobExecutionAlreadyRunningException e) {
           e.printStackTrace();
    return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
  }    
    return new ResponseEntity<>("Employee inserted succesfully", 
    HttpStatus.OK);   
  }
}

1 ответ

Это требование заставляет вашу реализацию ждать завершения задания, прежде чем возвращать веб-ответ, что не является типичным способом запуска пакетных заданий из веб-запросов. Как правило, поскольку пакетные задания могут выполняться в течение нескольких минут/часов, они запускаются в фоновом режиме, а идентификатор задания возвращается клиенту для последующей проверки состояния.

В весенней партии— это точка расширения, которая позволяет вам добавлять пользовательский код, когда при чтении, обработке или записи элемента возникает исключение с возможностью пропуска. Я бы добавил бизнес-проверку в обработчик элементов и выдал бы исключение с пропущенным элементом и причиной этого пропуска (оба инкапсулированы в класс исключения, который должен быть объявлен как пропускаемый).

Пропущенные элементы обычно сохраняются где-то для последующего анализа (например, таблица, файл или контекст выполнения задания). В вашем случае вам нужно отправить их обратно в веб-ответ, чтобы вы могли прочитать их из магазина по вашему выбору, прежде чем возвращать их, прикрепленные к веб-ответу. В псевдокоде вашего контроллера это должно выглядеть примерно так:

      - run the job and wait for its termination (the skip listener would write skipped items in the storage of your choice)
- get skipped items from storage
- return web response

Например, если вы решите хранить пропущенные элементы в контексте выполнения задания, вы можете сделать что-то подобное в своем контроллере:

      JobExecution jobExecution = jobLauncher.run(job, jobParameters);
ExecutionContext executionContext = jobExecution.getExecutionContext();
// get skipped items from the execution context
// return the web response
Другие вопросы по тегам