`(close! connection-ish-thing)`
enhancement
When working with next.jdbc, I always open a connection explicitly, pass it around to do some work, then close it. This is straightforward when I know that the connection is a java.sql.Connection; I have a function like:
```clj
(defn open
"Opens a connection to the given node."
[node]
(let [spec {:dbtype "postgresql"
:host node
:port (:postgres-port test)}]
ds (j/get-datasource spec)
conn (j/get-connection ds)]
conn)
(defn close!
"Closes a connection."
[^java.sql.Connection conn]
(.close conn)))
```
My application calls `(open node)` to get a connection, works with it a bit, then calls `(close! conn)` to close it.
However, I *also* usually have some code that may (or may not, depending on configuration options) automatically wrap a connection in some logging code. I want this logging code to be applied across lots of parts of the program which all open their own clients, so I put it in `open`:
```clj
(defn with-logging
"Wraps a connection-esque thing with an SQL logger, if (:log-sql test) is true."
[test conn]
(if (:log-sql test)
(j/with-logging conn
(fn req-logger [op sql] sql)
(fn res-logger [op sql res]
(info op (pr-str sql) '->
(if (instance? Throwable res)
(.getMessage ^Throwable res)
(pr-str res)))))
conn))
(defn open
"Opens a connection to the given node."
[test node]
(let [spec {:dbtype "postgresql"
:host node
:port (:postgres-port test)}]
ds (j/get-datasource spec)
conn (j/get-connection ds)]
(with-logging test conn))
```
Depending on whether or not `(:log-sql test)` is true, the connection will either be a raw `Connection`, or a next.jdbc logging wrapper object. Now `close!` will explode if logging is enabled. My workaround right now is:
```clj
(defn close!
"Closes a connection."
[conn]
(j/on-connection [^java.sql.Connection conn conn]
(.close conn)))
```
This is, I think, OK because I always open a connection explicitly--there should be, *somewhere* in whatever `conn` is, an actual `Connection` to close. But it also feels a little kludgy, because `on-connection` might, if passed a spec map, open a new connection in order to close it.
Tangentially, I wound up writing a new macro that preserves the logging wrapper through transactions.
```clj
(defmacro with-txn
"Like next.jdbc/with-transaction, but takes a test map first. Re-wraps the
connection in logging, if applicable."
[test [lhs rhs & opts] & body]
`(j/with-transaction [~lhs ~rhs ~@opts]
(let [~lhs (with-logging ~test ~lhs)]
~@body)))
```
All this feels... not bad, but a little awkward. I feel like closing connections--and in general, making sure resources are released properly--is such a fundamental thing for any network client that there should be a standard way to do it, and I'm surprised I haven't found something for this in the library.
I'm not sure what a fix might look like. At a deep level, I feel like next.jdbc's choice to allow passing maps and implicitly opening connections winds up creating all kinds of cascading, awkward effects throughout the API, but it also feels like such a core choice that I'm hesitant to suggest altering it. Maybe the logging wrapper could... implement `java.io.Closeable`? Or some sort of marker interface? That might also let things like `j/with-transaction` automatically preserve logging wrappers when transactions are opened.
关闭于 2026-05-23 4 条评论