Fixes #34 by adding save point tests

And updating the Transactions documentation to show save point examples.
This commit is contained in:
Sean Corfield 2019-07-02 18:36:00 -07:00
parent 1cec0a2643
commit 1fe7e92df2
3 changed files with 50 additions and 0 deletions

View file

@ -9,6 +9,7 @@ The following changes have been committed to the **master** branch since the 1.0
* Fix #37 by adjusting the spec for `with-transaction` to "require less" of the `:binding` vector. * Fix #37 by adjusting the spec for `with-transaction` to "require less" of the `:binding` vector.
* Fix #36 by adding type hint in `with-transaction` macro. * Fix #36 by adding type hint in `with-transaction` macro.
* Fix #35 by explaining the database-specific options needed to ensure `insert-multi!` performs a single, batched operation. * Fix #35 by explaining the database-specific options needed to ensure `insert-multi!` performs a single, batched operation.
* Fix #34 by explaining save points (in the Transactions documentation).
## Stable Builds ## Stable Builds

View file

@ -47,4 +47,31 @@ Instead of throwing an exception (which will propagate through `with-transaction
(jdbc/execute! tx ...)))) (jdbc/execute! tx ...))))
``` ```
## Save Points Inside a Transaction
In general, transactions are per-connection and do not nest in JDBC. If you nest calls to `with-transaction` using a `DataSource` argument (or a db-spec) then you will get separate connections inside each invocation and the transactions will be independent, as permitted by the isolation level.
If you nest such calls passing a `Connection` instead, the inner call will commit (or rollback) all operations on that connection up to that point -- including any performed in the outer call, prior to entering the inner call. The outer call will then commit (or rollback) any additional operations within its scope. This will be confusing at best and most likely buggy behavior!
If you want the ability to selectively roll back certain groups of operations inside a transaction, you can use named or unnamed save points:
```clojure
(jdbc/with-transaction [tx my-datasource]
(let [result (jdbc/execute! tx ...) ; op A
sp1 (.setSavepoint)] ; unnamed save point
(jdbc/execute! tx ...) ; op B
(when ... (.rollback tx sp1)) ; just rolls back op B
(let [sp2 (.setSavepoint "two")] ; named save point
(jdbc/execute! tx ...) ; op C
(when ... (.rollback tx sp2))) ; just rolls back op C
result)) ; returns this and will commit op A
;; (and ops B & C if they weren't rolled back above)
```
[<: Prepared Statements](/doc/prepared-statements.md) | [All The Options :>](/doc/all-the-options.md) [<: Prepared Statements](/doc/prepared-statements.md) | [All The Options :>](/doc/all-the-options.md)

View file

@ -136,6 +136,28 @@ VALUES ('Pear', 'green', 49, 47)
"])] "])]
(.rollback t) (.rollback t)
result)))) result))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "with-transaction with unnamed save point"
(is (= [{:next.jdbc/update-count 1}]
(jdbc/with-transaction [t (ds)]
(let [save-point (.setSavepoint t)
result (jdbc/execute! t ["
INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47)
"])]
(.rollback t save-point)
result))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))
(testing "with-transaction with named save point"
(is (= [{:next.jdbc/update-count 1}]
(jdbc/with-transaction [t (ds)]
(let [save-point (.setSavepoint t (name (gensym)))
result (jdbc/execute! t ["
INSERT INTO fruit (name, appearance, cost, grade)
VALUES ('Pear', 'green', 49, 47)
"])]
(.rollback t save-point)
result))))
(is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"])))))) (is (= 4 (count (jdbc/execute! (ds) ["select * from fruit"]))))))
(deftest plan-misuse (deftest plan-misuse