* Combine adjacent `with-open` calls. * Show how `set-parameters` is used. * Add a section on batched parameters, with their caveats, and how to use them.
This commit is contained in:
parent
67f39638a0
commit
7184ef996c
6 changed files with 77 additions and 45 deletions
|
|
@ -6,7 +6,7 @@ Only accretive/fixative changes will be made from now on.
|
|||
|
||||
The following changes have been committed to the **master** branch since the 1.0.1 release:
|
||||
|
||||
* None.
|
||||
* Improved docstrings and documentation, especially around prepared statement handling.
|
||||
|
||||
## Stable Builds
|
||||
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -46,11 +46,11 @@ In addition, there are API functions to create `PreparedStatement`s (`prepare`)
|
|||
Since `next.jdbc` uses raw Java JDBC types, you can use `with-open` directly to reuse connections and ensure they are cleaned up correctly:
|
||||
|
||||
```clojure
|
||||
(let [my-datasource (get-datasource {:dbtype "..." :dbname "..." ...})]
|
||||
(with-open [connection (get-connection my-datasource)]
|
||||
(execute! connection [...])
|
||||
(reduce my-fn init-value (plan connection [...]))
|
||||
(execute! connection [...])
|
||||
(let [my-datasource (jdbc/get-datasource {:dbtype "..." :dbname "..." ...})]
|
||||
(with-open [connection (jdbc/get-connection my-datasource)]
|
||||
(jdbc/execute! connection [...])
|
||||
(reduce my-fn init-value (jdbc/plan connection [...]))
|
||||
(jdbc/execute! connection [...])))
|
||||
```
|
||||
|
||||
### Usage scenarios
|
||||
|
|
|
|||
|
|
@ -9,7 +9,16 @@ If you need to pass an option map to `plan`, `execute!`, or `execute-one!` when
|
|||
```clojure
|
||||
(with-open [con (jdbc/get-connection ds)]
|
||||
(with-open [ps (jdbc/prepare con ["..." ...])]
|
||||
(execute-one! ps nil {...})))
|
||||
(jdbc/execute-one! ps nil {...})))
|
||||
```
|
||||
|
||||
You can provide the parameters in the `prepare` call or you can provide them via a call to `set-parameters` (discussed in more detail below).
|
||||
|
||||
```clojure
|
||||
;; assuming require next.jdbc.prepare :as p
|
||||
(with-open [con (jdbc/get-connection ds)
|
||||
ps (jdbc/prepare con ["..."])]
|
||||
(jdbc/execute-one! (p/set-parameters ps [...])))
|
||||
```
|
||||
|
||||
## Prepared Statement Parameters
|
||||
|
|
@ -24,10 +33,42 @@ This can be extended to any Clojure data type, to provide a customized way to ad
|
|||
(with-meta obj {'next.jdbc.prepare/set-parameter (fn [v ps i]...)})
|
||||
```
|
||||
|
||||
`next.jdbc.prepare/set-parameters` is available for you to call on any existing `PreparedStatement` to set or update the parameters that will be used when the statement is executed:
|
||||
As noted above, `next.jdbc.prepare/set-parameters` is available for you to call on any existing `PreparedStatement` to set or update the parameters that will be used when the statement is executed:
|
||||
|
||||
* `(set-parameters ps params)` -- loops over a sequence of parameter values and calls `set-parameter` for each one, as above.
|
||||
|
||||
If you need more specialized parameter handling than the protocol can provide, then you can create prepared statements explicitly, instead of letting `next.jdbc` do it for you, and then calling your own variant of `set-parameters` to install those parameters.
|
||||
|
||||
## Batched Parameters
|
||||
|
||||
By default, `next.jdbc` assumes that you are providing a single set of parameter values and then executing the prepared statement. If you want to run a single prepared statement with multiple sets of parameters, you might want to take advantage of the increased performance that may come from using JDBC's batching machinery.
|
||||
|
||||
```clojure
|
||||
(with-open [con (jdbc/get-connection ds)
|
||||
ps (jdbc/prepare con ["insert into status (id,name) values (?,?)"])]
|
||||
(p/set-parameters ps [1 "Approved"])
|
||||
(.addBatch ps)
|
||||
(p/set-parameters ps [2 "Rejected"])
|
||||
(.addBatch ps)
|
||||
(p/set-parameters ps [3 "New"])
|
||||
(.addBatch ps)
|
||||
(.executeBatch ps))
|
||||
```
|
||||
|
||||
Here we set parameters and add them in batches to the prepared statement, then we execute the prepared statement in batch mode. You could also do the above like this, assuming you have those sets of parameters in a sequence:
|
||||
|
||||
```clojure
|
||||
(with-open [con (jdbc/get-connection ds)
|
||||
ps (jdbc/prepare con ["insert into status (id,name) values (?,?)"])]
|
||||
(run! #(.addBatch (p/set-parameters ps %))
|
||||
[[1 "Approved"] [2 "Rejected"] [3 "New"]])
|
||||
(.executeBatch ps))
|
||||
```
|
||||
|
||||
There are several caveats around using batched parameters. Some JDBC drivers need a "hint" in order to perform the batch operation as a single command for the database. In particular, PostgreSQL requires the `:reWriteBatchedInserts true` option and MySQL requires `:rewriteBatchedStatement true` (both non-standard JDBC options, of course!).
|
||||
|
||||
In addition, if the batch operation fails for one of the sets of parameters, it is database-specific whether the remaining sets of parameters are used, i.e., whether the operation is performed for any further sets of parameters after the one that failed. The result of calling `.executeBatch` is an array of integers (specifically a Java array `int[]`). Each element of the array is the number of rows affected by the operation for each set of parameters. `.executeBatch` may throw a `BatchUpdateException` and calling `.getUpdatedCounts` on the exception may return an array containing a mix of update counts and error values. Some databases don't always return an update count but instead a value indicating the number of rows is not known (but sometimes you can still get the update counts).
|
||||
|
||||
Finally, some database drivers don't do batched operations at all -- they accept `.executeBatch` but they run the operation as separate commands for the database rather than a single batched command.
|
||||
|
||||
[<: Result Set Builders](/doc/result-set-builders.md) | [Transactions :>](/doc/transactions.md)
|
||||
|
|
|
|||
|
|
@ -439,34 +439,34 @@
|
|||
(-execute [this sql-params opts]
|
||||
(reify clojure.lang.IReduceInit
|
||||
(reduce [_ f init]
|
||||
(with-open [con (p/get-connection this opts)]
|
||||
(with-open [stmt (prepare/create con
|
||||
(first sql-params)
|
||||
(rest sql-params)
|
||||
opts)]
|
||||
(reduce-stmt stmt f init opts))))
|
||||
(with-open [con (p/get-connection this opts)
|
||||
stmt (prepare/create con
|
||||
(first sql-params)
|
||||
(rest sql-params)
|
||||
opts)]
|
||||
(reduce-stmt stmt f init opts)))
|
||||
(toString [_] "`IReduceInit` from `plan` -- missing reduction?")))
|
||||
(-execute-one [this sql-params opts]
|
||||
(with-open [con (p/get-connection this opts)]
|
||||
(with-open [stmt (prepare/create con
|
||||
(first sql-params)
|
||||
(rest sql-params)
|
||||
opts)]
|
||||
(with-open [con (p/get-connection this opts)
|
||||
stmt (prepare/create con
|
||||
(first sql-params)
|
||||
(rest sql-params)
|
||||
opts)]
|
||||
(if-let [rs (stmt->result-set stmt opts)]
|
||||
(let [builder-fn (get opts :builder-fn as-maps)
|
||||
builder (builder-fn rs opts)]
|
||||
(when (.next rs)
|
||||
(datafiable-row (row-builder builder) this opts)))
|
||||
{:next.jdbc/update-count (.getUpdateCount stmt)}))))
|
||||
{:next.jdbc/update-count (.getUpdateCount stmt)})))
|
||||
(-execute-all [this sql-params opts]
|
||||
(with-open [con (p/get-connection this opts)]
|
||||
(with-open [stmt (prepare/create con
|
||||
(first sql-params)
|
||||
(rest sql-params)
|
||||
opts)]
|
||||
(with-open [con (p/get-connection this opts)
|
||||
stmt (prepare/create con
|
||||
(first sql-params)
|
||||
(rest sql-params)
|
||||
opts)]
|
||||
(if-let [rs (stmt->result-set stmt opts)]
|
||||
(datafiable-result-set rs this opts)
|
||||
[{:next.jdbc/update-count (.getUpdateCount stmt)}]))))
|
||||
[{:next.jdbc/update-count (.getUpdateCount stmt)}])))
|
||||
|
||||
java.sql.PreparedStatement
|
||||
;; we can't tell if this PreparedStatement will return generated
|
||||
|
|
|
|||
|
|
@ -203,15 +203,6 @@
|
|||
(for-insert-multi table cols rows opts)
|
||||
(merge {:return-keys true} opts))))
|
||||
|
||||
(comment
|
||||
;; removed this caveat from insert-multi! because it doesn't apply --
|
||||
;; these DB-specific options are only needed when you have batched
|
||||
;; parameters and the driver needs a "hint" to perform an actual batch op!
|
||||
"Note: some database drivers need to be told to rewrite the SQL for this to
|
||||
be performed as a single, batched operation. In particular, PostgreSQL
|
||||
requires the `:reWriteBatchedInserts true` option and MySQL requires
|
||||
`:rewriteBatchedStatement true` (both non-standard JDBC options, of course!).")
|
||||
|
||||
(defn query
|
||||
"Syntactic sugar over `execute!` to provide a query alias.
|
||||
|
||||
|
|
|
|||
|
|
@ -82,21 +82,21 @@
|
|||
(is (every? int? (map first (rest rs))))
|
||||
(is (every? string? (map second (rest rs))))))
|
||||
(testing "prepare"
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))]
|
||||
(with-open [ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit order by id"])]
|
||||
(jdbc/execute! ps)))]
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))
|
||||
ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit order by id"])]
|
||||
(jdbc/execute! ps))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 4 (count rs)))
|
||||
(is (= 1 (:FRUIT/ID (first rs))))
|
||||
(is (= 4 (:FRUIT/ID (last rs)))))
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))]
|
||||
(with-open [ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit where id = ?"])]
|
||||
(jdbc/execute! (prep/set-parameters ps [4]))))]
|
||||
(let [rs (with-open [con (jdbc/get-connection (ds))
|
||||
ps (jdbc/prepare
|
||||
con
|
||||
["select * from fruit where id = ?"])]
|
||||
(jdbc/execute! (prep/set-parameters ps [4])))]
|
||||
(is (every? map? rs))
|
||||
(is (every? meta rs))
|
||||
(is (= 1 (count rs)))
|
||||
|
|
|
|||
Loading…
Reference in a new issue