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.
|
||||
|
||||
* 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
|
||||
* 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.
|
||||
|
|
|
|||
|
|
@ -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)`).
|
||||
|
||||
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
|
||||
|
||||
`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.
|
||||
|
||||
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
|
||||
|
||||
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.result-set :as rs]
|
||||
[next.jdbc.sql-logging :as logger]
|
||||
[next.jdbc.transaction])
|
||||
[next.jdbc.transaction :as tx])
|
||||
(:import (java.sql PreparedStatement)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -399,6 +399,17 @@
|
|||
(let [con (vary-meta sym assoc :tag 'java.sql.Connection)]
|
||||
`(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
|
||||
"Given a connectable/transactable object and a set of (default) options
|
||||
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})))
|
||||
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
|
||||
(testing "with-transaction rollback-only"
|
||||
(is (not (jdbc/active-tx?)) "should not be in a transaction")
|
||||
(is (= [{:next.jdbc/update-count 1}]
|
||||
(jdbc/with-transaction [t (ds) {:rollback-only true}]
|
||||
(is (jdbc/active-tx?) "should be in a transaction")
|
||||
(jdbc/execute! t ["
|
||||
INSERT INTO fruit (name, appearance, cost, grade)
|
||||
VALUES ('Pear', 'green', 49, 47)
|
||||
"]))))
|
||||
(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))]
|
||||
(let [ac (.getAutoCommit con)]
|
||||
(is (= [{:next.jdbc/update-count 1}]
|
||||
(jdbc/with-transaction [t con {:rollback-only true}]
|
||||
(is (jdbc/active-tx?) "should be in a transaction")
|
||||
(jdbc/execute! t ["
|
||||
INSERT INTO fruit (name, appearance, cost, grade)
|
||||
VALUES ('Pear', 'green', 49, 47)
|
||||
|
|
@ -241,8 +245,10 @@ VALUES ('Pear', 'green', 49, 47)
|
|||
INSERT INTO fruit (name, appearance, cost, grade)
|
||||
VALUES ('Pear', 'green', 49, 47)
|
||||
"])
|
||||
(is (jdbc/active-tx?) "should be in a transaction")
|
||||
(throw (ex-info "abort" {})))))
|
||||
(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))]
|
||||
(let [ac (.getAutoCommit con)]
|
||||
(is (thrown? Throwable
|
||||
|
|
@ -251,6 +257,7 @@ VALUES ('Pear', 'green', 49, 47)
|
|||
INSERT INTO fruit (name, appearance, cost, grade)
|
||||
VALUES ('Pear', 'green', 49, 47)
|
||||
"])
|
||||
(is (jdbc/active-tx?) "should be in a transaction")
|
||||
(throw (ex-info "abort" {})))))
|
||||
(is (= 4 (count (jdbc/execute! con ["select * from fruit"]))))
|
||||
(is (= ac (.getAutoCommit con))))))
|
||||
|
|
@ -262,8 +269,11 @@ INSERT INTO fruit (name, appearance, cost, grade)
|
|||
VALUES ('Pear', 'green', 49, 47)
|
||||
"])]
|
||||
(.rollback t)
|
||||
;; still in a next.jdbc TX even tho' we rolled back!
|
||||
(is (jdbc/active-tx?) "should be in a transaction")
|
||||
result))))
|
||||
(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))]
|
||||
(let [ac (.getAutoCommit con)]
|
||||
(is (= [{:next.jdbc/update-count 1}]
|
||||
|
|
@ -285,8 +295,11 @@ INSERT INTO fruit (name, appearance, cost, grade)
|
|||
VALUES ('Pear', 'green', 49, 47)
|
||||
"])]
|
||||
(.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))))
|
||||
(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))]
|
||||
(let [ac (.getAutoCommit con)]
|
||||
(is (= [{:next.jdbc/update-count 1}]
|
||||
|
|
|
|||
Loading…
Reference in a new issue