Explore Next.jdbc, a modern, high-performance JDBC library for Clojure, and learn how to perform common database operations efficiently.
As experienced Java developers, you are likely familiar with JDBC (Java Database Connectivity) for interacting with relational databases. In Clojure, next.jdbc is a modern, high-performance library that provides a more idiomatic and efficient way to work with databases. This section will introduce next.jdbc, demonstrate how to perform common database operations, and highlight its advantages over traditional JDBC.
next.jdbc is a Clojure library designed to simplify database interactions while maintaining high performance. It builds upon the foundation of JDBC, offering a more functional approach that aligns with Clojure’s paradigms. Unlike traditional JDBC, which often involves verbose and imperative code, next.jdbc allows for concise and expressive database operations.
Before diving into code examples, let’s set up next.jdbc in your Clojure project. Ensure you have a working Clojure environment and a database to connect to.
Add the following dependency to your deps.edn or project.clj file:
1;; deps.edn
2{:deps {org.clojure/clojure {:mvn/version "1.10.3"}
3 seancorfield/next.jdbc {:mvn/version "1.2.780"}}}
4
5;; project.clj
6(defproject my-clojure-project "0.1.0-SNAPSHOT"
7 :dependencies [[org.clojure/clojure "1.10.3"]
8 [seancorfield/next.jdbc "1.2.780"]])
To connect to a database, you’ll need a JDBC URL and credentials. Here’s how to establish a connection using next.jdbc:
1(require '[next.jdbc :as jdbc])
2
3(def db-spec
4 {:dbtype "h2" ; Replace with your database type, e.g., "mysql", "postgresql"
5 :dbname "testdb"
6 :user "sa"
7 :password ""})
8
9(def datasource (jdbc/get-datasource db-spec))
Let’s explore how to perform common database operations using next.jdbc, including querying, inserting, updating, and deleting data.
Fetching data from a database is a fundamental operation. With next.jdbc, you can execute queries and retrieve results as Clojure data structures.
1(defn fetch-users []
2 (jdbc/execute! datasource ["SELECT * FROM users"]))
The execute! function executes the SQL query and returns the results as a vector of maps, where each map represents a row.
Inserting data into a database is straightforward with next.jdbc. Use the execute! function with an insert statement.
1(defn insert-user [name email]
2 (jdbc/execute! datasource
3 ["INSERT INTO users (name, email) VALUES (?, ?)" name email]))
This function inserts a new user into the users table, using parameterized queries to prevent SQL injection.
Updating existing records is similar to inserting. Use the execute! function with an update statement.
1(defn update-user-email [id new-email]
2 (jdbc/execute! datasource
3 ["UPDATE users SET email = ? WHERE id = ?" new-email id]))
This function updates the email of a user identified by their id.
Deleting records is also straightforward. Use the execute! function with a delete statement.
1(defn delete-user [id]
2 (jdbc/execute! datasource
3 ["DELETE FROM users WHERE id = ?" id]))
This function deletes a user from the users table based on their id.
Beyond basic CRUD operations, next.jdbc offers advanced features that enhance its functionality and performance.
Transactions ensure that a series of operations either complete successfully or have no effect. Use with-transaction to manage transactions in next.jdbc.
1(defn transfer-funds [from-account to-account amount]
2 (jdbc/with-transaction [tx datasource]
3 (jdbc/execute! tx ["UPDATE accounts SET balance = balance - ? WHERE id = ?" amount from-account])
4 (jdbc/execute! tx ["UPDATE accounts SET balance = balance + ? WHERE id = ?" amount to-account])))
This function transfers funds between two accounts, ensuring atomicity.
next.jdbc allows you to customize how data is transformed when retrieved from the database. Use the :builder-fn option to specify a custom transformation function.
1(defn snake-to-kebab [s]
2 (clojure.string/replace s "_" "-"))
3
4(defn custom-fetch-users []
5 (jdbc/execute! datasource
6 ["SELECT * FROM users"]
7 {:builder-fn (jdbc/as-unqualified-lower-maps snake-to-kebab)}))
This example transforms column names from snake_case to kebab-case.
Efficient database connections are crucial for performance. next.jdbc supports connection pooling through libraries like HikariCP.
1(require '[hikari-cp.core :as hikari])
2
3(def pooled-datasource
4 (hikari/make-datasource {:jdbc-url "jdbc:h2:mem:testdb"
5 :username "sa"
6 :password ""}))
Let’s compare next.jdbc with traditional JDBC to highlight the differences and advantages.
Here’s a typical JDBC example in Java for querying data:
1import java.sql.Connection;
2import java.sql.DriverManager;
3import java.sql.ResultSet;
4import java.sql.Statement;
5
6public class JdbcExample {
7 public static void main(String[] args) {
8 try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "")) {
9 Statement stmt = conn.createStatement();
10 ResultSet rs = stmt.executeQuery("SELECT * FROM users");
11 while (rs.next()) {
12 System.out.println("User: " + rs.getString("name"));
13 }
14 } catch (Exception e) {
15 e.printStackTrace();
16 }
17 }
18}
The equivalent operation in Clojure using next.jdbc is more concise and expressive:
1(defn fetch-users []
2 (jdbc/execute! datasource ["SELECT * FROM users"]))
next.jdbc reduces boilerplate code, making it easier to read and maintain.Experiment with the following modifications to deepen your understanding of next.jdbc:
Below is a diagram illustrating the flow of data through next.jdbc operations, from establishing a connection to executing queries and handling results.
graph TD;
A[Establish Connection] --> B[Execute Query];
B --> C[Transform Data];
C --> D[Return Clojure Data Structures];
D --> E[Use Data in Application];
Diagram: Data flow in next.jdbc, from connection to data usage.
For more information on next.jdbc, consider exploring the following resources:
next.jdbc.next.jdbc with traditional JDBC in a sample application.next.jdbc provides a modern, high-performance way to interact with databases in Clojure.next.jdbc, you can write more expressive and maintainable database code in Clojure.Now that we’ve explored how next.jdbc enhances database interactions in Clojure, let’s apply these concepts to build robust and efficient data-driven applications.