From b3f462e90b47773c16a242207a79e5300e1e7c60 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 18 Jul 2019 12:12:09 -0700 Subject: [PATCH] Fixes #48 by documenting connection/->pool --- doc/getting-started.md | 45 ++++++++++++++++++++++++++++++++++-- src/next/jdbc/connection.clj | 25 ++++++++++++++++---- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/doc/getting-started.md b/doc/getting-started.md index 3b2c345..3dddea6 100644 --- a/doc/getting-started.md +++ b/doc/getting-started.md @@ -22,7 +22,7 @@ In addition, you will need to add dependencies for the JDBC drivers you wish to ## An Example REPL Session -To start using `next.jdbc`, you need to create a datasource (an instance of `javax.sql.DataSource`). You can use `next.jdbc/get-datasource` with either a "db-spec" -- a hash map describing the database you wish to connect to -- or a JDBC URI string. Or you can construct a datasource from one of the connection pooling libraries out there, such as [HikariCP](https://brettwooldridge.github.io/HikariCP/) or [c3p0](https://www.mchange.com/projects/c3p0/). +To start using `next.jdbc`, you need to create a datasource (an instance of `javax.sql.DataSource`). You can use `next.jdbc/get-datasource` with either a "db-spec" -- a hash map describing the database you wish to connect to -- or a JDBC URI string. Or you can construct a datasource from one of the connection pooling libraries out there, such as [HikariCP](https://brettwooldridge.github.io/HikariCP/) or [c3p0](https://www.mchange.com/projects/c3p0/) -- see [Connection Pooling](#connection-pooling) below. For the examples in this documentation, we will use a local H2 database on disk, and we'll use the [Clojure CLI tools](https://clojure.org/guides/deps_and_cli) and `deps.edn`: @@ -113,7 +113,7 @@ Any operation that can perform key-based lookup can be used here without creatin ## Datasources, Connections & Transactions -In the examples above, we created a datasource and then passed it into each function call. When `next.jdbc` is given a datasource, it creates a `java.sql.Connection` from it, uses it for the SQL operation, and then closes it. If you're not using a connection pooling datasource, that can be quite an overhead: setting up database connections to remote servers is not cheap! +In the examples above, we created a datasource and then passed it into each function call. When `next.jdbc` is given a datasource, it creates a `java.sql.Connection` from it, uses it for the SQL operation, and then closes it. If you're not using a connection pooling datasource (see below), that can be quite an overhead: setting up database connections to remote servers is not cheap! If you want to run multiple SQL operations without that overhead each time, you can create the connection yourself and reuse it across several operations using `with-open` and `next.jdbc/get-connection`: @@ -145,6 +145,47 @@ If `with-transaction` is given a datasource, it will create and close the connec (jdbc/execute! con ...)) ; committed ``` +## Connection Pooling + +`next.jdbc` makes it easy to use either HikariCP or c3p0 for connection pooling. + +First, you need to add the connection pooling library as a dependency, e.g., + +```clojure +com.zaxxer/HikariCP {:mvn/version "3.3.1"} +;; or: +com.mchange/c3p0 {:mvn/version "0.9.5.4"} +``` + +_Check those libraries' documentation for the latest version to use!_ + +Then import the appropriate classes into your code: + +```clojure +(ns my.main + (:require [next.jdbc :as jdbc] + [next.jdbc.connection :as connection]) + (:import (com.zaxxer.hikari HikariDataSource) + ;; or: + (com.mchange.v2.c3p0 ComboPooledDataSource PooledDataSource))) +``` + +Finally, create the connection pooled datasource. `db-spec` here contains the regular `next.jdbc` options (`:dbtype`, `:dbname`, and maybe `:host`, `:port`, `:classname` etc). Those are used to construct the JDBC URL that is passed into the datasource object (by calling `.setJdbcUrl` on it). You can also specify any of the connection pooling library's options, as mixed case keywords corresponding to any simple setter methods on the class being passed in, e.g., `:connectionTestQuery`, `:maximumPoolSize` (HikariCP), `:maxPoolSize`, `:preferredTestQuery` (c3p0). + +```clojure +(let [^HikariDataSource ds (connection/->pool HikariDataSource db-spec)] + (jdbc/execute! ds ...) + (jdbc/execute! ds ...) + (into [] (map :column) (jdbc/plan ds ...))) +;; or: +(let [^PooledDataSource ds (connection/->pool ComboPooledDataSource db-spec)] + (jdbc/execute! ds ...) + (jdbc/execute! ds ...) + (into [] (map :column) (jdbc/plan ds ...))) +``` + +You only need the type hints on `ds` if you plan to call methods on it via Java interop, such as `.close` (or using `with-open` instead of `let` to auto-close it) and you want to avoid reflection. + ## Support from Specs As you are developing with `next.jdbc`, it can be useful to have assistance from `clojure.spec` in checking calls to `next.jdbc`'s functions, to provide explicit argument checking and/or better error messages for some common mistakes, e.g., trying to pass a plain SQL string where a vector (containing a SQL string, and no parameters) is expected. diff --git a/src/next/jdbc/connection.clj b/src/next/jdbc/connection.clj index ba043f2..32bae89 100644 --- a/src/next/jdbc/connection.clj +++ b/src/next/jdbc/connection.clj @@ -193,11 +193,28 @@ [url etc])) (defn ->pool - "Given a connection pooling class and a database spec, return an connection - pool object built from the database spec." + "Given a (connection pooled datasource) class and a database spec, return a + connection pool object built from that class and the database spec. + + Assumes the `clazz` has a `.setJdbcUrl` method (which HikariCP and c3p0 do). + + If you already have a JDBC URL and want to use this method, pass `:jdbcUrl` + in the database spec (instead of `:dbtype`, `:dbname`, etc). + + Properties for the connection pool object can be passed as mixed case + keywords that correspond to setter methods (just as `:jdbcUrl` maps to + `.setJdbcUrl`). `clojure.java.data/to-java` is used to construct the + object and call the setters. + + Note that the result is not type-hinted (because there's no common base + class or interface that can be assumed). In particular, connection pooled + datasource object may need to be closed but they don't necessarily implement + `java.io.Closeable` (HikariCP does, c3p0 does not)." [clazz db-spec] - (let [[url etc] (spec->url+etc db-spec)] - (to-java clazz (assoc etc :jdbcUrl url)))) + (if (:jdbcUrl db-spec) + (to-java clazz db-spec) + (let [[url etc] (spec->url+etc db-spec)] + (to-java clazz (assoc etc :jdbcUrl url))))) (defn- string->url+etc "Given a JDBC URL, return it with an empty set of options with no parsing."