I've recently begun using Spring's Data Source Transaction Manager. I have a problem now. My transaction includes updates to a DB table and a write operation to a file.
It works fine but I have some doubts about file I/O. As you see below, I have configured openFile and closeFile methods of my bean as the init-method and the destroy-method respectively, which in turn provides those methods to be called just like a constuructor and a destructor. If the file is not closed properly, some of the records may not have successfully been written to the output.txt file which means that I have not been able to handle transaction management properly as well.
However, I'd like to rollback those DB updates which have not been appended to the flat file. With my solution, it looks impossible to add the fileClose method to the transaction. Does anyone know how to implement this desired action properly?
Any suggestions would be greatly appreciated
<!--XML CONFIGURATION -->
<bean id="myFileWriter" class="com.job.step.ItemFileWriter" init-method="openFile" destroy-method="closeFile">
<property name="jdbcTemplate" ref="jdbcTemplateProduct"/>
</bean>
public class ItemFileWriter implements ItemWriter<Item> {
private static final Logger log = Logger.getLogger(ItemFileWriter.class);
private BufferedWriter bw = null;
public void openFile() throws IOException {
try {
bw = new BufferedWriter(new FileWriter("C:\\output.txt"));
} catch (IOException e) {
//log.error(e);
throw e;
}
}
public void closeFile() throws IOException {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
log.error(e);
throw e;
}
}
}
@Transactional(rollbackFor = IOException.class)
public void write(List<? extends Item> itemList) throws IOException
{
for (Iterator<? extends Item> iterator = itemList.iterator(); iterator.hasNext();) {
Item item = (Item) iterator.next();
String updateRtlnOutbound = "UPDATE SAMPLESCHEMA.SAMPLETABLE SET STATUS='TRANSFERRED' WHERE ID = ?";
jdbcTemplate.update(updateRtlnOutbound, new Object[]{item.getID()});
String item = String.format("%09d\n", item.customerNumber);
bw.write(item);
}
}
}
Generally speaking, file IO is not transactional (except for some OS-specific features).
So, the best you can do is to move open and close operation to write()
method, in order to execute them inside a transaction and rollback the transaction if closing fails.
Note, however, that you can't rollback the file IO in the case of transaction rollback, so that under some circumstances you can get the correct file with items, whereas in the database these items are not marked as TRANSFERRED
.
To sove this problem you can try to use low-level transaction management support and try to delete the file in the case of rollback, but I think it still can't provide strong guarantees of consistency:
@Transactional(rollbackFor = IOException.class)
public void write(List<? extends Item> itemList) throws IOException
{
openFile();
TransactionSynchronizationManager().registerSynchronization(new TransactionSynchronizationAdapter() {
public void afterCompletion(int status) {
if (status = STATUS_ROLLED_BACK) {
// try to delete the file
}
}
});
try {
...
} finally {
closeFile();
}
}
You are doing operations over two different systems: file-system and database. Typically, XA transactions make it easy for us to combine different transactional systems into a single transaction.
Most databases can be made to participate in XA transactions. For file-system you can use XADisk to enable XA. Once you enable XA on both database (through right configuration of data-source) and file-system (through xadisk), you can be sure that both the file and database operations commit or both of them rollback.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With