fix #254 by adding active-tx?
This commit is contained in:
parent
24d55f3165
commit
06f9baea01
4 changed files with 40 additions and 1 deletions
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
Only accretive/fixative changes will be made from now on.
|
Only accretive/fixative changes will be made from now on.
|
||||||
|
|
||||||
|
* 1.3.next in progress
|
||||||
|
* Address [#254](https://github.com/seancorfield/next-jdbc/issues/254) by adding `next.jdbc/active-tx?` and adding more explanation to [**Transactions**](https://cljdoc.org/d/com.github.seancorfield/next.jdbc/CURRENT/doc/getting-started/transactions) about the conventions behind transactions and the limitations of thread-local tracking of active transactions in `next.jdbc`.
|
||||||
|
|
||||||
* 1.3.874 -- 2023-04-15
|
* 1.3.874 -- 2023-04-15
|
||||||
* Fix [#248](https://github.com/seancorfield/next-jdbc/issues/248) by allowing `:port` to be `:none`.
|
* Fix [#248](https://github.com/seancorfield/next-jdbc/issues/248) by allowing `:port` to be `:none`.
|
||||||
* Address [#247](https://github.com/seancorfield/next-jdbc/issues/247) by adding examples of using `next.jdbc.connection/jdbc-url` to build a connection string with additional parameters when creating connection pools.
|
* Address [#247](https://github.com/seancorfield/next-jdbc/issues/247) by adding examples of using `next.jdbc.connection/jdbc-url` to build a connection string with additional parameters when creating connection pools.
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ By default, all connections that `next.jdbc` creates are automatically committab
|
||||||
|
|
||||||
It is possible to tell `next.jdbc` to create connections that do not automatically commit operations: pass `{:auto-commit false}` as part of the options map to anything that creates a connection (including `get-connection` itself). You can then decide when to commit or rollback by calling `.commit` or `.rollback` on the connection object itself. You can also create save points (`(.setSavepoint con)`, `(.setSavepoint con name)`) and rollback to them (`(.rollback con save-point)`). You can also change the auto-commit state of an open connection at any time (`(.setAutoCommit con on-off)`).
|
It is possible to tell `next.jdbc` to create connections that do not automatically commit operations: pass `{:auto-commit false}` as part of the options map to anything that creates a connection (including `get-connection` itself). You can then decide when to commit or rollback by calling `.commit` or `.rollback` on the connection object itself. You can also create save points (`(.setSavepoint con)`, `(.setSavepoint con name)`) and rollback to them (`(.rollback con save-point)`). You can also change the auto-commit state of an open connection at any time (`(.setAutoCommit con on-off)`).
|
||||||
|
|
||||||
|
This is the machinery behind "transactions": one or more operations on a
|
||||||
|
`Connection` that are not automatically committed, and which can be rolled back
|
||||||
|
or committed explicitly at any point.
|
||||||
|
|
||||||
## Automatic Commit & Rollback
|
## Automatic Commit & Rollback
|
||||||
|
|
||||||
`next.jdbc`'s transaction handling provides a convenient baseline for either committing a group of operations if they all succeed or rolling them all back if any of them fails, by throwing an exception. You can either do this on an existing connection -- and `next.jdbc` will try to restore the state of the connection after the transaction completes -- or by providing a datasource and letting `with-transaction` create and manage its own connection:
|
`next.jdbc`'s transaction handling provides a convenient baseline for either committing a group of operations if they all succeed or rolling them all back if any of them fails, by throwing an exception. You can either do this on an existing connection -- and `next.jdbc` will try to restore the state of the connection after the transaction completes -- or by providing a datasource and letting `with-transaction` create and manage its own connection:
|
||||||
|
|
@ -35,6 +39,14 @@ You can also provide an options map as the third element of the binding vector (
|
||||||
|
|
||||||
The latter can be particularly useful in tests, to run a series of SQL operations during a test and then roll them all back at the end.
|
The latter can be particularly useful in tests, to run a series of SQL operations during a test and then roll them all back at the end.
|
||||||
|
|
||||||
|
If you use `next.jdbc/with-transaction` (or `next.jdbc/transact`), then
|
||||||
|
`next.jdbc` keeps track of whether a "transaction" is in progress or not, and
|
||||||
|
you can call `next.jdbc/active-tx?` to determine that, in your own code, in
|
||||||
|
case you want to write code that behaves differently inside or outside a
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
> Note: `active-tx?` only knows about `next.jdbc` transactions -- it cannot track any transactions that you create yourself using the underlying JDBC `Connection`. In addition, this is a **global** state (per thread) and not related to just a single connection, so you can't use this predicate if you are working with multiple databases in the same context.
|
||||||
|
|
||||||
## Manual Rollback Inside a Transaction
|
## Manual Rollback Inside a Transaction
|
||||||
|
|
||||||
Instead of throwing an exception (which will propagate through `with-transaction` and therefore provide no result), you can also explicitly rollback if you want to return a result in that case:
|
Instead of throwing an exception (which will propagate through `with-transaction` and therefore provide no result), you can also explicitly rollback if you want to return a result in that case:
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
[next.jdbc.protocols :as p]
|
[next.jdbc.protocols :as p]
|
||||||
[next.jdbc.result-set :as rs]
|
[next.jdbc.result-set :as rs]
|
||||||
[next.jdbc.sql-logging :as logger]
|
[next.jdbc.sql-logging :as logger]
|
||||||
[next.jdbc.transaction])
|
[next.jdbc.transaction :as tx])
|
||||||
(:import (java.sql PreparedStatement)))
|
(:import (java.sql PreparedStatement)))
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
@ -399,6 +399,17 @@
|
||||||
(let [con (vary-meta sym assoc :tag 'java.sql.Connection)]
|
(let [con (vary-meta sym assoc :tag 'java.sql.Connection)]
|
||||||
`(transact ~transactable (^{:once true} fn* [~con] ~@body) ~(or opts {}))))
|
`(transact ~transactable (^{:once true} fn* [~con] ~@body) ~(or opts {}))))
|
||||||
|
|
||||||
|
(defn active-tx?
|
||||||
|
"Returns true if `next.jdbc` has a currently active transaction in the
|
||||||
|
current thread, else false.
|
||||||
|
|
||||||
|
Note: transactions are a convention of operations on a `Connection` so
|
||||||
|
this predicate only reflects `next.jdbc/transact` and `next.jdbc/with-transaction`
|
||||||
|
operations -- it does not reflect any other operations on a `Connection`,
|
||||||
|
performed via JDBC interop directly."
|
||||||
|
[]
|
||||||
|
@#'tx/*active-tx*)
|
||||||
|
|
||||||
(defn with-options
|
(defn with-options
|
||||||
"Given a connectable/transactable object and a set of (default) options
|
"Given a connectable/transactable object and a set of (default) options
|
||||||
that should be used on all operations on that object, return a new
|
that should be used on all operations on that object, return a new
|
||||||
|
|
|
||||||
|
|
@ -217,17 +217,21 @@ VALUES ('Pear', 'green', 49, 47)
|
||||||
{:rollback-only true})))
|
{:rollback-only true})))
|
||||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
|
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
|
||||||
(testing "with-transaction rollback-only"
|
(testing "with-transaction rollback-only"
|
||||||
|
(is (not (jdbc/active-tx?)) "should not be in a transaction")
|
||||||
(is (= [{:next.jdbc/update-count 1}]
|
(is (= [{:next.jdbc/update-count 1}]
|
||||||
(jdbc/with-transaction [t (ds) {:rollback-only true}]
|
(jdbc/with-transaction [t (ds) {:rollback-only true}]
|
||||||
|
(is (jdbc/active-tx?) "should be in a transaction")
|
||||||
(jdbc/execute! t ["
|
(jdbc/execute! t ["
|
||||||
INSERT INTO fruit (name, appearance, cost, grade)
|
INSERT INTO fruit (name, appearance, cost, grade)
|
||||||
VALUES ('Pear', 'green', 49, 47)
|
VALUES ('Pear', 'green', 49, 47)
|
||||||
"]))))
|
"]))))
|
||||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||||
|
(is (not (jdbc/active-tx?)) "should not be in a transaction")
|
||||||
(with-open [con (jdbc/get-connection (ds))]
|
(with-open [con (jdbc/get-connection (ds))]
|
||||||
(let [ac (.getAutoCommit con)]
|
(let [ac (.getAutoCommit con)]
|
||||||
(is (= [{:next.jdbc/update-count 1}]
|
(is (= [{:next.jdbc/update-count 1}]
|
||||||
(jdbc/with-transaction [t con {:rollback-only true}]
|
(jdbc/with-transaction [t con {:rollback-only true}]
|
||||||
|
(is (jdbc/active-tx?) "should be in a transaction")
|
||||||
(jdbc/execute! t ["
|
(jdbc/execute! t ["
|
||||||
INSERT INTO fruit (name, appearance, cost, grade)
|
INSERT INTO fruit (name, appearance, cost, grade)
|
||||||
VALUES ('Pear', 'green', 49, 47)
|
VALUES ('Pear', 'green', 49, 47)
|
||||||
|
|
@ -241,8 +245,10 @@ VALUES ('Pear', 'green', 49, 47)
|
||||||
INSERT INTO fruit (name, appearance, cost, grade)
|
INSERT INTO fruit (name, appearance, cost, grade)
|
||||||
VALUES ('Pear', 'green', 49, 47)
|
VALUES ('Pear', 'green', 49, 47)
|
||||||
"])
|
"])
|
||||||
|
(is (jdbc/active-tx?) "should be in a transaction")
|
||||||
(throw (ex-info "abort" {})))))
|
(throw (ex-info "abort" {})))))
|
||||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||||
|
(is (not (jdbc/active-tx?)) "should not be in a transaction")
|
||||||
(with-open [con (jdbc/get-connection (ds))]
|
(with-open [con (jdbc/get-connection (ds))]
|
||||||
(let [ac (.getAutoCommit con)]
|
(let [ac (.getAutoCommit con)]
|
||||||
(is (thrown? Throwable
|
(is (thrown? Throwable
|
||||||
|
|
@ -251,6 +257,7 @@ VALUES ('Pear', 'green', 49, 47)
|
||||||
INSERT INTO fruit (name, appearance, cost, grade)
|
INSERT INTO fruit (name, appearance, cost, grade)
|
||||||
VALUES ('Pear', 'green', 49, 47)
|
VALUES ('Pear', 'green', 49, 47)
|
||||||
"])
|
"])
|
||||||
|
(is (jdbc/active-tx?) "should be in a transaction")
|
||||||
(throw (ex-info "abort" {})))))
|
(throw (ex-info "abort" {})))))
|
||||||
(is (= 4 (count (jdbc/execute! con ["select * from fruit"]))))
|
(is (= 4 (count (jdbc/execute! con ["select * from fruit"]))))
|
||||||
(is (= ac (.getAutoCommit con))))))
|
(is (= ac (.getAutoCommit con))))))
|
||||||
|
|
@ -262,8 +269,11 @@ INSERT INTO fruit (name, appearance, cost, grade)
|
||||||
VALUES ('Pear', 'green', 49, 47)
|
VALUES ('Pear', 'green', 49, 47)
|
||||||
"])]
|
"])]
|
||||||
(.rollback t)
|
(.rollback t)
|
||||||
|
;; still in a next.jdbc TX even tho' we rolled back!
|
||||||
|
(is (jdbc/active-tx?) "should be in a transaction")
|
||||||
result))))
|
result))))
|
||||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||||
|
(is (not (jdbc/active-tx?)) "should not be in a transaction")
|
||||||
(with-open [con (jdbc/get-connection (ds))]
|
(with-open [con (jdbc/get-connection (ds))]
|
||||||
(let [ac (.getAutoCommit con)]
|
(let [ac (.getAutoCommit con)]
|
||||||
(is (= [{:next.jdbc/update-count 1}]
|
(is (= [{:next.jdbc/update-count 1}]
|
||||||
|
|
@ -285,8 +295,11 @@ INSERT INTO fruit (name, appearance, cost, grade)
|
||||||
VALUES ('Pear', 'green', 49, 47)
|
VALUES ('Pear', 'green', 49, 47)
|
||||||
"])]
|
"])]
|
||||||
(.rollback t save-point)
|
(.rollback t save-point)
|
||||||
|
;; still in a next.jdbc TX even tho' we rolled back to a save point!
|
||||||
|
(is (jdbc/active-tx?) "should be in a transaction")
|
||||||
result))))
|
result))))
|
||||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))
|
||||||
|
(is (not (jdbc/active-tx?)) "should not be in a transaction")
|
||||||
(with-open [con (jdbc/get-connection (ds))]
|
(with-open [con (jdbc/get-connection (ds))]
|
||||||
(let [ac (.getAutoCommit con)]
|
(let [ac (.getAutoCommit con)]
|
||||||
(is (= [{:next.jdbc/update-count 1}]
|
(is (= [{:next.jdbc/update-count 1}]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue