Home > Software engineering >  Spring transaction and try catch blocks
Spring transaction and try catch blocks

Time:08-12

I am pretty new to Kotlin and Spring and had a very basic doubt. Consider a function like this:

@transaction
fun accept() {
    try {
        write to table A
        throw Exception()
    } catch(){
        write to table B
    }
}

Question: I understand that Table B write will be successful, but will the table A write succeed? and why? Any pointers to

CodePudding user response:

Spring has a bit of a bizarre take on what exceptions 'mean'. It acts as follows. A transaction method doesn't commit anything until it exits. i.e. this:

@transaction
fun accept() {
  write to table A
  while (true) {} // loop forever
}

trivially never actually commits anything, that's probably obvious. Otherwise, the method exits, and it can exit in one of three ways:

  • The method 'returns'. As in, no exception is thrown out of the method - exceptions might be thrown, but they are caught. Then upon exiting the method, the transaction is committed. Note that this describes your snippet - which does not throw an exception out of the method. Hence, the write to table A and B will both happen and be visible from outside parties!
  • The method finishes by throwing an UNCHECKED exception. Spring's opinion is that this is 'unexpected', and indicates the result of executing the method is failure - the entire transaction is aborted. the write to table A will not be visible to other transactions, ever, nor would the write to table B (let's imagine that after the catch block, you have throw new RuntimeException(), which is unchecked).
  • The method ends by throwing a CHECKED exception out. Spring's opinion is that this must mean that it is 'intentional', and indicates that the code succeeded; it simply chose to "return" an exception instead of finishing up normally. The transaction is committed - the write to table A will be visible to others. The write to table B will also be visible to others (this is assuming that you add throw new IOException() to the end of your accept method, and of course that your method is allowed to do that. Which in java means it has to be declared as void accept() throws IOException {}, of course.

The notion of checked and unchecked does not exist in kotlin - in kotlin, all exceptions are 'unchecked', effectively. However, spring doesn't go: "OOoooohhhh, kotlin user, I'll just treat everything as if it was unchecked, i.e. any exceptions thrown result in a aborted transaction". Nope, so, you must learn what checked exceptions are even if you write only kotlin.

Unchecked exceptions are all throwables that have in their type hierarchy either java.lang.Error (such as java.lang.InternalError, which extends j.l.Error), or java.lang.RuntimeException, such as NullPointerException.

All other throwables are 'checked'. Such as IOException, which extends Exception. which extends Throwable. Exception itself is checked.

NB: You can add interceptors and the like to change this behaviour; "checked exception means we need to commit, checked means we need to abort" is merely the default behaviour.

CodePudding user response:

If "write to table A" is successful then yes it will persist.

Why? Because transaction demarcation is done by interceptors that act upon exceptions and in your code no interceptor would see that exception as they could only be executed around those "write to table X" calls (if those are done via interceptable code) or accept().

Let me try to add some more details:

In general, you can conceptually think of the interceptor looking like this (they look different but the example is used to illustrate the point - I'll also use Java code because I'm not that familiar with Kotlin):

void runInTransaction(Runnable interceptedCode) {
  Transaction tx = startTransaction();

  try {
    interceptedCode.run();
    tx.commit();
  } catch( Exception e) { //kept simple for illustration purposes, in reality this is way more complex
    tx.rollback();
  }
}

Then you'd call it like this:

runInTransaction(() -> accept());

As you can see, since the exception is only thrown and caught in accept() - and thus won't leave the method - the interceptor won't even see it and thus will commit the transaction.

  • Related