Repository: jdbi/jdbi
Issue: TransactionIsolationLevel.UNKNOWN cause unexpected failure

Hello @jdbi Team!

I was implementing some nifty transactions support which uses `Transaction` annotation of JDBI and got exception I have not expected.

Sample code:

```java
@Rule
public JdbiMySqlRule db = new JdbiMySqlRule();

interface SampleDao {

	@SqlQuery("select count(*) from sample")
	int select();
}

static class Bubu {

	@Transaction
	public void doSomething() {
	}
}

@Test
public void test_tx() throws NoSuchMethodException, SecurityException {

	final Method method = Bubu.class.getDeclaredMethod("doSomething");
	final Transaction tx = method.getAnnotation(Transaction.class);

	db.getHandle().inTransaction(tx.value(), c -> {
		return null; // do nothing
	});
}
```

Stack trace:

```plain
org.jdbi.v3.core.transaction.UnableToManipulateTransactionIsolationLevelException: Unable to set isolation level to -2147483648
	at org.jdbi.v3.core.Handle.setTransactionIsolation(Handle.java:477)
	at org.jdbi.v3.core.Handle.setTransactionIsolation(Handle.java:461)
	at org.jdbi.v3.core.Handle.inTransaction(Handle.java:430)
	at ***
Caused by: org.h2.jdbc.JdbcSQLException: Invalid value "-2147483648" for parameter "level" [90008-197]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
	at org.h2.message.DbException.get(DbException.java:179)
	at org.h2.message.DbException.getInvalidValueException(DbException.java:240)
	at org.h2.jdbc.JdbcConnection.setTransactionIsolation(JdbcConnection.java:744)
	at org.jdbi.v3.core.Handle.setTransactionIsolation(Handle.java:475)
	... 32 more
```

I understand why this is happening, but from API point of view this is rather unexpected since  `TransactionIsolationLevel.UNKNOWN`, which is a default `Transaction` value, is a valid enum value. As API user I would expect not to have any errors regardless of `TransactionIsolationLevel` being used.

There is this method in `Handle` class: https://github.com/jdbi/jdbi/blob/master/core/src/main/java/org/jdbi/v3/core/Handle.java#L472-L482

```java
    /**
     * Set the transaction isolation level on the underlying connection.
     *
     * @param level the isolation level to use
     */
    public void setTransactionIsolation(int level) {
        try {
            if (connection.getTransactionIsolation() == level) {
                // already set, noop
                return;
            }
            connection.setTransactionIsolation(level);
        } catch (SQLException e) {
            throw new UnableToManipulateTransactionIsolationLevelException(level, e);
        }
    }
```

Which I think should be changed to:

```java
    /**
     * Set the transaction isolation level on the underlying connection.
     *
     * @param level the isolation level to use
     */
    public void setTransactionIsolation(int level) {
        try {
            boolean isAlreadySet = level == connection.getTransactionIsolation();
            boolean isUnknown = level == TransactionIsolationLevel.UNKNOWN;
            if (isAlreadySet || isUnknown) {
                // already set or unknown, noop
                return;
            }
            connection.setTransactionIsolation(level);
        } catch (SQLException e) {
            throw new UnableToManipulateTransactionIsolationLevelException(level, e);
        }
    }
```

So that `TransactionIsolationLevel.UNKNOWN` is handled as a special case.

