I’m using Postgres and have a transactional method defined as follows:
@Entity
public class SomeEntity {
// …
}
@Transactional(isolation = READ_COMMITED)
public void persistUniqueAndSendEmail() {
SomeEntity e = // …
// Persis the entity to Postgres with a unique constraint that may fail
// Once the entity is persisted send the notification email
}
With READ_COMMITED isolation level is it possible that the unique constraint violation is thrown when the transaction is actually committed, not the actual sql statement is executed?
If so that would mean that the email can be sent, but the relevant changes are not persisted.
2
Answers
The transaction doesnt finish until the end of the method. The save doesnt flush until the transaction finishing prompts it to. So yes, you could get a constraint violation that rolls back the save after the email is sent.
You could try calling saveAndFlush instead of save (see Difference between save and saveAndFlush in Spring data jpa). The flush forces the database call to occur, which will trigger any constraint violation.
But you dont want the transaction to fail for some other reason and have the email sent erroneously. The safe thing to do is make sure the transaction completes successfully, then send the email either in another service calling the transactional service, or in an AOP interceptor.
You have a few problems here.
Yes, it can. Specifically, constraint checks can be done on the spot or when you commit and it depends on the DDL, not the setup of the connection. In PSQL, such constraint checks are triggers and you can configure them: Don’t run (bad idea, but can be useful when you’re bulk importing), Run on the spot, and defer until commit. We have no idea how you configured your DB (what SQL you wrote to set up these constraints). By default constraints are checked as the statement is executed.
Use SERIALIZABLE instead. Your database’s integrity will thank you.
Regardless of the situation of where constraints are checked, a commit is never guaranteed. What if you send out the email and then someone trips over the power cable? If you reverse the situation and commit, then send the email, what happens if someone trips over a power cable after the commit but before the email is sent?
You’re stuck here in a 2-system commit cascade. You have 2 things that both aren’t atomic and you want either both to occur (an email is sent and the db row is updated), or neither to occur. But that is not possible – 2 disparate systems can’t do that.
You have many ways out of the dilemma and they’re all bad. Some general aspects:
If the last entry is ‘about to send an email’ then one of two things has happened:
You can’t solve the dilemma without a separate API or other system that lets you check what happened. For example, if you’re sending via gmail y ou could use its API to check the sent box. Worst comes to worst, on boot you just hard crash and demand that a human operator update the entry to explain precisely what the fate is of that email. Or, pick some default (when in doubt, send twice – for example. i.e. assume the email did not go out, so to restore state, send it again).
This is quite complicated but the only way to atomicize 2 disparate systems.