Проблема
Все файлы помещены в один и тот же каталог, но имеют разную структуру (= разные имена столбцов).
Моя цель — прочитать эти разнородные файлы и нормализовать их в единый объект базы данных, называемый, скажем, FinalTransaction.
Моя текущая стратегия
Я ищу самый «родной для Spring Batch» способ справиться с этим.
Мой код уже может обрабатывать транзакции N26 (на данный момент класс называется просто Transaction, потому что это именно то, что он поддерживает)
Транзакция.класс:
Код: Выделить всё
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table
public class Transaction {
@Id
@GeneratedValue
private Integer id; // optional, can be generated later
private LocalDate bookingDate;
private LocalDate valueDate;
private String partnerName;
private String partnerIban;
private String type;
private String paymentReference;
private String accountName;
private Double amount;
private Double originalAmount;
private String originalCurrency;
private Double exchangeRate;
private String category; // TODO: Should be enum
}
JobRestController.class:
Код: Выделить всё
@RestController
public class JobRestController {
@Autowired
private JobOperator jobOperator;
@Autowired
private Job transactionCategorizationJob;
@PostMapping("job/run")
ResponseEntity startJob(@RequestParam String schemaType, @RequestParam String fileName) throws JobInstanceAlreadyCompleteException, InvalidJobParametersException, JobExecutionAlreadyRunningException, JobRestartException {
// TODO: Make a cleaner validation
if(!schemaType.equals("n26") && !schemaType.equals("revolut")) {
return ResponseEntity.badRequest().body("Invalid schema type. The value must be either 'n26' or 'revolut'");
}
JobParameters jobParameters = new JobParametersBuilder()
.addString("schemaType", schemaType)
.addString("fileName", fileName)
.toJobParameters();
JobParameters params = new JobParametersBuilder()
.addLong("run.id", System.currentTimeMillis())
.addJobParameters(jobParameters)
.toJobParameters();
jobOperator.start(transactionCategorizationJob, params);
return ResponseEntity.ok("Transaction job started");
}
}
TransactionLineMapper.class:
Код: Выделить всё
@StepScope // Necessary to get the schemaType
@Component
public class TransactionLineMapper extends DefaultLineMapper {
public TransactionLineMapper(
TransactionFieldSetMapper mapper,
CsvSchemas csvSchemas,
@Value("#{jobParameters['schemaType']}") String schemaType) {
super();
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setStrict(false);
setLineTokenizer(tokenizer);
setFieldSetMapper(mapper);
tokenizer.setNames(csvSchemas.getColumnNames(schemaType));
}
}
CsvSchema.class:
Код: Выделить всё
/**
* This class is used to load the CSV column names from the application.properties file.
* TODO: This is a temporary solution because @ConfigurationProperties does not work with immutable Map objects, which is a problem because the CSV column names should not be changed at runtime.
*/
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "app.csv")
public class CsvSchemas {
private Map schema = new HashMap();
public String[] getColumnNames(String schemaName) {
List schema = this.schema.get(schemaName);
if (schema == null) {
throw new IllegalArgumentException("Schema not found: " + schemaName);
}
return schema.toArray(String[]::new);
}
}
TransactionFieldSetMapper.class:
Код: Выделить всё
@Component
public class TransactionFieldSetMapper extends BeanWrapperFieldSetMapper {
public TransactionFieldSetMapper() {
setTargetType(Transaction.class);
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(String.class, LocalDate.class, source -> LocalDate.parse(source, DateTimeFormatter.ISO_LOCAL_DATE));
setConversionService(conversionService);
}
}
Код: Выделить всё
@Component
public class TransactionItemProcessor implements ItemProcessor {
@Override
public @Nullable Transaction process(Transaction transaction) {
// No transformation needed for the moment
return transaction;
}
}
CategorizationPostProcessing.java:
Код: Выделить всё
@Log4j2
@Component
public class CategorizationPostProcessing implements JobExecutionListener {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private TransactionRepository transactionRepository;
@Override
public void beforeJob(JobExecution jobExecution) {
// no-op
}
@Override
public void afterJob(JobExecution jobExecution) {
log.info("Job ended with status: {}", jobExecution.getStatus());
(...)
}
}
Является ли это стандартным способом обработки динамических форматов CSV в Spring Batch 6?
Для меня важно то, что я не хочу менять код Java в случае, если я захочу поддерживать другой банковский формат CSV.
Я старший разработчик Java/Spring, но честно пытаюсь понять, как это реализовать чисто. Я постараюсь легко объяснить, какие есть варианты, насколько я понимаю:
- Сопоставьте имена столбцов с FinalTransaction без каких-либо знаний о SchemaType . Хорошо, но не идеально?
- Получаем тип схемы в качестве входных данных (что я делаю) и выбираем Reader. Плохо, я не хочу писать Java-код каждый раз, когда хочу поддержать новый банк.
- Что еще??
Подробнее здесь: https://stackoverflow.com/questions/798 ... -entity-wi
Мобильная версия