How to use Spring transactions with files

It’s not uncommon to write data to the database and the filesystem in the same transaction. The challenge is to remove files from the filesystem when the transaction fails. It is possible to combine database Spring transactions with files on the filesystem .
The following example has a Spring-managed transactional method which will first write some data to the database. After doing so it will create a file on the filesystem.

1
2
3
4
5
@Transactional
public void methodInTransaction() {
Data data = writeDataToDataBase();
File outputFile= createFileBasedOnData(data);
}

If an exception is thrown during the commit (let’s say a constraint violation) then there will still be an outputFile present on the filesystem. In some cases this is not desirable. Now let’s see how we can extend Spring transactions with files support. First we need to create a TransactionSynchronization listener.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CleanupTransactionListener extends TransactionSynchronizationAdapter {
private File outputFile;
public CleanupTransactionListener(File outputFile) {
this.outputFile = outputFile;
}
@Override
public void afterCompletion(int status) {
if (STATUS_COMMITTED != status) {
if (outputFile.exists()) {
if (!outputFile.delete()) {
System.out.println("Could not delete File"
+ outputFile.getPath() + " after failed transaction");
}
}
}
}
}

This listener will try to remove the given file after a failed transaction. We still need to register the listener to the transaction. The TransactionSynchronizationManager from Spring may be used to register a transaction listener to the current thread.

1
2
3
4
5
6
7
8
@Transactional
public void methodInTransaction() {
CleanupTransactionListener transactionListener = new CleanupTransactionListener(outputFile);
TransactionSynchronizationManager.registerSynchronization(transactionListener);
Data data = writeDataToDataBase();
File outputFile= createFileBasedOnData(data);
}

This will cause the TransactionSynchronizationManager to call our listener after completing the transaction. By checking the status passed to the listener we can decide which actions (if any) to take.