Revert Database changes on Jakarta EE Batch failure

119 Views Asked by At

I am developing a Jakarta EE Batch using EclipseLink and Wildfly. I am stuck at the ItemWriter level. The Writer basically creates a file and should write some records into it. Each file is associated with a Delivery which is my entity. So one file per delivery. The delivery numbers should increase ascendingly.

Here is the relevant part of the entity that im using:

@Entity
@Table(name = "DELIVERY")
public class Delivery {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lieferung_id_generator")
    @SequenceGenerator(name = "lieferung_id_generator", sequenceName = "lieferung_id_seq", allocationSize = 1)
    @Column(name = "DELIVERY_NO", nullable = false)
    private Long deliveryNo;

    @Column(name = "DELIVERY_DATE", nullable = false)
    private LocalDate deliveryDate;

    @Column(name = "FILENAME", nullable = false)
    private String deliveryFile;

Here is the writer:

@Named
@Dependent
public class MyItemWriter extends TypedItemWriter<List<String>, Serializable> {

    @PersistenceContext(unitName = MyPersistenceUnit.NAME)
    private EntityManager entityManager;

    private FileWriter fileWriter;
    private Delivery newDelivery;
    private Integer writtenRecords;

    @Override
    protected void doOpen(Serializable checkpoint) {
        if (checkpoint == null) {
            File deliveryFile = new File(erzeugeDateiName());
            newDelivery = persistNewLieferung(dateiZurLieferung);
            writtenRecords = 0;

            try {
                boolean isFileCreated = deliveryFile.createNewFile();
                if (!isFileCreated) {
                    throw new BatchRuntimeException("File already exists.");
                }
                fileWriter = new FileWriter(deliveryFile);
            } catch (IOException e) {
                throw new BatchRuntimeException("Error upon file creation: {}", e);
            }

            // Initial Filewriting without Records from `ItemProcessor`
            // [...]
        } else {
            writtenRecords = (Integer)checkpoint;
        }
    }

    @Override
    protected void doWriteItems(List<List<String>> items) {
        for (List<String> records: items) {
            records.forEach(this::writeToFile);
        }
    }

    @Override
    protected Serializable doCheckpointInfo() {
        return writtenRecords;
    }

    @Override
    public void close() throws IOException {
        Delivery previousDelivery = getPreviousDelivery();
        writeToFile(createRecord(newDelivery.getDeliveryNo().toString(),
                                 writtenRecords,
                                 previousDelivery.getDeliveryDate(),
                                 previousDelivery.getDeliveryNo().toString()));                                              
        fileWriter.close();
    }
  1. In doOpen() I create a new Delivery and persist it. The delivery number deliveryNo is the primary key and gets assigned by the database when I persist the new delivery.
  2. Afterwards all the records coming from my ItemProcessor get written to the file.
  3. In close() I need to write one more record thats different from the others. In this record I need information about the new and the previous delivery (to be precise, I need their deliveryNo and deliveryDate).

The problem

When an exception occurs during the batch job (e.g. IOException, the new delivery is a failure but still...

  • it gets persisted
  • the sequence that manages the deliveryNos is therefore increased nonetheless.
  • the content of the special record that im writing in close() points to a failed delivery which is a critical error state and must be prevented. Ideally, there should only be successful deliveries in the database.

Now my question is: How can I prevent the failed delivery to be persisted and the deliveryNo counter to be increased when the batch job fails? Is there an easy / non-hacky way to "revert" everything so the database is basically in the state from before the batch job's execution? Is this even possible?

0

There are 0 best solutions below