ITADN

`(close! connection-ish-thing)`

#318Closedaphyr 创建于 2026-05-21
enhancement
A
aphyrcommented
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 条评论