Clojure - next.jdbc に入門した

              ·

next.jdbc を初めて触ってみたので、その備忘録

モチベーション

Clojure 自体の入門がぼちぼちできてきた感じがうっすらするので、具体的なアプリケーションを作成してみたい。 JVM といえばサーバサイド、サーバサイドといえば RDB を避けては通れないよね? ということで next.jdbc に入門してみる。

ちなみに、Clojure でのDBアクセスと言えば clojure.java.jdbc がデファクトのようなので、本当はそっちを先に学ぶべき。

next.jdbc ってなに?

clojure.java.jdbc の作者が開発している低レベルなJDBCベースのdatabaseアクセスライブラリ。

clojure.java.jdbc
A low-level Clojure wrapper for JDBC-based access to databases. This project is “Stable” (no longer “Active”). It has effectively been superseded by seancorfield/next.jdbc. https://github.com/clojure/java.jdbc#clojurejavajdbc

next.jdbc
The next generation of clojure.java.jdbc: a new low-level Clojure wrapper for JDBC-based access to databases. https://github.com/seancorfield/next-jdbc#nextjdbc-

とのことなので、作者の Sean Corfield としては 今後は next.jdbc の方の開発を進めてゆく感じのようだ。

導入

まずは導入として、 もっとも基本的な機能である get-connectionでのデータベース接続execute! および execute-one! でのクエリ実行をやってみる

データベースを構築

まずは 操作対象となる データベースをローカルに構築する。 docker-compose を使ってローカルに PostgreSQL 1 を立てるために、設定ファイルと定義ファイルを準備。

$ mkdir -p postgresql/conf

$ # postgres.conf
$ cat <<EOF > postgresql/conf/postgres.conf
listen_addresses = '*'
max_connections = 10
shared_buffers = 128MB
EOF

$ #Dockerfile
$ cat <<EOF > postgresql/Dockerfile
FROM postgres:10.5

# ロケール設定
RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8
ENV LANG ja_JP.utf8
EOF

続いて、 docker-compose.yml での起動定義を記述

$ cat <<EOF > docker-compose.yml version: '3.1'

services:

  rdb:
    build:
      context: ./postgresql
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: next-jdbc-basic
      #POSTGRES_PASSWORD: passwd
    ports:
      - "5432:5432"
    volumes:
      - ./postgresql/conf:/etc/postgresql/
EOF

コンテナを起動して、 host=localhost かつ user=postgres で疎通ができれば準備完了

$ docker-compose up -d

...

$ psql -h localhost -U postgres -c "\l next-jdbc-basic";
                                  List of databases
      Name       |  Owner   | Encoding |  Collate   |   Ctype    | Access privileges
-----------------+----------+----------+------------+------------+-------------------
 next-jdbc-basic | postgres | UTF8     | ja_JP.utf8 | ja_JP.utf8 |
(1 row)

いつもなら ここで DDL を実行するところだけど、今回は next.jdbc からDDLを発行してみる。

REPL から触ってみる

clj で repl を起動し、スキーマの初期化およびレコードを投入してみる。

PostgreSQL 向けの JDBCドライバは pgjdbc-ng を採用することとして、 deps.edn に next.jdbc および JDBCドライバ への依存を定義

;; deps.edn
{:paths ["src"]
 :deps {org.clojure/clojure {:mvn/version "RELEASE"}
        seancorfield/next.jdbc {:mvn/version "1.0.7"}
        com.impossibl.pgjdbc-ng/pgjdbc-ng {:mvn/version "0.8.2"}}}

clj を起動し、 user namespace に next.jdbc をロード

$ clj
Clojure 1.10.1

user=> (require 'next.jdbc 'clojure.pprint)
nil

next.jdbc にて定義されている各種関数は第1引数に Database Connection を受け取るようなので、 まずは next.jdbc/get-connection を実行し、 Connection を生成する。

user=> (def db-spec {:dbtype "pgsql" :dbname "next-jdbc-basic" :user "postgres"})
#'user/db-spec
user=> (def ds (next.jdbc/get-connection db-spec))
#'user/ds
user=> (type ds)
com.impossibl.postgres.jdbc.PGDirectConnection

生成された Connection を使って、クエリを発行してみる

user=> ; テーブル作成
user=> (next.jdbc/execute! ds ["
CREATE TABLE todo (
  id         UUID PRIMARY KEY,
  title      VARCHAR(256) NOT NULL,
  done       BOOLEAN NOT NULL DEFAULT false,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);"])
[#:next.jdbc{:update-count -1}]

user=> ; レコードを投入
user=> (next.jdbc/execute! ds ["
INSERT INTO todo(id, title) VALUES (?, ?);
", (java.util.UUID/randomUUID), "my first todo item"])
[#:next.jdbc{:update-count 1}]

user=> ; レコードを抽出
user=> ; next.jdbc/exectute-one! は返却値を hash-map で返却する
user=> (-> (next.jdbc/execute-one! ds ["SELECT * FROM todo;"])
(clojure.pprint/pprint))
#:todo{:id #uuid "70702da6-1b6c-4cd8-a799-b7756f091314",
       :title "my first todo item",
       :done false,
       :created_at #inst "2019-09-24T01:57:22.658499000-00:00"}
nil

next.jdbc/execute! を使用して SELECT を発行した場合は、抽出結果は ベクタ で返却されるようだ

user=> (-> (next.jdbc/execute! ds ["SELECT * FROM todo;"])
(clojure.pprint/pprint))
[#:todo{:id #uuid "70702da6-1b6c-4cd8-a799-b7756f091314",
        :title "my first todo item",
        :done false,
        :created_at #inst "2019-09-24T01:57:22.658499000-00:00"}
 #:todo{:id #uuid "d1397c78-5dde-4dfb-974a-23e128d985e9",
        :title "my second todo item",
        :done false,
        :created_at #inst "2019-09-24T02:00:32.907179000-00:00"}]
nil

とりあえず、テーブル作成・レコード登録・抽出はできた 😀

おわりに

next.jdbc では next.jdbc.sql 配下に SQL操作 を簡易にするための関数群が用意されているらしく、 実際の開発時にはそちらを使用することになるだろう。friendly-sql-functions.md#friendly-sql-functions

関数 用途
insert! 単一テーブルに単一行を登録する
insert-multi! insert! の複数行版
query execute! のエイリアス。行を抽出
update! 単一テーブルの行更新
delete! 単一テーブルから行削除

簡単なプロジェクトも作ったので、これらについても忘れないうちにメモっておきたいところ。
サンプルプロジェクト: https://github.com/micheam/examples-clj/tree/master/next-jdbc-basic


  1. ちょっと ライブラリを 触ってみるだけなのに大仰な感じもするし、本家の Getting Started にも記載されている通り H2 Database で試して見るのが良いんだろうけど。個人的にはまとまったアプリケーションを作るなら PostgreSQL を使いたいし、ということで。 ↩︎

comments powered by Disqus