SQL queries in Clojure with next.jdbc libraryλ︎
Using next.jdbc to connect to a database and run queries only a few steps
- add
seancorfield/next.jdbc
as a project dependency - require the
seancorfield/next.jdbc
in the relevant project namespace definitions - define a database specification (hash-map of database details or JDBC string)
- create a connection (optionally using a connection pool)
- execute SQL statements (individual, batch, transaction)
Hint::next.jdbc supersedes clojure.java.jdbcλ︎
seancorfield/next.jdbc supersedes
clojure.java.jdbc
which used to be the defacto library for database backed projects. next.jdbc is faster and exposes a more modern API design (according to the author of clojure.java.jdbc). Migration from clojure.java.jdbc is documented on the next.jdbc repository
Live Coding exampleλ︎
Summary of using next.jdbcλ︎
Include next.jdbc as a dependency in the project
{:deps
{org.clojure/clojure {:mvn/version "1.10.1"}
org.seancorfield/next.jdbc {:mvn/version "1.1.569"}}}
Require next.jdbc into the project namespace
Specify the database connectionλ︎
Define a data source connection using a next.jdbc hash map or a JDBC URL
An example next.jdbc specification for H2 database
An example JDBC connection string for postgres database
"jdbc:postgresql://<hostname>:port/<database-name>?user=<username>&password=<password>&sslmode=require"
Running SQL queriesλ︎
execute!
runs an SQL statement and returns the results as a vector of hash maps. The hash maps use table and column name to create qualified keywords in the results.
A Clojure string contains the SQL statement.
(jdbc/execute!
connection
[(str "insert into account_holders(
account_holder_id,first_name,last_name,email_address,residential_address,social_security_number)
values(
'" account-holder-id "', 'Jenny', 'Jetpack', 'jen@jetpack.org', '42 Meaning Lane, Altar IV', 'AB101112C' )")])
Hint::Datafy resultsλ︎
Hash maps returned by
execute!
use Datafy and are therefore navigable using Clojure data browsers
Using connections and queries effectivelyλ︎
Define a name for the database connection using the form (jdbc/get-datasource {:dbtype "..." :dbname "..." ...})
Use the with-open
Clojure core function to automatically close connections after running SQL expressions
Defining a generic function provides a simple way to run any SQL query for a specified data base connection.
(defn query-database
[db-spec sql-statement]
(with-open [connection (jdbc/get-connection db-spec)]
(jdbc/execute! connection sql-statement)))
Using next.jdbc friendly functionsλ︎
next.jdbc provides higher level abstractions over execute! function. These friendly functions take a database connection, a table name as a Clojure keyword and a hash map that contains the values in the query. As the query is a hash map it can also be represented by a Clojure specification (clojure.spec or Malli).
Function names ending with a bang, !
, change the contents of the database
insert!
- insert new rowsquery
- read dataupdate!
- update existing rowsdelete!
- remove rows
Generic insert function with next.jdbc.sqlλ︎
To save repetition, define a generic function that uses insert
and takes a database table name, data to insert and the database connection.
(defn insert-data
[db-spec table record-data]
(with-open [connection (jdbc/get-connection db-spec)]
(jdbc-sql/insert! connection table record-data)))
Call the generic insert function with the database connection, table name and query specification
(insert-data
db-spec
:public.account_holders
{:account_holder_id (java.util.UUID/randomUUID)
:first_name "Rachel"
:last_name "Rocketpack"
:email_address "rach@rocketpack.org"
:residential_address "1 Ultimate Question Lane, Altar IV"
:social_security_number "BB104312D"} )
HINT::next.jdbc getting started guideλ︎
next.jdbc getting started guide is very detailed.