I see this already done in [`TransactionDecorator`](https://github.com/jdbi/jdbi/blob/a831d3314db43859c9894aa987d3ee4827edc459/sqlobject/src/main/java/org/jdbi/v3/sqlobject/transaction/internal/TransactionDecorator.java#L58-L62) and I'm wondering - why this is done in abstraction and not on a lower lever, in a `Handle` class. Is there something I am missing? Something which requires `Handle` to fail when transaction isolation level is unknown?

One of the other options is to have `inTransaction()` method modified to detect this case, but this will rather mask the problem, not fix it. IMHO `TransactionIsolationLevel.UNKNOWN` should be handled at the most bottom layer, before it's propagated to `Connection`, but I may be wrong since I'm not as familiar with JDBI internals as you are, guys.

This concludes my issue.

Changed files:
sqlobject/src/main/java/org/jdbi/v3/sqlobject/transaction/internal/TransactionDecorator.java
core/src/test/java/org/jdbi/v3/core/TestHandle.java
core/src/main/java/org/jdbi/v3/core/Jdbi.java
core/src/main/java/org/jdbi/v3/core/Handle.java

diff --git a/core/src/main/java/org/jdbi/v3/core/Handle.java b/core/src/main/java/org/jdbi/v3/core/Handle.java
index 26701def2..755644870 100644
--- a/core/src/main/java/org/jdbi/v3/core/Handle.java
+++ b/core/src/main/java/org/jdbi/v3/core/Handle.java
@@ -458,10 +458,14 @@ public class Handle implements Closeable, Configurable<Handle> {
     /**
      * Set the transaction isolation level on the underlying connection.
      *
+     * @throws UnableToManipulateTransactionIsolationLevelException if isolation level is not supported by the underlying connection or JDBC driver
+     *
      * @param level the isolation level to use
      */
     public void setTransactionIsolation(TransactionIsolationLevel level) {
-        setTransactionIsolation(level.intValue());
+        if (level != TransactionIsolationLevel.UNKNOWN) {
+            setTransactionIsolation(level.intValue());
+        }
     }
 
     /**
@@ -471,11 +475,9 @@ public class Handle implements Closeable, Configurable<Handle> {
      */
     public void setTransactionIsolation(int level) {
         try {
-            if (connection.getTransactionIsolation() == level) {
-                // already set, noop
-                return;
+            if (connection.getTransactionIsolation() != level) {
+                connection.setTransactionIsolation(level);
             }
-            connection.setTransactionIsolation(level);
         } catch (SQLException e) {
             throw new UnableToManipulateTransactionIsolationLevelException(level, e);
         }
diff --git a/core/src/main/java/org/jdbi/v3/core/Jdbi.java b/core/src/main/java/org/jdbi/v3/core/Jdbi.java
index 7b99632f9..a5bbc6539 100644
--- a/core/src/main/java/org/jdbi/v3/core/Jdbi.java
+++ b/core/src/main/java/org/jdbi/v3/core/Jdbi.java
@@ -399,7 +399,7 @@ public class Jdbi implements Configurable<Jdbi> {
      * @throws X any exception thrown by the callback
      */
     public <R, X extends Exception> R inTransaction(final TransactionIsolationLevel level, final HandleCallback<R, X> callback) throws X {
-        return withHandle(handle -> handle.<R, X>inTransaction(level, callback));
+        return withHandle(handle -> handle.inTransaction(level, callback));
     }
 
     /**
diff --git a/core/src/test/java/org/jdbi/v3/core/TestHandle.java b/core/src/test/java/org/jdbi/v3/core/TestHandle.java
index 80d0317e6..711471c2e 100644
--- a/core/src/test/java/org/jdbi/v3/core/TestHandle.java
+++ b/core/src/test/java/org/jdbi/v3/core/TestHandle.java
@@ -14,10 +14,14 @@
 package org.jdbi.v3.core;
 
 import org.jdbi.v3.core.rule.H2DatabaseRule;
+import org.jdbi.v3.core.transaction.TransactionIsolationLevel;
+import org.jdbi.v3.core.transaction.UnableToManipulateTransactionIsolationLevelException;
 import org.junit.Rule;
 import org.junit.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 public class TestHandle {
     @Rule
@@ -62,4 +66,15 @@ public class TestHandle {
         final Handle h = dbRule.getSharedHandle();
         h.execute("CREATE TABLE \\"\\u2603\\" (pk int primary key)");
     }
+
+    @Test
+    public void unknownTransactionLevelIsOk() {
+        Handle h = dbRule.openHandle();
+
+        assertThatThrownBy(() -> h.setTransactionIsolation(Integer.MIN_VALUE))
+            .isInstanceOf(UnableToManipulateTransactionIsolationLevelException.class);
+
+        assertThatCode(() -> h.setTransactionIsolation(TransactionIsolationLevel.UNKNOWN))
+            .doesNotThrowAnyException();
+    }
 }
diff --git a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/transaction/internal/TransactionDecorator.java b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/transaction/internal/TransactionDecorator.java
index b7c6ce9d9..ca7c6f663 100644
--- a/sqlobject/src/main/java/org/jdbi/v3/sqlobject/transaction/internal/TransactionDecorator.java
+++ b/sqlobject/src/main/java/org/jdbi/v3/sqlobject/transaction/internal/TransactionDecorator.java
@@ -55,11 +55,7 @@ public class TransactionDecorator implements HandlerDecorator {
             }
 
             try {
-                if (isolation == TransactionIsolationLevel.UNKNOWN) {
-                    return h.inTransaction(callback);
-                } else {
-                    return h.inTransaction(isolation, callback);
-                }
+                return h.inTransaction(isolation, callback);
             } finally {
                 if (flipReadOnly) {
                     h.setReadOnly(!readOnly);