Clojure 앱에서 데이터베이스에 접속해 쿼리를 실행하는 방법은 여러 가지가 있다. Korma를 이용해도 되고 jdbc를 이용해도 된다. 이번에는 clojure.jdbc를 이용한 데이터베이스 쿼리하기를 정리해보기로 한다.
korma
http://sqlkorma.com
clojure.jdbc
http://funcool.github.io/clojure.jdbc/latest
프로젝트 생성
clojure.jdbc는 jdbc 기반의 로우레벨 라이브러리다. 이 라이브러리를 사용하기 위해 lein으로 생성한 프로젝트의 project.clj 파일에 아래와 같이 의존성을 추가한다.
[funcool/clojure.jdbc "0.9.0"]
[mysql/mysql-connector-java "5.1.6"]
그리고 c3p0의 Connection pool 기능을 사용하기 위해 아래와 같은 의존성을 추가한다.
[com.mchange/c3p0 "0.9.5"]
프로젝트 폴더에 다음과 같은 파일들을 추가한다.
config
- dev
- config.edn
src
- model
- users.clj
- app.clj
- db.clj
- settings.clj
config/dev/config.edn
데이터베이스 접속과 관련된 credentials을 코드로부터 분리하기 위해 config.edn 파일을 추가했다. 이와 관련된 내용은 아래 링크의 내용을 참고하세요.
Clojure 앱에서 환경변수 사용 방법
https://iamcool.tistory.com/11
settings.clj
settings.clj 파일은 config/dev/config.edn 파일을 읽어와 db-spec, redis-spec을 만든다. db-spec 파일은 url, user, password로 구성되어 있으며 db.clj 파일에서 사용된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (ns settings (:require [config.core :refer [env]])) (defn create-db-spec [runtime-env-db] (let [{subprotocol :dbtype host :host port :port user :user password :password dbname :dbname} runtime-env-db subname (format "//%s:%s/%s" host port dbname)] {:url (str "jdbc:" subprotocol ":" subname) :user user :password password})) (def db-spec (create-db-spec (get-in env [:runtime :db]))) (def redis-spec (get-in env [:runtime :redis])) | cs |
db.clj
db.clj 파일은 settings.clj 파일로부터 db-spec을 가져와 c3p0 pooling DataSource를 만든다. 우리는 이 DataSource로부터 jdbc connection을 만들어 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | (ns db (:require [settings :refer [db-spec]] [jdbc.core :as jdbc]) (:import [java.sql Connection DriverManager] (com.mchange.v2.c3p0 ComboPooledDataSource))) (defn get-datasource [db-spec] (let [{url :url user :user password :password} db-spec] (doto (ComboPooledDataSource.) (.setDriverClass "com.mysql.jdbc.Driver") (.setJdbcUrl url) (.setUser user) (.setPassword password) ;; Pool Size Management (.setMinPoolSize 2) (.setMaxPoolSize 20) (.setAcquireIncrement 5) (.setMaxStatements 180) ;; Connection eviction (.setMaxConnectionAge 3600) (.setMaxIdleTime 1800) (.setMaxIdleTimeExcessConnections 120) ;; Connection testing (.setTestConnectionOnCheckin false) (.setTestConnectionOnCheckout false) (.setIdleConnectionTestPeriod 600)))) (def ds (get-datasource db-spec)) (defn db-conn [] (jdbc/connection ds)) | cs |
이 때 사용되는 db-spec은 config/dev/config.edn으로부터 만들어졌으며 다음과 같은 값을 가진다.
{:url "jdbc:mysql://localhost:3306/dbname"
:user"user"
:password "password"}
커넥션풀과 관련된 설정은 c3p0 사이트를 참고하세요.
c3p0
https://www.mchange.com/projects/c3p0
models/users.clj
이 파일은 db.clj 파일로부터 jdbc connection을 가져와 test_user 데이터베이스에 생성한 users 테이블에 CRUD 작업을 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | (ns models.users (:use [jdbc.core :as jdbc] [db :refer [db-conn]])) (defn read-user [data] (with-open [conn (db-conn)] (let [{username :username} data query "SELECT id, username, email, title, first_name, surname, created_at, updated_at FROM users WHERE username = ?"] (jdbc/fetch conn [query username])))) (defn create-user [row] (with-open [conn (db-conn)] (let [{username :username email :email title :title first_name :first_name surname :surname} row query "INSERT INTO users (username, email, title, first_name, surname) VALUES (?, ?, ?, ?, ?)"] (jdbc/execute conn [query username, email, title, first_name, surname])))) (defn delete-user [data] (with-open [conn (db-conn)] (let [{username :username} data query "DELETE FROM users WHERE username = ?"] (jdbc/execute conn [query username])))) | cs |
테스트
테스트를 위해 아래 명령을 실행한다.
명령이 실행되면 앱은 test_user 데이터베이스의 users 테이블에 접속하여 새로운 유저를 생성, 조회한 뒤 삭제한다. 아래 로그를 참고하세요.
Create users!
// JDBC CONNECTION LOG
Read users!
[{:id 17, :username user1, :email user1@example.com, :title Mr, :first_name James, :surname Kook, :created_at #inst "2019-01-15T22:47:02.000000914-00:00", :updated_at #inst "2019-01-15T22:47:02.000000914-00:00"}]
Delete users!
소스코드
간단한 예제이지만 여기에 전체 소스코드를 올려놓았다.
https://github.com/ksleeq21/clojure-db-example
처음에는 korma를 이용해 개발을 했었는데 조금 복잡한 쿼리를 만들기가 어려웠다. 예전에 후배가 Clojure를 쓰다보면 Java를 찾게 된다더니 그 기분을 조금 이해하게 됐다.