Skip to content

Commit 2ea886c

Browse files
committed
Add lesson 27: working with databases
1 parent f804824 commit 2ea886c

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed

otus-27/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Занятие «Работа с реляционными базами данных»
2+
3+
## Цели занятия
4+
5+
Научиться работать с SQL базами данных используя Clojure библиотеки.
6+
7+
## Краткое содержание
8+
9+
- познакомимся с протоколом jdbc и подключим библиотеку next.jdbc;
10+
- подключимся к базе данных и научимся выполнять транзакции;
11+
- научимся использовать библиотеку honey-sql для работы с языком запросов SQL;
12+
- создадим базу данных для проекта URL Shortener, напишем SQL запросы для реализации бизнес логики приложения.
13+
14+
## JDBC
15+
16+
[Java Database Connectivity](https://en.wikipedia.org/wiki/Java_Database_Connectivity) is an application programming interface (API) for the Java programming language which defines how a client may access a database.
17+
18+
## next.jdbc
19+
20+
[next.jdbc repo](https://github.com/seancorfield/next-jdbc)
21+
22+
db-spec -> DataSource -> Connection
23+
24+
### JDBC drivers
25+
26+
- [PostgreSQL](https://central.sonatype.com/artifact/org.postgresql/postgresql)
27+
- [H2](https://central.sonatype.com/artifact/com.h2database/h2)
28+
- [SQLite](https://central.sonatype.com/artifact/org.xerial/sqlite-jdbc)
29+
30+
### Connection Pool
31+
32+
[HikariCP](https://github.com/brettwooldridge/HikariCP) or [c3p0](https://github.com/swaldman/c3p0)
33+
34+
### H2 Database
35+
36+
[H2 documentation](https://www.h2database.com/html/main.html)
37+
38+
- Very fast, open source, JDBC API;
39+
- Embedded and server modes; in-memory databases;
40+
- Browser based Console application;
41+
- Small footprint: around 2.5 MB jar file size.
42+
43+
### The primary SQL execution API in next.jdbc is
44+
45+
- plan — yields an IReduceInit that, when reduced with an initial value, executes the SQL statement and then reduces over the ResultSet with as little overhead as possible.
46+
- execute-one! — executes the SQL or DDL statement and produces a single realized hash map. The realized hash map returned by execute-one! is Datafiable and thus Navigable.
47+
- execute — executes the SQL statement and produces a vector of realized hash maps, that use qualified keywords for the column names, of the form :`<table>/<column>`.
48+
49+
## HoneySQL
50+
51+
[HoneySQL repo](https://github.com/seancorfield/honeysql)
52+
53+
## HugSQL
54+
55+
[HugSQL repo](https://github.com/layerware/hugsql)
56+
57+
## Migratus
58+
59+
[Migratus repo](https://github.com/yogthos/migratus)

otus-27/project.clj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(defproject otus-27 "0.1.0-SNAPSHOT"
2+
:description "Lesson 27: Working with databases"
3+
:dependencies [[org.clojure/clojure "1.11.1"]
4+
[com.github.seancorfield/next.jdbc "1.3.909"]
5+
[com.h2database/h2 "2.2.224"]
6+
[com.github.seancorfield/honeysql "2.5.1103"]]
7+
:repl-options {:init-ns otus-27.core})

otus-27/src/otus_27/core.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(ns otus-27.core)

otus-27/src/otus_27/repl.clj

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
(ns otus-27.repl
2+
(:require [next.jdbc :as jdbc]
3+
[next.jdbc.result-set :as rs]
4+
[next.jdbc.sql :as jdbc.sql]))
5+
6+
(comment
7+
;; https://clojuredocs.org/clojure.core/*print-namespace-maps*
8+
(set! *print-namespace-maps* false)
9+
(set! *print-namespace-maps* true))
10+
11+
;; next.jdbc
12+
13+
;; db-spec -> DataSource -> Connection
14+
(def db {:dbtype "h2" ; h2:mem
15+
:dbname "example"})
16+
17+
(def ds (jdbc/get-datasource db))
18+
19+
(with-open [conn (jdbc/get-connection ds)]
20+
(jdbc/execute! conn ...))
21+
22+
;; Primary API
23+
(jdbc/execute! ds ["select * from information_schema.tables"])
24+
25+
(reduce (fn [res row]
26+
(conj res
27+
(select-keys row
28+
[:tables/table_name
29+
:tables/table_type])))
30+
[]
31+
(jdbc/plan ; [connectable sql-params opts]
32+
ds
33+
["select * from information_schema.tables"]))
34+
35+
36+
(jdbc/execute-one! ds ["
37+
create table address (
38+
id int auto_increment primary key,
39+
name varchar(32),
40+
email varchar(255)
41+
)"])
42+
43+
(jdbc/execute-one! ds ["
44+
insert into address(name, email)
45+
values('Clojure Developer', '[email protected]')"])
46+
47+
(jdbc/execute! ds ["select * from address"])
48+
49+
(jdbc/execute-one! ds ["insert into address(name, email)
50+
values('Java Developer', '[email protected]')
51+
"] {:return-keys true})
52+
53+
(jdbc/execute-one! ds ["select * from address where id = ?" 2])
54+
(jdbc/execute-one! ds ["select * from address where id = ?" 3])
55+
56+
;; Options & Result Set Builders
57+
;; https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.909/doc/all-the-options#generating-rows-and-result-sets
58+
(require '[next.jdbc.result-set :as rs])
59+
60+
(jdbc/execute-one! ds ["select * from address where id = ?" 2]
61+
{:builder-fn rs/as-unqualified-lower-maps})
62+
63+
;; plan & Reducing Result Sets
64+
(jdbc/execute-one! ds ["
65+
create table invoice (
66+
id int auto_increment primary key,
67+
product varchar(32),
68+
unit_price decimal(10,2),
69+
unit_count int,
70+
customer_id int
71+
)"])
72+
73+
(jdbc/execute-one! ds ["
74+
insert into invoice (product, unit_price, unit_count, customer_id)
75+
values ('apple', 0.99, 6, 100),
76+
('banana', 1.25, 3, 100),
77+
('cucumber', 2.49, 2, 100)
78+
"])
79+
80+
(reduce
81+
(fn [cost row]
82+
(+ cost (* (:unit_price row)
83+
(:unit_count row))))
84+
0
85+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
86+
87+
(transduce
88+
(map #(* (:unit_price %) (:unit_count %)))
89+
+
90+
0
91+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
92+
93+
(transduce
94+
(comp (map (juxt :unit_price :unit_count))
95+
(map #(apply * %)))
96+
+
97+
0
98+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
99+
100+
(transduce
101+
(map :unit_count)
102+
+
103+
0
104+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
105+
106+
;; set of unique products
107+
(into #{}
108+
(map :product)
109+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
110+
111+
;; use run! for side-effects
112+
(run! #(println (:product %))
113+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
114+
115+
116+
;; selects specific keys (as simple keywords):
117+
(into []
118+
(map #(select-keys % [:id :product :unit_price :unit_count :customer_id]))
119+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
120+
121+
;; selects specific keys (as qualified keywords):
122+
(into []
123+
(map #(select-keys % [:invoice/id :invoice/product
124+
:invoice/unit_price :invoice/unit_count
125+
:invoice/customer_id]))
126+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
127+
128+
;; selects specific keys (as qualified keywords -- ignoring the table name):
129+
(into []
130+
(map #(select-keys % [:foo/id :bar/product
131+
:quux/unit_price :wibble/unit_count
132+
:blah/customer_id]))
133+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
134+
135+
;; do not do this:
136+
(into []
137+
(map #(into {} %))
138+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
139+
140+
;; https://clojure.org/reference/datafy
141+
;; do this if you just want realized rows with default qualified names:
142+
(into []
143+
(map #(rs/datafiable-row % ds {}))
144+
(jdbc/plan ds ["select * from invoice where customer_id = ?" 100]))
145+
146+
147+
;; Datasources, Connections & Transactions
148+
(with-open [con (jdbc/get-connection ds)]
149+
(jdbc/execute! con ...)
150+
(jdbc/execute! con ...)
151+
(into [] (map :column) (jdbc/plan con ...)))
152+
153+
154+
(jdbc/with-transaction [tx ds]
155+
(jdbc/execute! tx ...)
156+
(jdbc/execute! tx ...)
157+
(into [] (map :column) (jdbc/plan tx ...)))
158+
159+
;; Joins
160+
(jdbc/execute-one! ds ["
161+
create table owners (
162+
id int auto_increment primary key,
163+
name varchar(255) not null
164+
)"])
165+
166+
(jdbc/execute-one! ds ["
167+
create table pets (
168+
id int auto_increment primary key,
169+
name varchar(255) not null,
170+
owner int not null
171+
references owners(id)
172+
on delete cascade
173+
)"])
174+
175+
(jdbc/execute! ds ["insert into owners (name) values (?), (?)"
176+
"Bob"
177+
"Alice"])
178+
179+
;; Friendly SQL Functions
180+
;; https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.909/doc/getting-started/friendly-sql-functions
181+
(require '[next.jdbc.sql :as jdbc.sql])
182+
183+
(jdbc.sql/insert! ds :owners {:name "Tom"})
184+
185+
(jdbc/execute! ds ["select id, name from owners"])
186+
187+
(jdbc/with-transaction [tx ds]
188+
(jdbc.sql/insert-multi!
189+
tx
190+
:pets
191+
[{:name "Skipper" :owner 1}
192+
{:name "Spot" :owner 2}
193+
{:name "Stinky" :owner 2}
194+
{:name "Jerry" :owner 3}]))
195+
196+
(jdbc.sql/find-by-keys ds :pets {:owner 2})
197+
198+
(jdbc/execute! ds ["
199+
select
200+
o.name as owner,
201+
p.name as pet
202+
from owners as o
203+
left join pets as p
204+
on p.owner = o.id
205+
"])
206+
207+
;; HoneySQL
208+
(require '[honey.sql :as sql])
209+
210+
(jdbc/execute!
211+
ds
212+
(sql/format {:from [[:pets :p]]
213+
:select [:p.name :o.name]
214+
:where [[:= :p.name [:param :?]]]
215+
:left-join [[:owners :o]
216+
[:= :p.owner :o.id]]}
217+
{:params {:? "Skipper"}}))
218+
219+
(jdbc/execute!
220+
ds
221+
(sql/format '{from ((pets p))
222+
select (p.name, o.name)
223+
where ((= p.name (param :?)))
224+
left-join ((owners o)
225+
(= p.owner o.id))}
226+
{:params {:? "Jerry"}}))
227+
228+
(require '[honey.sql.helpers :as h])
229+
230+
(jdbc/execute!
231+
ds
232+
(sql/format (-> {}
233+
(h/select :p.name :p.name)
234+
(h/from [:pets :p])
235+
(h/where [:= :p.name [:param :?]])
236+
(h/left-join [:owners :o]
237+
[:= :p.owner :o.id]))
238+
{:params {:? "Jerry"}}))

0 commit comments

Comments
 (0)