Working with Databases
Prerequisites: Web services with Ring
Clojure talks to SQL databases through JDBC, and the standard library for that is next.jdbc. Results come back as vectors of plain maps — no ORM, just data.
Queries with next.jdbc
;; illustrative — deps: com.github.seancorfield/next.jdbc 1.3.1118 and a JDBC driver
(require '[next.jdbc :as jdbc]
'[next.jdbc.sql :as sql])
(def ds (jdbc/get-datasource {:dbtype "postgresql" :dbname "shop"
:user "app" :password (System/getenv "DB_PASSWORD")}))
(jdbc/execute! ds ["CREATE TABLE IF NOT EXISTS product (id SERIAL PRIMARY KEY, name TEXT, price NUMERIC)"])
(sql/insert! ds :product {:name "Keyboard" :price 49.00M})
(sql/insert! ds :product {:name "Mouse" :price 19.00M})
(jdbc/execute! ds ["SELECT * FROM product WHERE price > ? ORDER BY name" 20])
;; => [#:product{:id 1, :name "Keyboard", :price 49.00M}]
(sql/update! ds :product {:price 17.50M} {:name "Mouse"})
(sql/find-by-keys ds :product {:name "Mouse"})
- A datasource describes the connection; in production, wrap it in
a connection pool (HikariCP via
next.jdbc.connection). - SQL is written as a vector: the query string followed by parameters.
?placeholders are always used for values — never build SQL by concatenating strings, or you invite SQL injection. - Column names come back as qualified keywords (
:product/name), which keeps data from different tables distinct. - Format and check your queries with the SQL Formatter.
Transactions
;; illustrative
(jdbc/with-transaction [tx ds]
(sql/update! tx :account {:balance 70} {:id 1})
(sql/update! tx :account {:balance 80} {:id 2}))
If anything inside throws, the whole transaction rolls back.
Building SQL as data
HoneySQL turns maps into SQL strings with parameters, which is handy when queries are assembled dynamically:
;; illustrative — deps: com.github.seancorfield/honeysql
(require '[honey.sql :as hsql])
(hsql/format {:select [:name :price]
:from [:product]
:where [:> :price 20]
:order-by [:name]})
;; => ["SELECT name, price FROM product WHERE price > ? ORDER BY name" 20]
Migrations and other databases
- Migratus or Ragtime manage schema migrations.
- Datomic and XTDB are databases designed around immutable, time-aware facts, queried with Datalog — a natural fit for Clojure's data model.
- For MongoDB, Redis, Kafka, and cloud services, use Clojure wrappers or the Java clients directly through interop.