์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™๊ณผ ORM ํ™œ์šฉ์„ ํ†ตํ•œ ํšจ์œจ์  ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ

๐Ÿš€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™๊ณผ ORM ํ™œ์šฉ์„ ํ†ตํ•œ ํšจ์œจ์  ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ

์›น๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ์˜ ๋ชจ๋“  ๊ฒƒ

์•ˆ๋…•! ์˜ค๋Š˜์€ ์›น๊ฐœ๋ฐœ์—์„œ ์ •๋ง ์ค‘์š”ํ•œ ์ฃผ์ œ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™๊ณผ ORM์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊ฒŒ. ๐Ÿ˜Š

ํ˜น์‹œ ์›น์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ค๋‹ค๊ฐ€ "์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ์–ด๋””์— ์ €์žฅํ•˜์ง€?", "๊ฒŒ์‹œ๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•˜์ง€?" ๊ฐ™์€ ๊ณ ๋ฏผ ํ•ด๋ณธ ์  ์žˆ์–ด? ๋ฐ”๋กœ ์ด๋Ÿด ๋•Œ ํ•„์š”ํ•œ ๊ฒŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์•ผ. ๊ทธ๋ฆฌ๊ณ  ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋” ์‰ฝ๊ณ  ํšจ์œจ์ ์œผ๋กœ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ๋งˆ๋ฒ• ๊ฐ™์€ ๋„๊ตฌ๊ฐ€ ๋ฐ”๋กœ ORM(Object-Relational Mapping)์ด์ง€!

์ด ๊ธ€์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™์˜ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ORM ํ™œ์šฉ๊นŒ์ง€, ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ์ง€์‹๋“ค์„ ์นœ๊ตฌ์ฒ˜๋Ÿผ ํŽธํ•˜๊ฒŒ ์„ค๋ช…ํ•ด์ค„๊ฒŒ. ์ปคํ”ผ ํ•œ ์ž” ๋“ค๊ณ  ํŽธํ•˜๊ฒŒ ์ฝ์–ด๋ด! โ˜•

๐Ÿ“š ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์™œ ํ•„์š”ํ•œ ๊ฑธ๊นŒ?

์›น์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ด. ์‚ฌ์šฉ์ž๊ฐ€ ํšŒ์›๊ฐ€์ž…์„ ํ•˜๊ณ , ๊ฒŒ์‹œ๊ธ€์„ ์“ฐ๊ณ , ๋Œ“๊ธ€์„ ๋‹ฌ๊ณ ... ์ด๋Ÿฐ ๋ชจ๋“  ์ •๋ณด๋“ค์„ ์–ด๋””์— ์ €์žฅํ•ด์•ผ ํ• ๊นŒ? ๐Ÿค”

์ดˆ๋ณด ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฐ€๋” ํ•˜๋Š” ์‹ค์ˆ˜๊ฐ€ ์žˆ์–ด. "๊ทธ๋ƒฅ ํ…์ŠคํŠธ ํŒŒ์ผ์— ์ €์žฅํ•˜๋ฉด ๋˜์ง€ ์•Š๋‚˜์š”?" ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฑฐ์•ผ. ๋ฌผ๋ก  ๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ๋Š” ํŒŒ์ผ๋กœ๋„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์‚ฌ์šฉ์ž๊ฐ€ 100๋ช…, 1000๋ช…, 10000๋ช…์œผ๋กœ ๋Š˜์–ด๋‚˜๋ฉด? ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ , ์ˆ˜์ •ํ•˜๊ณ , ์‚ญ์ œํ•˜๋Š” ์ž‘์—…์ด ์—„์ฒญ๋‚˜๊ฒŒ ๋ณต์žกํ•ด์ง€์ง€.

๐Ÿ’ก ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ํ•„์š”ํ•œ ์ด์œ  โ€ข ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ: ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋„ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ
โ€ข ๋™์‹œ ์ ‘์† ์ฒ˜๋ฆฌ: ์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ์–ด
โ€ข ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ: ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜๋Š” ๊ฑธ ๋ฐฉ์ง€
โ€ข ๋ณด์•ˆ: ๊ถŒํ•œ ๊ด€๋ฆฌ์™€ ์•”ํ˜ธํ™”๋กœ ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ
โ€ข ๋ฐฑ์—…๊ณผ ๋ณต๊ตฌ: ๋ฐ์ดํ„ฐ ์†์‹ค ๊ฑฑ์ • ์—†์ด ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌ
์‹ค์ œ๋กœ ์žฌ๋Šฅ๋„ท(https://www.jaenung.net) ๊ฐ™์€ ์žฌ๋Šฅ ๊ณต์œ  ํ”Œ๋žซํผ์„ ์ƒ๊ฐํ•ด๋ด. ์ˆ˜๋งŽ์€ ์žฌ๋Šฅ ํŒ๋งค์ž์™€ ๊ตฌ๋งค์ž์˜ ์ •๋ณด, ๊ฑฐ๋ž˜ ๋‚ด์—ญ, ๋ฆฌ๋ทฐ ๋“ฑ ์—„์ฒญ๋‚œ ์–‘์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ƒ์„ฑ๋˜๊ณ  ๊ด€๋ฆฌ๋˜์ž–์•„? ์ด๋Ÿฐ ์„œ๋น„์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—†์ด๋Š” ์ ˆ๋Œ€ ์šด์˜ํ•  ์ˆ˜ ์—†์–ด! ๐ŸŽฏ

๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ข…๋ฅ˜์™€ ํŠน์ง•

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋„ ์—ฌ๋Ÿฌ ์ข…๋ฅ˜๊ฐ€ ์žˆ์–ด. ํฌ๊ฒŒ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(RDBMS)์™€ NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์ง€.

๐Ÿ”ท ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (RDBMS)

๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ(ํ…Œ์ด๋ธ”) ํ˜•ํƒœ๋กœ ์ €์žฅํ•ด. ์—‘์…€ ์‹œํŠธ๋ฅผ ๋– ์˜ฌ๋ฆฌ๋ฉด ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›Œ! ๊ฐ ํ…Œ์ด๋ธ”์€ ํ–‰(row)๊ณผ ์—ด(column)๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๊ณ , ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ“Š ์ฃผ์š” RDBMS ์ข…๋ฅ˜ 1. MySQL / MariaDB
โ€ข ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
โ€ข ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ
โ€ข ๋น ๋ฅธ ์†๋„์™€ ์•ˆ์ •์„ฑ
โ€ข ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ (MySQL Community Edition)

2. PostgreSQL
โ€ข ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๊ณผ ํ™•์žฅ์„ฑ
โ€ข ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์™€ ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ๊ฐ•ํ•จ
โ€ข JSON ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ง€์›
โ€ข ์˜คํ”ˆ์†Œ์Šค์ด๋ฉด์„œ ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ์„ฑ๋Šฅ

3. Oracle Database
โ€ข ๋Œ€๊ธฐ์—…์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉ
โ€ข ์ตœ๊ณ  ์ˆ˜์ค€์˜ ์„ฑ๋Šฅ๊ณผ ๋ณด์•ˆ
โ€ข ๋น„์šฉ์ด ๋†’์ง€๋งŒ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ ์ œ๊ณต

4. Microsoft SQL Server
โ€ข Windows ํ™˜๊ฒฝ์— ์ตœ์ ํ™”
โ€ข .NET ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๊ถํ•ฉ์ด ์ข‹์Œ
โ€ข ๊ฐ•๋ ฅํ•œ ๊ด€๋ฆฌ ๋„๊ตฌ ์ œ๊ณต

5. SQLite
โ€ข ํŒŒ์ผ ๊ธฐ๋ฐ˜์˜ ๊ฒฝ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
โ€ข ๋ณ„๋„ ์„œ๋ฒ„ ์„ค์น˜ ๋ถˆํ•„์š”
โ€ข ๋ชจ๋ฐ”์ผ ์•ฑ์ด๋‚˜ ์†Œ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉ

๐Ÿ”ถ NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

NoSQL์€ "Not Only SQL"์˜ ์•ฝ์ž๋กœ, ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ œ์•ฝ์—์„œ ๋ฒ—์–ด๋‚œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์•ผ. ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๊ฐ€ ์•„๋‹Œ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ“ฆ

๐ŸŽฏ NoSQL์˜ ์ฃผ์š” ์œ ํ˜• โ€ข Document DB (๋ฌธ์„œํ˜•)
์˜ˆ์‹œ: MongoDB, CouchDB
JSON ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ ์ €์žฅ, ์œ ์—ฐํ•œ ์Šคํ‚ค๋งˆ

โ€ข Key-Value Store (ํ‚ค-๊ฐ’ ์ €์žฅ์†Œ)
์˜ˆ์‹œ: Redis, DynamoDB
๋น ๋ฅธ ์ฝ๊ธฐ/์“ฐ๊ธฐ ์†๋„, ์บ์‹ฑ์— ์ ํ•ฉ

โ€ข Column-Family Store (์ปฌ๋Ÿผํ˜•)
์˜ˆ์‹œ: Cassandra, HBase
๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ๋ถ„์‚ฐ ์ฒ˜๋ฆฌ์— ๊ฐ•ํ•จ

โ€ข Graph DB (๊ทธ๋ž˜ํ”„ํ˜•)
์˜ˆ์‹œ: Neo4j, ArangoDB
๊ด€๊ณ„ ์ค‘์‹ฌ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง์— ์ตœ์ 
๊ทธ๋Ÿผ ์–ด๋–ค ๊ฑธ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ? ๐Ÿคทโ€โ™‚๏ธ

โš–๏ธ ์„ ํƒ ๊ธฐ์ค€ RDBMS๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ:
โ€ข ๋ฐ์ดํ„ฐ ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ๋ช…ํ™•ํ•˜๊ณ  ์ค‘์š”ํ•  ๋•Œ
โ€ข ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๊ฐ€ ์ค‘์š”ํ•  ๋•Œ (์˜ˆ: ๊ธˆ์œต ์‹œ์Šคํ…œ)
โ€ข ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์ด ์ตœ์šฐ์„ ์ผ ๋•Œ
โ€ข ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ๋•Œ

NoSQL์„ ์„ ํƒํ•˜๋Š” ๊ฒฝ์šฐ:
โ€ข ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋  ๋•Œ
โ€ข ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ
โ€ข ์ˆ˜ํ‰์  ํ™•์žฅ(์Šค์ผ€์ผ ์•„์›ƒ)์ด ํ•„์š”ํ•  ๋•Œ
โ€ข ๋น„์ •ํ˜• ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ
์‹ค๋ฌด์—์„œ๋Š” ๋‘ ๊ฐ€์ง€๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ์•„. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ์ •๋ณด์™€ ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ๋Š” MySQL์— ์ €์žฅํ•˜๊ณ , ์„ธ์…˜ ์ •๋ณด๋‚˜ ์บ์‹œ ๋ฐ์ดํ„ฐ๋Š” Redis์— ์ €์žฅํ•˜๋Š” ์‹์ด์ง€! ๐ŸŽจ
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ๊ณ„ ๊ด€๊ณ„ํ˜• DB (RDBMS) MySQL PostgreSQL Oracle SQLite NoSQL DB (๋น„๊ด€๊ณ„ํ˜•) MongoDB Redis Cassandra Neo4j ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ํ™œ์šฉ ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ ํŠธ๋žœ์žญ์…˜ ์ค‘์š” ์œ ์—ฐํ•œ ์Šคํ‚ค๋งˆ ๋น ๋ฅธ ํ™•์žฅ์„ฑ

๐Ÿ”Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™์˜ ๊ธฐ์ดˆ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ์—ฐ๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊ฒŒ! ๐Ÿ’ป

๐ŸŽฏ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ํ”„๋กœ์„ธ์Šค

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™์€ ํฌ๊ฒŒ 4๋‹จ๊ณ„๋กœ ์ด๋ฃจ์–ด์ ธ:

1๋‹จ๊ณ„: ์—ฐ๊ฒฐ (Connection) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„์— ์ ‘์†ํ•˜๋Š” ๋‹จ๊ณ„์•ผ. ํ˜ธ์ŠคํŠธ ์ฃผ์†Œ, ํฌํŠธ, ์‚ฌ์šฉ์ž๋ช…, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ์˜ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•ด.

2๋‹จ๊ณ„: ์ฟผ๋ฆฌ ์‹คํ–‰ (Query Execution)
SQL ๋ฌธ์„ ์ž‘์„ฑํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๋‹จ๊ณ„. SELECT, INSERT, UPDATE, DELETE ๋“ฑ์˜ ๋ช…๋ น์„ ์‚ฌ์šฉํ•ด.

3๋‹จ๊ณ„: ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ (Result Handling)
์ฟผ๋ฆฌ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด.

4๋‹จ๊ณ„: ์—ฐ๊ฒฐ ์ข…๋ฃŒ (Connection Close)
์ž‘์—…์ด ๋๋‚˜๋ฉด ์—ฐ๊ฒฐ์„ ๋‹ซ์•„์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ํ•ด์ œํ•ด์•ผ ํ•ด. ์ด๊ฑฐ ์•ˆ ํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด! โš ๏ธ

๐Ÿ’ป ์–ธ์–ด๋ณ„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ๋ฐฉ๋ฒ•

๊ฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์กฐ๊ธˆ์”ฉ ๋‹ฌ๋ผ. ์ฃผ์š” ์–ธ์–ด๋“ค์˜ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณผ๊ฒŒ!

๐Ÿ Python + MySQL ์—ฐ๋™

Python์—์„œ๋Š” ์ฃผ๋กœ mysql-connector-python์ด๋‚˜ PyMySQL ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด.

import mysql.connector

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ
connection = mysql.connector.connect(
    host='localhost',
    user='your_username',
    password='your_password',
    database='your_database'
)

# ์ปค์„œ ์ƒ์„ฑ
cursor = connection.cursor()

# ์ฟผ๋ฆฌ ์‹คํ–‰
cursor.execute("SELECT * FROM users WHERE age > 20")

# ๊ฒฐ๊ณผ ๊ฐ€์ ธ์˜ค๊ธฐ
results = cursor.fetchall()
for row in results:
    print(row)

# ์—ฐ๊ฒฐ ์ข…๋ฃŒ
cursor.close()
connection.close()
โ˜• Java + MySQL ์—ฐ๋™

Java์—์„œ๋Š” JDBC(Java Database Connectivity)๋ฅผ ์‚ฌ์šฉํ•ด. JDBC๋Š” ์ž๋ฐ” ํ‘œ์ค€ API๋กœ, ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์–ด.

import java.sql.*;

public class DatabaseExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String user = "your_username";
        String password = "your_password";
        
        try {
            // ๋“œ๋ผ์ด๋ฒ„ ๋กœ๋“œ
            Class.forName("com.mysql.cj.jdbc.Driver");
            
            // ์—ฐ๊ฒฐ ์ƒ์„ฑ
            Connection conn = DriverManager.getConnection(url, user, password);
            
            // Statement ์ƒ์„ฑ
            Statement stmt = conn.createStatement();
            
            // ์ฟผ๋ฆฌ ์‹คํ–‰
            ResultSet rs = stmt.executeQuery("SELECT * FROM users");
            
            // ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
            while(rs.next()) {
                System.out.println(rs.getString("name"));
            }
            
            // ๋ฆฌ์†Œ์Šค ํ•ด์ œ
            rs.close();
            stmt.close();
            conn.close();
            
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
๐ŸŸจ JavaScript (Node.js) + MySQL ์—ฐ๋™

Node.js์—์„œ๋Š” mysql์ด๋‚˜ mysql2 ํŒจํ‚ค์ง€๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด.

const mysql = require('mysql2');

// ์—ฐ๊ฒฐ ํ’€ ์ƒ์„ฑ (์—ฌ๋Ÿฌ ์—ฐ๊ฒฐ์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌ)
const pool = mysql.createPool({
    host: 'localhost',
    user: 'your_username',
    password: 'your_password',
    database: 'your_database',
    waitForConnections: true,
    connectionLimit: 10
});

// Promise ๊ธฐ๋ฐ˜ ์‚ฌ์šฉ
const promisePool = pool.promise();

async function getUsers() {
    try {
        const [rows] = await promisePool.query('SELECT * FROM users');
        console.log(rows);
    } catch(error) {
        console.error(error);
    }
}

getUsers();
๐Ÿ’Ž Ruby + PostgreSQL ์—ฐ๋™

Ruby์—์„œ๋Š” pg gem์„ ์‚ฌ์šฉํ•ด์„œ PostgreSQL๊ณผ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์–ด.

require 'pg'

# ์—ฐ๊ฒฐ ์ƒ์„ฑ
conn = PG.connect(
    host: 'localhost',
    dbname: 'your_database',
    user: 'your_username',
    password: 'your_password'
)

# ์ฟผ๋ฆฌ ์‹คํ–‰
result = conn.exec('SELECT * FROM users')

# ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
result.each do |row|
    puts row['name']
end

# ์—ฐ๊ฒฐ ์ข…๋ฃŒ
conn.close
๐Ÿ” ๋ณด์•ˆ ํŒ! ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด(ํŠนํžˆ ๋น„๋ฐ€๋ฒˆํ˜ธ)๋Š” ์ ˆ๋Œ€ ์ฝ”๋“œ์— ์ง์ ‘ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๋ฉด ์•ˆ ๋ผ! ๋Œ€์‹  ์ด๋ ‡๊ฒŒ ํ•ด:

โ€ข ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ: .env ํŒŒ์ผ์— ์ €์žฅํ•˜๊ณ  ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
โ€ข ์„ค์ • ํŒŒ์ผ ๋ถ„๋ฆฌ: config ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  .gitignore์— ์ถ”๊ฐ€
โ€ข ์‹œํฌ๋ฆฟ ๊ด€๋ฆฌ ๋„๊ตฌ: AWS Secrets Manager, HashiCorp Vault ๋“ฑ ํ™œ์šฉ
โ€ข ์ตœ์†Œ ๊ถŒํ•œ ์›์น™: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌ

๐ŸŽญ SQL ์ฟผ๋ฆฌ์˜ ๊ธฐ์ดˆ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋Œ€ํ™”ํ•˜๋ ค๋ฉด SQL(Structured Query Language)์„ ์•Œ์•„์•ผ ํ•ด. SQL์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ณต์šฉ์–ด ๊ฐ™์€ ๊ฑฐ์•ผ! ๐Ÿ—ฃ๏ธ

๐Ÿ“ ๊ธฐ๋ณธ SQL ๋ช…๋ น์–ด

1. SELECT - ๋ฐ์ดํ„ฐ ์กฐํšŒ

๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ช…๋ น์–ด์•ผ. ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์˜ฌ ๋•Œ ์‚ฌ์šฉํ•ด.

-- ๋ชจ๋“  ์ปฌ๋Ÿผ ์กฐํšŒ
SELECT * FROM users;

-- ํŠน์ • ์ปฌ๋Ÿผ๋งŒ ์กฐํšŒ
SELECT name, email FROM users;

-- ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•œ ์กฐํšŒ
SELECT * FROM users WHERE age >= 20;

-- ์ •๋ ฌํ•ด์„œ ์กฐํšŒ
SELECT * FROM users ORDER BY created_at DESC;

-- ๊ฐœ์ˆ˜ ์ œํ•œ
SELECT * FROM users LIMIT 10;

-- ์—ฌ๋Ÿฌ ์กฐ๊ฑด ์กฐํ•ฉ
SELECT name, email 
FROM users 
WHERE age >= 20 AND status = 'active'
ORDER BY name ASC
LIMIT 50;
2. INSERT - ๋ฐ์ดํ„ฐ ์‚ฝ์ž…

์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉํ•ด.

-- ๋‹จ์ผ ํ–‰ ์‚ฝ์ž…
INSERT INTO users (name, email, age) 
VALUES ('ํ™๊ธธ๋™', 'hong@example.com', 25);

-- ์—ฌ๋Ÿฌ ํ–‰ ๋™์‹œ ์‚ฝ์ž…
INSERT INTO users (name, email, age) 
VALUES 
    ('๊น€์ฒ ์ˆ˜', 'kim@example.com', 30),
    ('์ด์˜ํฌ', 'lee@example.com', 28),
    ('๋ฐ•๋ฏผ์ˆ˜', 'park@example.com', 35);
3. UPDATE - ๋ฐ์ดํ„ฐ ์ˆ˜์ •

๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•ด. WHERE ์ ˆ์„ ๊ผญ ์จ์•ผ ํ•ด! ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ์–ด๋ฒ„๋ ค! ๐Ÿ˜ฑ

-- ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ ๋ณ€๊ฒฝ
UPDATE users 
SET email = 'newemail@example.com' 
WHERE id = 1;

-- ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ ๋™์‹œ ์ˆ˜์ •
UPDATE users 
SET age = 26, status = 'active' 
WHERE name = 'ํ™๊ธธ๋™';

-- ์กฐ๊ฑด์— ๋งž๋Š” ์—ฌ๋Ÿฌ ํ–‰ ์ˆ˜์ •
UPDATE users 
SET status = 'inactive' 
WHERE last_login < '2023-01-01';
4. DELETE - ๋ฐ์ดํ„ฐ ์‚ญ์ œ

๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์‚ฌ์šฉํ•ด. ์ด๊ฒƒ๋„ WHERE ์ ˆ ํ•„์ˆ˜! โš ๏ธ

-- ํŠน์ • ์‚ฌ์šฉ์ž ์‚ญ์ œ
DELETE FROM users WHERE id = 1;

-- ์กฐ๊ฑด์— ๋งž๋Š” ์—ฌ๋Ÿฌ ํ–‰ ์‚ญ์ œ
DELETE FROM users WHERE status = 'deleted';

-- ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ (์œ„ํ—˜!)
DELETE FROM users;

๐Ÿ”— JOIN - ํ…Œ์ด๋ธ” ๊ฒฐํ•ฉ

์‹ค๋ฌด์—์„œ๋Š” ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ์ด๋•Œ JOIN์„ ์‚ฌ์šฉํ•ด!

JOIN์˜ ์ข…๋ฅ˜ โ€ข INNER JOIN: ์–‘์ชฝ ํ…Œ์ด๋ธ”์— ๋ชจ๋‘ ์กด์žฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒ
โ€ข LEFT JOIN: ์™ผ์ชฝ ํ…Œ์ด๋ธ”์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ์™€ ๋งค์นญ๋˜๋Š” ์˜ค๋ฅธ์ชฝ ๋ฐ์ดํ„ฐ ์กฐํšŒ
โ€ข RIGHT JOIN: ์˜ค๋ฅธ์ชฝ ํ…Œ์ด๋ธ”์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ์™€ ๋งค์นญ๋˜๋Š” ์™ผ์ชฝ ๋ฐ์ดํ„ฐ ์กฐํšŒ
โ€ข FULL OUTER JOIN: ์–‘์ชฝ ํ…Œ์ด๋ธ”์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์กฐํšŒ
-- INNER JOIN ์˜ˆ์‹œ
-- ์‚ฌ์šฉ์ž์™€ ๊ทธ๋“ค์˜ ์ฃผ๋ฌธ ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์กฐํšŒ
SELECT users.name, orders.order_date, orders.total_amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;

-- LEFT JOIN ์˜ˆ์‹œ
-- ๋ชจ๋“  ์‚ฌ์šฉ์ž์™€ ์ฃผ๋ฌธ ์ •๋ณด ์กฐํšŒ (์ฃผ๋ฌธ์ด ์—†๋Š” ์‚ฌ์šฉ์ž๋„ ํฌํ•จ)
SELECT users.name, orders.order_date
FROM users
LEFT JOIN orders ON users.id = orders.user_id;

-- ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” JOIN
SELECT 
    users.name,
    orders.order_date,
    products.product_name,
    order_items.quantity
FROM users
INNER JOIN orders ON users.id = orders.user_id
INNER JOIN order_items ON orders.id = order_items.order_id
INNER JOIN products ON order_items.product_id = products.id;

๐Ÿ“Š ์ง‘๊ณ„ ํ•จ์ˆ˜

๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•  ๋•Œ ์œ ์šฉํ•œ ์ง‘๊ณ„ ํ•จ์ˆ˜๋“ค์ด์•ผ.

-- COUNT: ๊ฐœ์ˆ˜ ์„ธ๊ธฐ
SELECT COUNT(*) FROM users;
SELECT COUNT(DISTINCT email) FROM users;

-- SUM: ํ•ฉ๊ณ„
SELECT SUM(total_amount) FROM orders;

-- AVG: ํ‰๊ท 
SELECT AVG(age) FROM users;

-- MAX, MIN: ์ตœ๋Œ€๊ฐ’, ์ตœ์†Œ๊ฐ’
SELECT MAX(total_amount), MIN(total_amount) FROM orders;

-- GROUP BY: ๊ทธ๋ฃน๋ณ„ ์ง‘๊ณ„
SELECT status, COUNT(*) as count
FROM users
GROUP BY status;

-- HAVING: ๊ทธ๋ฃน ์กฐ๊ฑด
SELECT status, COUNT(*) as count
FROM users
GROUP BY status
HAVING count > 10;
โšก SQL ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ โ€ข ์ธ๋ฑ์Šค ํ™œ์šฉ: ์ž์ฃผ ๊ฒ€์ƒ‰ํ•˜๋Š” ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค ์ƒ์„ฑ
โ€ข SELECT * ์ง€์–‘: ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ์„ ํƒ
โ€ข WHERE ์ ˆ ์ตœ์ ํ™”: ์ธ๋ฑ์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์กฐ๊ฑด ์ž‘์„ฑ
โ€ข LIMIT ์‚ฌ์šฉ: ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
โ€ข ์„œ๋ธŒ์ฟผ๋ฆฌ๋ณด๋‹ค JOIN: ๊ฐ€๋Šฅํ•˜๋ฉด JOIN ์‚ฌ์šฉ์ด ๋” ํšจ์œจ์ 
โ€ข ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ณ„ํš ํ™•์ธ: EXPLAIN ๋ช…๋ น์–ด๋กœ ์ฟผ๋ฆฌ ๋ถ„์„
SQL ๋ช…๋ น์–ด ํ๋ฆ„๋„ SELECT ๋ฐ์ดํ„ฐ ์กฐํšŒ INSERT ๋ฐ์ดํ„ฐ ์‚ฝ์ž… UPDATE ๋ฐ์ดํ„ฐ ์ˆ˜์ • DELETE ๋ฐ์ดํ„ฐ ์‚ญ์ œ JOIN ํ…Œ์ด๋ธ” ๊ฒฐํ•ฉ GROUP BY ๊ทธ๋ฃน ์ง‘๊ณ„ Database ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ

๐ŸŽจ ORM์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

์ž, ์ด์ œ ์ด ๊ธ€์˜ ํ•ต์‹ฌ ์ฃผ์ œ์ธ ORM์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ ์‹œ๊ฐ„์ด์•ผ! ๐ŸŽฏ

ORM(Object-Relational Mapping)์€ ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์™€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์ด์˜ ๋‹ค๋ฆฌ ์—ญํ• ์„ ํ•ด. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, SQL์„ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ธฐ์ˆ ์ด์•ผ! ๐ŸŒ‰

๐Ÿค” ์™œ ORM์„ ์‚ฌ์šฉํ• ๊นŒ?

SQL์„ ์ง์ ‘ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๋„ ๋‚˜์˜์ง€ ์•Š์€๋ฐ, ์™œ ORM์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑธ๊นŒ? ์—ฌ๋Ÿฌ ์ด์œ ๊ฐ€ ์žˆ์–ด:

1. ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ โšก ๋ณต์žกํ•œ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ , ์ต์ˆ™ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ๋ฌธ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์–ด. ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ํ›จ์”ฌ ๋นจ๋ผ์ง€์ง€!

2. ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด ๐Ÿ”ง
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด๋„ ORM ๋ชจ๋ธ๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋ผ. SQL ์ฟผ๋ฆฌ๋ฅผ ์ผ์ผ์ด ์ฐพ์•„์„œ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์–ด.

3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋…๋ฆฝ์„ฑ ๐Ÿ”„
MySQL์—์„œ PostgreSQL๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋ณ€๊ฒฝํ•ด๋„ ์ฝ”๋“œ๋ฅผ ๊ฑฐ์˜ ์ˆ˜์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ผ. ORM์ด ์•Œ์•„์„œ ์ ์ ˆํ•œ SQL์„ ์ƒ์„ฑํ•ด์ฃผ๊ฑฐ๋“ !

4. ๊ฐ์ฒด ์ง€ํ–ฅ์  ์ ‘๊ทผ ๐ŸŽฏ
๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ด๋ธ”์ด ์•„๋‹Œ ๊ฐ์ฒด๋กœ ๋‹ค๋ฃจ๋‹ˆ๊นŒ ๋” ์ง๊ด€์ ์ด๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›Œ.

5. SQL ์ธ์ ์…˜ ๋ฐฉ์–ด ๐Ÿ›ก๏ธ
ORM์ด ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด์„œ SQL ์ธ์ ์…˜ ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•  ์ˆ˜ ์žˆ์–ด.

โš–๏ธ ORM์˜ ์žฅ๋‹จ์ 

๋ฌผ๋ก  ORM๋„ ๋งŒ๋Šฅ์€ ์•„๋‹ˆ์•ผ. ์žฅ๋‹จ์ ์„ ์ •ํ™•ํžˆ ์•Œ๊ณ  ์‚ฌ์šฉํ•ด์•ผ ํ•ด!

โœ… ์žฅ์  โ€ข ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ๋น ๋ฆ„
โ€ข ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์Œ
โ€ข ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ์ด ์šฉ์ด
โ€ข ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ ๊ฐ€๋Šฅ
โ€ข ํƒ€์ž… ์•ˆ์ •์„ฑ ์ œ๊ณต (์ปดํŒŒ์ผ ํƒ€์ž„ ์—๋Ÿฌ ์ฒดํฌ)
โ€ข ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง€์›

โŒ ๋‹จ์ 

โ€ข ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋Š” ์˜คํžˆ๋ ค ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Œ
โ€ข ์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
โ€ข ORM ์ž์ฒด๋ฅผ ํ•™์Šตํ•ด์•ผ ํ•จ
โ€ข ์ƒ์„ฑ๋œ SQL์„ ์ œ์–ดํ•˜๊ธฐ ์–ด๋ ค์›€
โ€ข ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์‹œ ๋น„ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์Œ
๐Ÿ’ก ์‹ค๋ฌด ํŒ ORM๊ณผ Raw SQL์„ ์ ์ ˆํžˆ ํ˜ผ์šฉํ•˜๋Š” ๊ฒŒ ๋ฒ ์ŠคํŠธ์•ผ!

โ€ข ์ผ๋ฐ˜์ ์ธ CRUD: ORM ์‚ฌ์šฉ
โ€ข ๋ณต์žกํ•œ ์ง‘๊ณ„/ํ†ต๊ณ„: Raw SQL ์‚ฌ์šฉ
โ€ข ๋Œ€์šฉ๋Ÿ‰ ๋ฐฐ์น˜ ์ž‘์—…: Raw SQL ๋˜๋Š” ๋ฒŒํฌ ์—ฐ์‚ฐ
โ€ข ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ๋ถ€๋ถ„: ์ฟผ๋ฆฌ ์ตœ์ ํ™” ํ›„ Raw SQL ๊ณ ๋ ค

๐ŸŒŸ ์ฃผ์š” ORM ํ”„๋ ˆ์ž„์›Œํฌ

๊ฐ ์–ธ์–ด๋งˆ๋‹ค ์ธ๊ธฐ ์žˆ๋Š” ORM ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์žˆ์–ด. ๋Œ€ํ‘œ์ ์ธ ๊ฒƒ๋“ค์„ ์‚ดํŽด๋ณผ๊ฒŒ!

๐Ÿ Python

1. Django ORM โ€ข Django ํ”„๋ ˆ์ž„์›Œํฌ์— ๋‚ด์žฅ
โ€ข ๊ฐ•๋ ฅํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์›€
โ€ข ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง€์›
โ€ข Admin ํŒจ๋„ ์ž๋™ ์ƒ์„ฑ

2. SQLAlchemy
โ€ข ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•œ Python ORM
โ€ข Core์™€ ORM ๋‘ ๊ฐ€์ง€ ๋ ˆ๋ฒจ ์ œ๊ณต
โ€ข ๋ณต์žกํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ ๊ฐ€๋Šฅ
โ€ข Flask, FastAPI ๋“ฑ๊ณผ ์ž˜ ์–ด์šธ๋ฆผ

3. Peewee
โ€ข ๊ฒฝ๋Ÿ‰ ORM
โ€ข ๊ฐ„๋‹จํ•œ API
โ€ข ์ž‘์€ ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉ
โ˜• Java

1. Hibernate โ€ข ๊ฐ€์žฅ ์œ ๋ช…ํ•œ Java ORM
โ€ข JPA(Java Persistence API) ๊ตฌํ˜„์ฒด
โ€ข ๊ฐ•๋ ฅํ•œ ์บ์‹ฑ ๊ธฐ๋Šฅ
โ€ข ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉ

2. MyBatis
โ€ข SQL ๋งคํผ ํ”„๋ ˆ์ž„์›Œํฌ
โ€ข SQL์„ ์ง์ ‘ ์ž‘์„ฑํ•˜๋˜ ๊ฐ์ฒด ๋งคํ•‘ ์ž๋™ํ™”
โ€ข ๋ณต์žกํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ์— ์œ ๋ฆฌ

3. JPA (Spring Data JPA)
โ€ข Spring Boot์™€ ์™„๋ฒฝํ•œ ํ†ตํ•ฉ
โ€ข Repository ํŒจํ„ด ์ง€์›
โ€ข ๋ฉ”์„œ๋“œ ์ด๋ฆ„์œผ๋กœ ์ฟผ๋ฆฌ ์ž๋™ ์ƒ์„ฑ
๐ŸŸจ JavaScript/TypeScript

1. Sequelize โ€ข Node.js์˜ ๋Œ€ํ‘œ์ ์ธ ORM
โ€ข Promise ๊ธฐ๋ฐ˜
โ€ข ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง€์›
โ€ข ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ ํฌํ•จ

2. TypeORM
โ€ข TypeScript์™€ ์™„๋ฒฝํ•œ ํ˜ธํ™˜
โ€ข ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ๋ฌธ๋ฒ•
โ€ข Active Record์™€ Data Mapper ํŒจํ„ด ์ง€์›
โ€ข NestJS์™€ ์ž˜ ์–ด์šธ๋ฆผ

3. Prisma
โ€ข ์ฐจ์„ธ๋Œ€ ORM
โ€ข ํƒ€์ž… ์•ˆ์ •์„ฑ์ด ๋›ฐ์–ด๋‚จ
โ€ข ์ง๊ด€์ ์ธ ์Šคํ‚ค๋งˆ ์ •์˜
โ€ข ์ž๋™ ์™„์„ฑ ์ง€์›์ด ํ›Œ๋ฅญํ•จ
๐Ÿ’Ž Ruby

Active Record โ€ข Ruby on Rails์˜ ๊ธฐ๋ณธ ORM
โ€ข Convention over Configuration
โ€ข ๋งค์šฐ ์ง๊ด€์ ์ธ API
โ€ข ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ ๋‚ด์žฅ
๐Ÿ˜ PHP

1. Eloquent โ€ข Laravel ํ”„๋ ˆ์ž„์›Œํฌ์˜ ORM
โ€ข ์šฐ์•„ํ•œ ๋ฌธ๋ฒ•
โ€ข ๊ด€๊ณ„ ์ •์˜๊ฐ€ ์‰ฌ์›€

2. Doctrine
โ€ข Symfony์˜ ๊ธฐ๋ณธ ORM
โ€ข ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•จ
โ€ข ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ๊ธฐ๋Šฅ

๐Ÿš€ ORM ์‹ค์ „ ํ™œ์šฉ - Django ORM

์ด๋ก ๋งŒ ๋ณด๋ฉด ์žฌ๋ฏธ์—†์œผ๋‹ˆ๊นŒ, ์‹ค์ œ๋กœ Django ORM์„ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณผ๊ฒŒ! Django๋Š” ์›น ๊ฐœ๋ฐœ์—์„œ ์ •๋ง ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๊ฑฐ๋“ . ๐ŸŽจ

๐Ÿ“‹ ๋ชจ๋ธ ์ •์˜ํ•˜๊ธฐ

Django์—์„œ๋Š” Python ํด๋ž˜์Šค๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ •์˜ํ•ด. ์ด๊ฑธ ๋ชจ๋ธ(Model)์ด๋ผ๊ณ  ๋ถˆ๋Ÿฌ.

from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    """์นดํ…Œ๊ณ ๋ฆฌ ๋ชจ๋ธ"""
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name_plural = "Categories"
        ordering = ['name']
    
    def __str__(self):
        return self.name

class Post(models.Model):
    """๊ฒŒ์‹œ๊ธ€ ๋ชจ๋ธ"""
    STATUS_CHOICES = [
        ('draft', '์ž„์‹œ์ €์žฅ'),
        ('published', '๋ฐœํ–‰๋จ'),
        ('archived', '๋ณด๊ด€๋จ'),
    ]
    
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts')
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    views = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['status', '-created_at']),
        ]
    
    def __str__(self):
        return self.title

class Comment(models.Model):
    """๋Œ“๊ธ€ ๋ชจ๋ธ"""
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ['created_at']
    
    def __str__(self):
        return f'{self.author.username}: {self.content[:50]}'
์ด๋ ‡๊ฒŒ ๋ชจ๋ธ์„ ์ •์˜ํ•˜๋ฉด, Django๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ด์ค˜! ๐Ÿ˜Š

๐Ÿ”„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

๋ชจ๋ธ์„ ์ •์˜ํ–ˆ์œผ๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์‹ค์ œ๋กœ ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค์–ด์•ผ ํ•ด. ์ด ๊ณผ์ •์„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด๋ผ๊ณ  ํ•ด.

# ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ ์ƒ์„ฑ
python manage.py makemigrations

# ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰ (ํ…Œ์ด๋ธ” ์ƒ์„ฑ)
python manage.py migrate

โœ๏ธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ (Create)

# ๋ฐฉ๋ฒ• 1: ๊ฐ์ฒด ์ƒ์„ฑ ํ›„ ์ €์žฅ
post = Post(
    title='Django ORM ์™„๋ฒฝ ๊ฐ€์ด๋“œ',
    content='ORM์„ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ํŽธ๋ฆฌํ•ด์š”!',
    author=user,
    category=category,
    status='published'
)
post.save()

# ๋ฐฉ๋ฒ• 2: create() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ (ํ•œ ๋ฒˆ์— ์ƒ์„ฑํ•˜๊ณ  ์ €์žฅ)
post = Post.objects.create(
    title='Python ์›น ๊ฐœ๋ฐœ',
    content='Django๋กœ ์›น์‚ฌ์ดํŠธ ๋งŒ๋“ค๊ธฐ',
    author=user,
    category=category
)

# ์—ฌ๋Ÿฌ ๊ฐœ ํ•œ ๋ฒˆ์— ์ƒ์„ฑ
Post.objects.bulk_create([
    Post(title='์ฒซ ๋ฒˆ์งธ ๊ธ€', content='๋‚ด์šฉ1', author=user),
    Post(title='๋‘ ๋ฒˆ์งธ ๊ธ€', content='๋‚ด์šฉ2', author=user),
    Post(title='์„ธ ๋ฒˆ์งธ ๊ธ€', content='๋‚ด์šฉ3', author=user),
])

๐Ÿ” ๋ฐ์ดํ„ฐ ์กฐํšŒ (Read)

Django ORM์˜ ์ง„๊ฐ€๋Š” ๋ฐ์ดํ„ฐ ์กฐํšŒ์—์„œ ๋‚˜ํƒ€๋‚˜! ์ •๋ง ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด.

# ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ๊ฐ€์ ธ์˜ค๊ธฐ
all_posts = Post.objects.all()

# ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ํ•„ํ„ฐ๋ง
published_posts = Post.objects.filter(status='published')

# ์—ฌ๋Ÿฌ ์กฐ๊ฑด ์กฐํ•ฉ
recent_posts = Post.objects.filter(
    status='published',
    created_at__gte='2024-01-01'
).order_by('-created_at')

# ๋‹จ์ผ ๊ฐ์ฒด ๊ฐ€์ ธ์˜ค๊ธฐ
post = Post.objects.get(id=1)

# ์กด์žฌํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ
try:
    post = Post.objects.get(id=999)
except Post.DoesNotExist:
    post = None

# ๋˜๋Š” get_or_404 ์‚ฌ์šฉ (Django shortcuts)
from django.shortcuts import get_object_or_404
post = get_object_or_404(Post, id=1)

# ์ฒซ ๋ฒˆ์งธ/๋งˆ์ง€๋ง‰ ๊ฐ์ฒด
first_post = Post.objects.first()
last_post = Post.objects.last()

# ๊ฐœ์ˆ˜ ์ œํ•œ
latest_5_posts = Post.objects.all()[:5]

# ํŠน์ • ํ•„๋“œ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ (์„ฑ๋Šฅ ์ตœ์ ํ™”)
posts = Post.objects.values('id', 'title', 'created_at')

# ์ค‘๋ณต ์ œ๊ฑฐ
categories = Post.objects.values('category').distinct()

# ๊ฐœ์ˆ˜ ์„ธ๊ธฐ
total_posts = Post.objects.count()
published_count = Post.objects.filter(status='published').count()

# ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
has_posts = Post.objects.filter(author=user).exists()

๐Ÿ”Ž ๊ณ ๊ธ‰ ์ฟผ๋ฆฌ

# Q ๊ฐ์ฒด๋กœ ๋ณต์žกํ•œ ์กฐ๊ฑด ๋งŒ๋“ค๊ธฐ
from django.db.models import Q

# OR ์กฐ๊ฑด
posts = Post.objects.filter(
    Q(status='published') | Q(status='archived')
)

# AND์™€ OR ์กฐํ•ฉ
posts = Post.objects.filter(
    Q(status='published') & (Q(title__contains='Django') | Q(content__contains='Django'))
)

# NOT ์กฐ๊ฑด
posts = Post.objects.filter(~Q(status='draft'))

# ๊ด€๊ณ„๋ฅผ ํ†ตํ•œ ์กฐํšŒ (JOIN)
# ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฒŒ์‹œ๊ธ€
posts = Post.objects.filter(category__name='Python')

# ํŠน์ • ์ž‘์„ฑ์ž์˜ ๊ฒŒ์‹œ๊ธ€
posts = Post.objects.filter(author__username='john')

# ๋Œ“๊ธ€์ด ์žˆ๋Š” ๊ฒŒ์‹œ๊ธ€
posts = Post.objects.filter(comments__isnull=False).distinct()

# ์—ญ์ฐธ์กฐ (related_name ์‚ฌ์šฉ)
user_posts = user.posts.all()
category_posts = category.posts.filter(status='published')

# ์ง‘๊ณ„ ํ•จ์ˆ˜
from django.db.models import Count, Avg, Sum, Max, Min

# ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜
categories = Category.objects.annotate(post_count=Count('posts'))

# ํ‰๊ท  ์กฐํšŒ์ˆ˜
avg_views = Post.objects.aggregate(Avg('views'))

# ์ž‘์„ฑ์ž๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜
authors = User.objects.annotate(
    post_count=Count('posts'),
    total_views=Sum('posts__views')
)

# ์กฐ๊ฑด๋ถ€ ์ง‘๊ณ„
from django.db.models import Case, When, IntegerField

posts = Post.objects.annotate(
    comment_count=Count('comments'),
    is_popular=Case(
        When(views__gte=1000, then=1),
        default=0,
        output_field=IntegerField()
    )
)

โœ๏ธ ๋ฐ์ดํ„ฐ ์ˆ˜์ • (Update)

# ๋ฐฉ๋ฒ• 1: ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์™€์„œ ์ˆ˜์ •
post = Post.objects.get(id=1)
post.title = '์ˆ˜์ •๋œ ์ œ๋ชฉ'
post.content = '์ˆ˜์ •๋œ ๋‚ด์šฉ'
post.save()

# ๋ฐฉ๋ฒ• 2: update() ๋ฉ”์„œ๋“œ (๋” ํšจ์œจ์ )
Post.objects.filter(id=1).update(
    title='์ˆ˜์ •๋œ ์ œ๋ชฉ',
    content='์ˆ˜์ •๋œ ๋‚ด์šฉ'
)

# ์—ฌ๋Ÿฌ ๊ฐ์ฒด ํ•œ ๋ฒˆ์— ์ˆ˜์ •
Post.objects.filter(status='draft').update(status='published')

# ์กฐํšŒ์ˆ˜ ์ฆ๊ฐ€
post = Post.objects.get(id=1)
post.views += 1
post.save()

# ๋˜๋Š” F ๊ฐ์ฒด ์‚ฌ์šฉ (๊ฒฝ์Ÿ ์กฐ๊ฑด ๋ฐฉ์ง€)
from django.db.models import F
Post.objects.filter(id=1).update(views=F('views') + 1)

# ํŠน์ • ํ•„๋“œ๋งŒ ์ €์žฅ (์„ฑ๋Šฅ ์ตœ์ ํ™”)
post.title = '์ƒˆ ์ œ๋ชฉ'
post.save(update_fields=['title'])

๐Ÿ—‘๏ธ ๋ฐ์ดํ„ฐ ์‚ญ์ œ (Delete)

# ๋‹จ์ผ ๊ฐ์ฒด ์‚ญ์ œ
post = Post.objects.get(id=1)
post.delete()

# ์กฐ๊ฑด์— ๋งž๋Š” ์—ฌ๋Ÿฌ ๊ฐ์ฒด ์‚ญ์ œ
Post.objects.filter(status='draft', created_at__lt='2023-01-01').delete()

# ๋ชจ๋“  ๊ฐ์ฒด ์‚ญ์ œ (์ฃผ์˜!)
Post.objects.all().delete()
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ โ€ข N+1 ๋ฌธ์ œ: ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ ๊ด€๊ณ„๋œ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•˜๋ฉด ์ฟผ๋ฆฌ๊ฐ€ ๋งŽ์ด ๋ฐœ์ƒํ•ด. select_related()๋‚˜ prefetch_related()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐ!

โ€ข ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ: bulk_create(), bulk_update() ์‚ฌ์šฉ

โ€ข ํŠธ๋žœ์žญ์…˜: ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์›์ž์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ๋Š” transaction.atomic() ์‚ฌ์šฉ

๐ŸŽฏ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

# select_related: ForeignKey, OneToOneField ๊ด€๊ณ„ (JOIN ์‚ฌ์šฉ)
posts = Post.objects.select_related('author', 'category').all()

# prefetch_related: ManyToManyField, ์—ญ์ฐธ์กฐ ๊ด€๊ณ„ (๋ณ„๋„ ์ฟผ๋ฆฌ)
posts = Post.objects.prefetch_related('comments').all()

# ๋‘˜ ๋‹ค ์‚ฌ์šฉ
posts = Post.objects.select_related('author', 'category').prefetch_related('comments')

# only: ํŠน์ • ํ•„๋“œ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ
posts = Post.objects.only('id', 'title', 'created_at')

# defer: ํŠน์ • ํ•„๋“œ ์ œ์™ธํ•˜๊ณ  ๊ฐ€์ ธ์˜ค๊ธฐ
posts = Post.objects.defer('content')

# iterator: ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ (๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ)
for post in Post.objects.iterator(chunk_size=1000):
    # ์ฒ˜๋ฆฌ ๋กœ์ง
    pass

# ์ฟผ๋ฆฌ ํ™•์ธ (๋””๋ฒ„๊น…)
print(Post.objects.filter(status='published').query)
ORM ์•„ํ‚คํ…์ฒ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์–ด Python / Java / JavaScript ์ฝ”๋“œ ๊ฐ์ฒด ์กฐ์ž‘ ORM ๋ ˆ์ด์–ด Django ORM / SQLAlchemy / Hibernate ๊ฐ์ฒด โ†” SQL ๋ณ€ํ™˜ SQL ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ ˆ์ด์–ด MySQL / PostgreSQL / Oracle ์žฅ์ : ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ, ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด ์ฃผ์˜: ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋Š” ์„ฑ๋Šฅ ๊ณ ๋ ค

๐Ÿ’Ž ORM ์‹ค์ „ ํ™œ์šฉ - SQLAlchemy

Django ORM ๋‹ค์Œ์œผ๋กœ, Python์—์„œ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ORM์ธ SQLAlchemy๋ฅผ ์‚ดํŽด๋ณผ๊ฒŒ! SQLAlchemy๋Š” Flask๋‚˜ FastAPI ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ•จ๊ป˜ ๋งŽ์ด ์‚ฌ์šฉ๋ผ. ๐Ÿ

๐Ÿ—๏ธ ๊ธฐ๋ณธ ์„ค์ •

from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from datetime import datetime

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—”์ง„ ์ƒ์„ฑ
engine = create_engine('mysql+pymysql://user:password@localhost/dbname', echo=True)

# Base ํด๋ž˜์Šค ์ƒ์„ฑ
Base = declarative_base()

# ์„ธ์…˜ ํŒฉํ† ๋ฆฌ ์ƒ์„ฑ
Session = sessionmaker(bind=engine)

๐Ÿ“‹ ๋ชจ๋ธ ์ •์˜

class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True)
    username = Column(String(50), unique=True, nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    
    # ๊ด€๊ณ„ ์ •์˜
    posts = relationship('Post', back_populates='author', cascade='all, delete-orphan')
    
    def __repr__(self):
        return f"<User(username='{self.username}')>"

class Post(Base):
    __tablename__ = 'posts'
    
    id = Column(Integer, primary_key=True)
    title = Column(String(200), nullable=False)
    content = Column(Text)
    author_id = Column(Integer, ForeignKey('users.id'))
    created_at = Column(DateTime, default=datetime.utcnow)
    
    # ๊ด€๊ณ„ ์ •์˜
    author = relationship('User', back_populates='posts')
    comments = relationship('Comment', back_populates='post', cascade='all, delete-orphan')
    
    def __repr__(self):
        return f"<Post(title='{self.title}')>"

class Comment(Base):
    __tablename__ = 'comments'
    
    id = Column(Integer, primary_key=True)
    content = Column(Text, nullable=False)
    post_id = Column(Integer, ForeignKey('posts.id'))
    user_id = Column(Integer, ForeignKey('users.id'))
    created_at = Column(DateTime, default=datetime.utcnow)
    
    # ๊ด€๊ณ„ ์ •์˜
    post = relationship('Post', back_populates='comments')
    user = relationship('User')
    
    def __repr__(self):
        return f"<Comment(content='{self.content[:20]}...')>"

# ํ…Œ์ด๋ธ” ์ƒ์„ฑ
Base.metadata.create_all(engine)

โœ๏ธ CRUD ์ž‘์—…

# ์„ธ์…˜ ์ƒ์„ฑ
session = Session()

# Create - ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
user = User(username='john_doe', email='john@example.com')
session.add(user)
session.commit()

# ์—ฌ๋Ÿฌ ๊ฐœ ์ถ”๊ฐ€
users = [
    User(username='alice', email='alice@example.com'),
    User(username='bob', email='bob@example.com')
]
session.add_all(users)
session.commit()

# Read - ๋ฐ์ดํ„ฐ ์กฐํšŒ
# ๋ชจ๋“  ์‚ฌ์šฉ์ž
all_users = session.query(User).all()

# ํŠน์ • ์กฐ๊ฑด
user = session.query(User).filter_by(username='john_doe').first()

# ๋ณต์žกํ•œ ์กฐ๊ฑด
from sqlalchemy import and_, or_

users = session.query(User).filter(
    and_(
        User.username.like('%john%'),
        User.created_at > '2024-01-01'
    )
).all()

# ์ •๋ ฌ
users = session.query(User).order_by(User.created_at.desc()).all()

# ์ œํ•œ
users = session.query(User).limit(10).all()

# ๊ฐœ์ˆ˜
user_count = session.query(User).count()

# Update - ๋ฐ์ดํ„ฐ ์ˆ˜์ •
user = session.query(User).filter_by(username='john_doe').first()
user.email = 'newemail@example.com'
session.commit()

# ๋˜๋Š” update() ์‚ฌ์šฉ
session.query(User).filter_by(username='john_doe').update({
    'email': 'newemail@example.com'
})
session.commit()

# Delete - ๋ฐ์ดํ„ฐ ์‚ญ์ œ
user = session.query(User).filter_by(username='john_doe').first()
session.delete(user)
session.commit()

# ๋˜๋Š” delete() ์‚ฌ์šฉ
session.query(User).filter_by(username='john_doe').delete()
session.commit()

# ์„ธ์…˜ ์ข…๋ฃŒ
session.close()

๐Ÿ”— ๊ด€๊ณ„ ํ™œ์šฉ

# ๊ฒŒ์‹œ๊ธ€๊ณผ ์ž‘์„ฑ์ž ํ•จ๊ป˜ ์กฐํšŒ (JOIN)
from sqlalchemy.orm import joinedload

posts = session.query(Post).options(
    joinedload(Post.author)
).all()

# ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€
user = session.query(User).filter_by(username='john_doe').first()
user_posts = user.posts

# ์—ญ์ฐธ์กฐ
post = session.query(Post).first()
author = post.author

# ์ค‘์ฒฉ ๊ด€๊ณ„
posts_with_comments = session.query(Post).options(
    joinedload(Post.author),
    joinedload(Post.comments)
).all()

๐Ÿ“Š ๊ณ ๊ธ‰ ์ฟผ๋ฆฌ

from sqlalchemy import func

# ์ง‘๊ณ„
# ์‚ฌ์šฉ์ž๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜
results = session.query(
    User.username,
    func.count(Post.id).label('post_count')
).join(Post).group_by(User.username).all()

# ํ‰๊ท , ์ตœ๋Œ€, ์ตœ์†Œ
avg_posts = session.query(func.avg(func.count(Post.id))).group_by(Post.author_id).scalar()

# ์„œ๋ธŒ์ฟผ๋ฆฌ
from sqlalchemy import select

subq = select([func.count(Post.id)]).where(Post.author_id == User.id).label('post_count')
users_with_count = session.query(User, subq).all()

# ์œˆ๋„์šฐ ํ•จ์ˆ˜
from sqlalchemy import over

ranked_posts = session.query(
    Post,
    func.row_number().over(
        partition_by=Post.author_id,
        order_by=Post.created_at.desc()
    ).label('rank')
).all()

๐ŸŒ Node.js ORM - Sequelize & Prisma

JavaScript/TypeScript ๊ฐœ๋ฐœ์ž๋“ค์„ ์œ„ํ•ด Node.js์˜ ์ธ๊ธฐ ORM๋“ค๋„ ์‚ดํŽด๋ณผ๊ฒŒ! ๐ŸŸจ

๐Ÿ”ท Sequelize

Sequelize๋Š” Node.js์—์„œ ๊ฐ€์žฅ ์˜ค๋ž˜๋˜๊ณ  ์„ฑ์ˆ™ํ•œ ORM์ด์•ผ.

const { Sequelize, DataTypes } = require('sequelize');

// ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ
const sequelize = new Sequelize('database', 'username', 'password', {
    host: 'localhost',
    dialect: 'mysql',
    logging: false
});

// ๋ชจ๋ธ ์ •์˜
const User = sequelize.define('User', {
    username: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true
    },
    email: {
        type: DataTypes.STRING,
        allowNull: false,
        unique: true,
        validate: {
            isEmail: true
        }
    },
    age: {
        type: DataTypes.INTEGER,
        validate: {
            min: 0,
            max: 150
        }
    }
}, {
    timestamps: true,
    tableName: 'users'
});

const Post = sequelize.define('Post', {
    title: {
        type: DataTypes.STRING,
        allowNull: false
    },
    content: {
        type: DataTypes.TEXT
    },
    status: {
        type: DataTypes.ENUM('draft', 'published', 'archived'),
        defaultValue: 'draft'
    }
});

// ๊ด€๊ณ„ ์ •์˜
User.hasMany(Post, { foreignKey: 'authorId', as: 'posts' });
Post.belongsTo(User, { foreignKey: 'authorId', as: 'author' });

// ๋™๊ธฐํ™”
await sequelize.sync();

// CRUD ์ž‘์—…
// Create
const user = await User.create({
    username: 'john_doe',
    email: 'john@example.com',
    age: 25
});

// Read
const users = await User.findAll({
    where: {
        age: {
            [Sequelize.Op.gte]: 20
        }
    },
    order: [['createdAt', 'DESC']],
    limit: 10
});

const user = await User.findOne({
    where: { username: 'john_doe' }
});

// ๊ด€๊ณ„ ํฌํ•จ
const userWithPosts = await User.findOne({
    where: { username: 'john_doe' },
    include: [{
        model: Post,
        as: 'posts'
    }]
});

// Update
await User.update(
    { email: 'newemail@example.com' },
    { where: { username: 'john_doe' } }
);

// Delete
await User.destroy({
    where: { username: 'john_doe' }
});

// ํŠธ๋žœ์žญ์…˜
await sequelize.transaction(async (t) => {
    const user = await User.create({
        username: 'alice',
        email: 'alice@example.com'
    }, { transaction: t });
    
    await Post.create({
        title: 'First Post',
        content: 'Hello World',
        authorId: user.id
    }, { transaction: t });
});

๐Ÿ”ถ Prisma

Prisma๋Š” ์ฐจ์„ธ๋Œ€ ORM์œผ๋กœ, ํƒ€์ž… ์•ˆ์ •์„ฑ๊ณผ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์ด ๋›ฐ์–ด๋‚˜! ๐Ÿš€

์Šคํ‚ค๋งˆ ์ •์˜ (schema.prisma)

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  username  String   @unique
  email     String   @unique
  age       Int?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]
  
  @@map("users")
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?  @db.Text
  status    Status   @default(DRAFT)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  comments  Comment[]
  
  @@index([authorId])
  @@map("posts")
}

model Comment {
  id        Int      @id @default(autoincrement())
  content   String   @db.Text
  postId    Int
  post      Post     @relation(fields: [postId], references: [id])
  userId    Int
  createdAt DateTime @default(now())
  
  @@map("comments")
}

enum Status {
  DRAFT
  PUBLISHED
  ARCHIVED
}
Prisma Client ์‚ฌ์šฉ

const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

// Create
const user = await prisma.user.create({
    data: {
        username: 'john_doe',
        email: 'john@example.com',
        age: 25
    }
});

// ๊ด€๊ณ„์™€ ํ•จ๊ป˜ ์ƒ์„ฑ
const userWithPost = await prisma.user.create({
    data: {
        username: 'alice',
        email: 'alice@example.com',
        posts: {
            create: [
                { title: 'First Post', content: 'Hello World' },
                { title: 'Second Post', content: 'Another post' }
            ]
        }
    },
    include: {
        posts: true
    }
});

// Read
const users = await prisma.user.findMany({
    where: {
        age: {
            gte: 20
        }
    },
    orderBy: {
        createdAt: 'desc'
    },
    take: 10
});

const user = await prisma.user.findUnique({
    where: { username: 'john_doe' }
});

// ๊ด€๊ณ„ ํฌํ•จ
const userWithPosts = await prisma.user.findUnique({
    where: { username: 'john_doe' },
    include: {
        posts: {
            where: {
                status: 'PUBLISHED'
            },
            orderBy: {
                createdAt: 'desc'
            }
        }
    }
});

// Update
const updatedUser = await prisma.user.update({
    where: { username: 'john_doe' },
    data: { email: 'newemail@example.com' }
});

// Delete
await prisma.user.delete({
    where: { username: 'john_doe' }
});

// ์ง‘๊ณ„
const userCount = await prisma.user.count();

const avgAge = await prisma.user.aggregate({
    _avg: {
        age: true
    }
});

// ๊ทธ๋ฃนํ™”
const postsByAuthor = await prisma.post.groupBy({
    by: ['authorId'],
    _count: {
        id: true
    },
    having: {
        authorId: {
            gt: 0
        }
    }
});

// ํŠธ๋žœ์žญ์…˜
await prisma.$transaction(async (tx) => {
    const user = await tx.user.create({
        data: {
            username: 'bob',
            email: 'bob@example.com'
        }
    });
    
    await tx.post.create({
        data: {
            title: 'Bob\'s Post',
            content: 'Content here',
            authorId: user.id
        }
    });
});

// Raw SQL (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
const result = await prisma.$queryRaw`
    SELECT * FROM users WHERE age > ${20}
`;

// ์—ฐ๊ฒฐ ์ข…๋ฃŒ
await prisma.$disconnect();
๐Ÿ’ก Prisma์˜ ์žฅ์  โ€ข ํƒ€์ž… ์•ˆ์ •์„ฑ: TypeScript์™€ ์™„๋ฒฝํ•œ ํ†ตํ•ฉ, ์ž๋™ ์™„์„ฑ ์ง€์›
โ€ข Prisma Studio: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค GUI ๋„๊ตฌ ์ œ๊ณต
โ€ข ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›€
โ€ข ์„ฑ๋Šฅ: ์ตœ์ ํ™”๋œ ์ฟผ๋ฆฌ ์ƒ์„ฑ
โ€ข ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜: ์ง๊ด€์ ์ธ API์™€ ํ›Œ๋ฅญํ•œ ๋ฌธ์„œ

๐ŸŽฏ ORM ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต

ORM์„ ์‚ฌ์šฉํ•˜๋ฉด ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. ์‹ค๋ฌด์—์„œ ๊ผญ ์•Œ์•„์•ผ ํ•  ์ตœ์ ํ™” ์ „๋žต๋“ค์„ ์•Œ๋ ค์ค„๊ฒŒ! โšก

๐ŸŒ N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ

N+1 ๋ฌธ์ œ๋Š” ORM์—์„œ ๊ฐ€์žฅ ํ”ํ•œ ์„ฑ๋Šฅ ๋ฌธ์ œ์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ:

โŒ ๋‚˜์œ ์˜ˆ์‹œ (N+1 ๋ฌธ์ œ ๋ฐœ์ƒ)

# Django
posts = Post.objects.all()  # 1๋ฒˆ์˜ ์ฟผ๋ฆฌ
for post in posts:
    print(post.author.name)  # N๋ฒˆ์˜ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ!

# ์ด 1 + N๋ฒˆ์˜ ์ฟผ๋ฆฌ ์‹คํ–‰
๊ฒŒ์‹œ๊ธ€์ด 100๊ฐœ๋ฉด 101๋ฒˆ์˜ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋ผ! ๐Ÿ˜ฑ
โœ… ์ข‹์€ ์˜ˆ์‹œ (ํ•ด๊ฒฐ)

# Django - select_related ์‚ฌ์šฉ
posts = Post.objects.select_related('author').all()  # 1๋ฒˆ์˜ JOIN ์ฟผ๋ฆฌ
for post in posts:
    print(post.author.name)  # ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ์—†์Œ!

# Sequelize - include ์‚ฌ์šฉ
const posts = await Post.findAll({
    include: [{ model: User, as: 'author' }]
});

# Prisma - include ์‚ฌ์šฉ
const posts = await prisma.post.findMany({
    include: { author: true }
});

๐Ÿ“Š ์ ์ ˆํ•œ ์ธ๋ฑ์Šค ์‚ฌ์šฉ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ๋ฑ์Šค๋Š” ๊ฒ€์ƒ‰ ์†๋„๋ฅผ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œ์ผœ!

# Django
class Post(models.Model):
    title = models.CharField(max_length=200)
    status = models.CharField(max_length=20)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        indexes = [
            models.Index(fields=['status']),
            models.Index(fields=['-created_at']),
            models.Index(fields=['status', '-created_at']),  # ๋ณตํ•ฉ ์ธ๋ฑ์Šค
        ]

# SQLAlchemy
from sqlalchemy import Index

class Post(Base):
    __tablename__ = 'posts'
    
    id = Column(Integer, primary_key=True)
    title = Column(String(200))
    status = Column(String(20), index=True)
    created_at = Column(DateTime, index=True)

# ๋ณตํ•ฉ ์ธ๋ฑ์Šค
Index('idx_status_created', Post.status, Post.created_at.desc())

# Prisma
model Post {
  id        Int      @id @default(autoincrement())
  title     String
  status    String
  createdAt DateTime @default(now())
  
  @@index([status])
  @@index([createdAt(sort: Desc)])
  @@index([status, createdAt(sort: Desc)])
}

๐ŸŽฏ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์กฐํšŒ

# Django - only()
posts = Post.objects.only('id', 'title', 'created_at')

# Django - values()
posts = Post.objects.values('id', 'title')

# Sequelize
const posts = await Post.findAll({
    attributes: ['id', 'title', 'createdAt']
});

# Prisma
const posts = await prisma.post.findMany({
    select: {
        id: true,
        title: true,
        createdAt: true
    }
});

๐Ÿ’พ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ์บ์‹ฑ

์ž์ฃผ ์กฐํšŒ๋˜์ง€๋งŒ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋Š” ์บ์‹ฑํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

# Django with Redis
from django.core.cache import cache

def get_popular_posts():
    cache_key = 'popular_posts'
    posts = cache.get(cache_key)
    
    if posts is None:
        posts = Post.objects.filter(
            status='published'
        ).order_by('-views')[:10]
        cache.set(cache_key, posts, 3600)  # 1์‹œ๊ฐ„ ์บ์‹ฑ
    
    return posts

# Node.js with Redis
const redis = require('redis');
const client = redis.createClient();

async function getPopularPosts() {
    const cacheKey = 'popular_posts';
    const cached = await client.get(cacheKey);
    
    if (cached) {
        return JSON.parse(cached);
    }
    
    const posts = await Post.findAll({
        where: { status: 'published' },
        order: [['views', 'DESC']],
        limit: 10
    });
    
    await client.setEx(cacheKey, 3600, JSON.stringify(posts));
    return posts;
}

๐Ÿ“ฆ ๋ฒŒํฌ ์—ฐ์‚ฐ ์‚ฌ์šฉ

์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” ๋ฒŒํฌ ์—ฐ์‚ฐ์„ ์‚ฌ์šฉํ•ด!

# Django
# ๋‚˜์œ ์˜ˆ
for data in large_dataset:
    Post.objects.create(**data)  # N๋ฒˆ์˜ INSERT

# ์ข‹์€ ์˜ˆ
posts = [Post(**data) for data in large_dataset]
Post.objects.bulk_create(posts, batch_size=1000)  # ๋ฐฐ์น˜๋กœ INSERT

# ์—…๋ฐ์ดํŠธ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€
Post.objects.filter(status='draft').update(status='published')

# Sequelize
await Post.bulkCreate(dataArray, {
    updateOnDuplicate: ['title', 'content']
});

# Prisma
await prisma.post.createMany({
    data: dataArray,
    skipDuplicates: true
});

๐Ÿ” ์ฟผ๋ฆฌ ๋ถ„์„ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

# Django - ์ฟผ๋ฆฌ ๋กœ๊น…
import logging
logging.basicConfig()
logging.getLogger('django.db.backends').setLevel(logging.DEBUG)

# Django Debug Toolbar ์‚ฌ์šฉ
# settings.py
INSTALLED_APPS = [
    ...
    'debug_toolbar',
]

# SQLAlchemy - ์ฟผ๋ฆฌ ์ถœ๋ ฅ
engine = create_engine('...', echo=True)

# Prisma - ์ฟผ๋ฆฌ ๋กœ๊น…
const prisma = new PrismaClient({
    log: ['query', 'info', 'warn', 'error'],
});

# ์‹คํ–‰ ๊ณ„ํš ํ™•์ธ
# Django
from django.db import connection
print(connection.queries)

# Raw SQL๋กœ EXPLAIN ์‹คํ–‰
cursor = connection.cursor()
cursor.execute('EXPLAIN SELECT * FROM posts WHERE status = %s', ['published'])
print(cursor.fetchall())
๐ŸŽฏ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ
  • โœ… N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ (select_related, prefetch_related)
  • โœ… ์ ์ ˆํ•œ ์ธ๋ฑ์Šค ์„ค์ •
  • โœ… ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์กฐํšŒ (only, values, select)
  • โœ… ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ์บ์‹ฑ
  • โœ… ๋ฒŒํฌ ์—ฐ์‚ฐ ์‚ฌ์šฉ
  • โœ… ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ตฌํ˜„
  • โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€ ์„ค์ •
  • โœ… ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ณ„ํš ๋ถ„์„
  • โœ… ๋А๋ฆฐ ์ฟผ๋ฆฌ ๋กœ๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง
  • โœ… ์ ์ ˆํ•œ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ์„ค์ •

๐Ÿ”’ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ๋ณด์•ˆ์ด ์ •๋ง ์ค‘์š”ํ•ด! ์‹ค๋ฌด์—์„œ ๊ผญ ์ง€์ผœ์•ผ ํ•  ๋ณด์•ˆ ์›์น™๋“ค์„ ์•Œ๋ ค์ค„๊ฒŒ. ๐Ÿ›ก๏ธ

๐Ÿ’‰ SQL ์ธ์ ์…˜ ๋ฐฉ์–ด

SQL ์ธ์ ์…˜์€ ๊ฐ€์žฅ ์œ„ํ—˜ํ•œ ๋ณด์•ˆ ์ทจ์•ฝ์  ์ค‘ ํ•˜๋‚˜์•ผ. ORM์„ ์‚ฌ์šฉํ•˜๋ฉด ๋Œ€๋ถ€๋ถ„ ์ž๋™์œผ๋กœ ๋ฐฉ์–ด๋˜์ง€๋งŒ, Raw SQL์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ฃผ์˜ํ•ด์•ผ ํ•ด!

โŒ ์œ„ํ—˜ํ•œ ์ฝ”๋“œ

# Django - ์ ˆ๋Œ€ ์ด๋ ‡๊ฒŒ ํ•˜์ง€ ๋งˆ!
username = request.GET.get('username')
query = f"SELECT * FROM users WHERE username = '{username}'"
User.objects.raw(query)

# ๊ณต๊ฒฉ์ž๊ฐ€ username์— "admin' OR '1'='1" ์ž…๋ ฅํ•˜๋ฉด?
# SELECT * FROM users WHERE username = 'admin' OR '1'='1'
# ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋จ!
โœ… ์•ˆ์ „ํ•œ ์ฝ”๋“œ

# Django - ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์‚ฌ์šฉ
username = request.GET.get('username')
User.objects.raw("SELECT * FROM users WHERE username = %s", [username])

# ๋˜๋Š” ORM ์‚ฌ์šฉ (๋” ์•ˆ์ „)
User.objects.filter(username=username)

# SQLAlchemy
username = request.args.get('username')
session.query(User).filter(User.username == username).all()

# Sequelize
const username = req.query.username;
await User.findAll({
    where: { username: username }
});

# Prisma
const username = req.query.username;
await prisma.user.findMany({
    where: { username: username }
});

๐Ÿ” ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”

๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ ˆ๋Œ€ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅํ•˜๋ฉด ์•ˆ ๋ผ!

# Django - ์ž๋™์œผ๋กœ ํ•ด์‹œ ์ฒ˜๋ฆฌ
from django.contrib.auth.models import User

user = User.objects.create_user(
    username='john',
    password='secret123'  # ์ž๋™์œผ๋กœ ํ•ด์‹œ๋จ
)

# ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
user.check_password('secret123')  # True

# Node.js - bcrypt ์‚ฌ์šฉ
const bcrypt = require('bcrypt');

// ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);

// ์‚ฌ์šฉ์ž ์ƒ์„ฑ
await User.create({
    username: 'john',
    password: hashedPassword
});

// ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
const isValid = await bcrypt.compare(inputPassword, user.password);

๐Ÿ”‘ ์ ‘๊ทผ ๊ถŒํ•œ ๊ด€๋ฆฌ

# Django - ๊ถŒํ•œ ์ฒดํฌ
from django.contrib.auth.decorators import login_required, permission_required

@login_required
def view_post(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    
    # ์ž‘์„ฑ์ž๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ
    if post.author != request.user:
        return HttpResponseForbidden()
    
    return render(request, 'post.html', {'post': post})

# ๋˜๋Š” Django Guardian ์‚ฌ์šฉ (๊ฐ์ฒด ๋ ˆ๋ฒจ ๊ถŒํ•œ)
from guardian.shortcuts import assign_perm, get_objects_for_user

# ๊ถŒํ•œ ๋ถ€์—ฌ
assign_perm('change_post', user, post)

# ๊ถŒํ•œ ํ™•์ธ
if user.has_perm('change_post', post):
    # ์ˆ˜์ • ํ—ˆ์šฉ
    pass

# Express.js - ๋ฏธ๋“ค์›จ์–ด๋กœ ๊ถŒํ•œ ์ฒดํฌ
const checkPostOwner = async (req, res, next) => {
    const post = await Post.findByPk(req.params.id);
    
    if (!post) {
        return res.status(404).json({ error: 'Post not found' });
    }
    
    if (post.authorId !== req.user.id) {
        return res.status(403).json({ error: 'Forbidden' });
    }
    
    req.post = post;
    next();
};

app.put('/posts/:id', authenticate, checkPostOwner, async (req, res) => {
    // ์ˆ˜์ • ๋กœ์ง
});

๐Ÿ”’ ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋ณดํ˜ธ

# Django - ํ•„๋“œ ์•”ํ˜ธํ™”
from django_cryptography.fields import encrypt

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    ssn = encrypt(models.CharField(max_length=11))  # ์ฃผ๋ฏผ๋ฒˆํ˜ธ ์•”ํ˜ธํ™”
    credit_card = encrypt(models.CharField(max_length=16))

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋ฏผ๊ฐ ์ •๋ณด ๊ด€๋ฆฌ
# .env ํŒŒ์ผ
DATABASE_URL=mysql://user:password@localhost/dbname
SECRET_KEY=your-secret-key-here
AWS_ACCESS_KEY=your-aws-key

# Python - python-dotenv
from dotenv import load_dotenv
import os

load_dotenv()

DATABASE_URL = os.getenv('DATABASE_URL')
SECRET_KEY = os.getenv('SECRET_KEY')

# Node.js - dotenv
require('dotenv').config();

const dbConfig = {
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD
};

๐Ÿšจ ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • โœ… SQL ์ธ์ ์…˜ ๋ฐฉ์–ด (ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์‚ฌ์šฉ)
  • โœ… ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ ์ฒ˜๋ฆฌ (bcrypt, Argon2 ๋“ฑ)
  • โœ… HTTPS ์‚ฌ์šฉ (์ „์†ก ์ค‘ ์•”ํ˜ธํ™”)
  • โœ… ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™”
  • โœ… ์ ‘๊ทผ ๊ถŒํ•œ ์ฒดํฌ
  • โœ… ์ž…๋ ฅ ๊ฐ’ ๊ฒ€์ฆ ๋ฐ sanitization
  • โœ… Rate limiting (๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ ๋ฐฉ์–ด)
  • โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด ๋ณดํ˜ธ
  • โœ… ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ (DB ์‚ฌ์šฉ์ž ๊ถŒํ•œ ์ œํ•œ)
  • โœ… ์ •๊ธฐ์ ์ธ ๋ณด์•ˆ ์—…๋ฐ์ดํŠธ
  • โœ… ๋กœ๊น… ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง
  • โœ… ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ ๊ณ„ํš

๐ŸŒŸ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์˜ˆ์‹œ

์ด๋ก ๋งŒ ๋ฐฐ์šฐ๋ฉด ์žฌ๋ฏธ์—†์œผ๋‹ˆ๊นŒ, ์‹ค์ œ๋กœ ๊ฐ„๋‹จํ•œ ๋ธ”๋กœ๊ทธ API๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ! Django REST Framework๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ์•ผ. ๐ŸŽจ

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

blog_project/
โ”œโ”€โ”€ manage.py
โ”œโ”€โ”€ blog/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ settings.py
โ”‚   โ”œโ”€โ”€ urls.py
โ”‚   โ””โ”€โ”€ wsgi.py
โ””โ”€โ”€ posts/
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ models.py
    โ”œโ”€โ”€ serializers.py
    โ”œโ”€โ”€ views.py
    โ”œโ”€โ”€ urls.py
    โ””โ”€โ”€ permissions.py

๐Ÿ“‹ ๋ชจ๋ธ ์ •์˜ (models.py)

from django.db import models
from django.contrib.auth.models import User
from django.utils.text import slugify

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(unique=True)
    description = models.TextField(blank=True)
    
    class Meta:
        verbose_name_plural = "Categories"
        ordering = ['name']
    
    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)
    
    def __str__(self):
        return self.name

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    slug = models.SlugField(unique=True)
    
    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)
    
    def __str__(self):
        return self.name

class Post(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('published', 'Published'),
        ('archived', 'Archived'),
    ]
    
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    content = models.TextField()
    excerpt = models.TextField(max_length=500, blank=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts')
    tags = models.ManyToManyField(Tag, related_name='posts', blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    views = models.IntegerField(default=0)
    featured_image = models.URLField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    published_at = models.DateTimeField(null=True, blank=True)
    
    class Meta:
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['status', '-published_at']),
            models.Index(fields=['author', '-created_at']),
        ]
    
    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)
        if not self.excerpt:
            self.excerpt = self.content[:200]
        super().save(*args, **kwargs)
    
    def __str__(self):
        return self.title

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='replies')
    is_approved = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ['created_at']
    
    def __str__(self):
        return f'{self.author.username}: {self.content[:50]}'

๐Ÿ”„ Serializers (serializers.py)

from rest_framework import serializers
from .models import Post, Category, Tag, Comment
from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email']

class CategorySerializer(serializers.ModelSerializer):
    post_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Category
        fields = ['id', 'name', 'slug', 'description', 'post_count']
    
    def get_post_count(self, obj):
        return obj.posts.filter(status='published').count()

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ['id', 'name', 'slug']

class CommentSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    replies = serializers.SerializerMethodField()
    
    class Meta:
        model = Comment
        fields = ['id', 'author', 'content', 'parent', 'replies', 'created_at']
    
    def get_replies(self, obj):
        if obj.replies.exists():
            return CommentSerializer(obj.replies.all(), many=True).data
        return []

class PostListSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    category = CategorySerializer(read_only=True)
    tags = TagSerializer(many=True, read_only=True)
    comment_count = serializers.SerializerMethodField()
    
    class Meta:
        model = Post
        fields = ['id', 'title', 'slug', 'excerpt', 'author', 'category', 
                  'tags', 'status', 'views', 'comment_count', 'created_at', 'published_at']
    
    def get_comment_count(self, obj):
        return obj.comments.filter(is_approved=True).count()

class PostDetailSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    category = CategorySerializer(read_only=True)
    tags = TagSerializer(many=True, read_only=True)
    comments = CommentSerializer(many=True, read_only=True)
    
    class Meta:
        model = Post
        fields = ['id', 'title', 'slug', 'content', 'excerpt', 'author', 
                  'category', 'tags', 'status', 'views', 'featured_image',
                  'comments', 'created_at', 'updated_at', 'published_at']

class PostCreateUpdateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['title', 'content', 'category', 'tags', 'status', 'featured_image']
    
    def create(self, validated_data):
        tags = validated_data.pop('tags', [])
        post = Post.objects.create(**validated_data)
        post.tags.set(tags)
        return post
    
    def update(self, instance, validated_data):
        tags = validated_data.pop('tags', None)
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        
        if tags is not None:
            instance.tags.set(tags)
        
        return instance

๐ŸŽฏ Views (views.py)

from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django.db.models import Q, Count
from .models import Post, Category, Tag, Comment
from .serializers import (
    PostListSerializer, PostDetailSerializer, PostCreateUpdateSerializer,
    CategorySerializer, TagSerializer, CommentSerializer
)
from .permissions import IsAuthorOrReadOnly

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.select_related('author', 'category').prefetch_related('tags', 'comments')
    permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['title', 'content']
    ordering_fields = ['created_at', 'views', 'published_at']
    
    def get_serializer_class(self):
        if self.action == 'list':
            return PostListSerializer
        elif self.action in ['create', 'update', 'partial_update']:
            return PostCreateUpdateSerializer
        return PostDetailSerializer
    
    def get_queryset(self):
        queryset = super().get_queryset()
        
        # ํ•„ํ„ฐ๋ง
        status_param = self.request.query_params.get('status')
        if status_param:
            queryset = queryset.filter(status=status_param)
        
        category = self.request.query_params.get('category')
        if category:
            queryset = queryset.filter(category__slug=category)
        
        tag = self.request.query_params.get('tag')
        if tag:
            queryset = queryset.filter(tags__slug=tag)
        
        # ๊ณต๊ฐœ๋œ ๊ฒŒ์‹œ๊ธ€๋งŒ (์ž‘์„ฑ์ž๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ)
        if not self.request.user.is_authenticated:
            queryset = queryset.filter(status='published')
        
        return queryset
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)
    
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        # ์กฐํšŒ์ˆ˜ ์ฆ๊ฐ€
        Post.objects.filter(pk=instance.pk).update(views=models.F('views') + 1)
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def popular(self, request):
        """์ธ๊ธฐ ๊ฒŒ์‹œ๊ธ€"""
        posts = self.get_queryset().order_by('-views')[:10]
        serializer = PostListSerializer(posts, many=True)
        return Response(serializer.data)
    
    @action(detail=False, methods=['get'])
    def trending(self, request):
        """์ตœ๊ทผ ์ธ๊ธฐ ๊ฒŒ์‹œ๊ธ€"""
        from datetime import timedelta
        from django.utils import timezone
        
        week_ago = timezone.now() - timedelta(days=7)
        posts = self.get_queryset().filter(
            created_at__gte=week_ago
        ).order_by('-views')[:10]
        
        serializer = PostListSerializer(posts, many=True)
        return Response(serializer.data)
    
    @action(detail=True, methods=['post'])
    def add_comment(self, request, pk=None):
        """๋Œ“๊ธ€ ์ถ”๊ฐ€"""
        post = self.get_object()
        serializer = CommentSerializer(data=request.data)
        
        if serializer.is_valid():
            serializer.save(post=post, author=request.user)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Category.objects.annotate(
        post_count=Count('posts', filter=Q(posts__status='published'))
    )
    serializer_class = CategorySerializer
    lookup_field = 'slug'

class TagViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    lookup_field = 'slug'

๐Ÿ” Permissions (permissions.py)

from rest_framework import permissions

class IsAuthorOrReadOnly(permissions.BasePermission):
    """
    ์ž‘์„ฑ์ž๋งŒ ์ˆ˜์ •/์‚ญ์ œ ๊ฐ€๋Šฅ, ๋‚˜๋จธ์ง€๋Š” ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ
    """
    def has_object_permission(self, request, view, obj):
        # ์ฝ๊ธฐ ๊ถŒํ•œ์€ ๋ชจ๋‘์—๊ฒŒ ํ—ˆ์šฉ
        if request.method in permissions.SAFE_METHODS:
            return True
        
        # ์“ฐ๊ธฐ ๊ถŒํ•œ์€ ์ž‘์„ฑ์ž์—๊ฒŒ๋งŒ
        return obj.author == request.user

๐Ÿ›ฃ๏ธ URLs (urls.py)

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import PostViewSet, CategoryViewSet, TagViewSet

router = DefaultRouter()
router.register(r'posts', PostViewSet, basename='post')
router.register(r'categories', CategoryViewSet, basename='category')
router.register(r'tags', TagViewSet, basename='tag')

urlpatterns = [
    path('', include(router.urls)),
]
์ด๋ ‡๊ฒŒ ๋งŒ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ API ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์ƒ์„ฑ๋ผ:

๐Ÿ“ก API ์—”๋“œํฌ์ธํŠธ โ€ข GET /api/posts/ - ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก
โ€ข POST /api/posts/ - ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ
โ€ข GET /api/posts/{id}/ - ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ
โ€ข PUT /api/posts/{id}/ - ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •
โ€ข DELETE /api/posts/{id}/ - ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ
โ€ข GET /api/posts/popular/ - ์ธ๊ธฐ ๊ฒŒ์‹œ๊ธ€
โ€ข GET /api/posts/trending/ - ํŠธ๋ Œ๋”ฉ ๊ฒŒ์‹œ๊ธ€
โ€ข POST /api/posts/{id}/add_comment/ - ๋Œ“๊ธ€ ์ถ”๊ฐ€
โ€ข GET /api/categories/ - ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก
โ€ข GET /api/tags/ - ํƒœ๊ทธ ๋ชฉ๋ก
์‹ค์ œ๋กœ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ๋„ ์ด๋Ÿฐ ์‹์œผ๋กœ API๋ฅผ ๊ตฌ์„ฑํ•ด์„œ ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

๐ŸŽ“ ํ•™์Šต ๋กœ๋“œ๋งต๊ณผ ์ถ”์ฒœ ์ž๋ฃŒ

์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ๋А๋ผ ์ˆ˜๊ณ ํ–ˆ์–ด! ์ด์ œ ์–ด๋–ป๊ฒŒ ๊ณต๋ถ€๋ฅผ ์ด์–ด๊ฐ€๋ฉด ์ข‹์„์ง€ ๋กœ๋“œ๋งต์„ ์•Œ๋ ค์ค„๊ฒŒ. ๐Ÿ“š

๐Ÿ—บ๏ธ ํ•™์Šต ๋‹จ๊ณ„

1๋‹จ๊ณ„: ๊ธฐ์ดˆ ๋‹ค์ง€๊ธฐ (1-2๊ฐœ์›”) โ€ข SQL ๊ธฐ๋ณธ ๋ฌธ๋ฒ• ์™„๋ฒฝํžˆ ์ตํžˆ๊ธฐ
โ€ข ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„ ์›์น™ (์ •๊ทœํ™”, ๊ด€๊ณ„ ์„ค์ •)
โ€ข ๊ธฐ๋ณธ CRUD ์ž‘์—… ์‹ค์Šต
โ€ข ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค์–ด๋ณด๊ธฐ

์ถ”์ฒœ ์ž๋ฃŒ:
- W3Schools SQL Tutorial
- SQLZoo (๋Œ€ํ™”ํ˜• SQL ํ•™์Šต)
- ์ƒํ™œ์ฝ”๋”ฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ•์˜
2๋‹จ๊ณ„: ORM ์ž…๋ฌธ (2-3๊ฐœ์›”) โ€ข ์„ ํƒํ•œ ์–ธ์–ด์˜ ORM ํ”„๋ ˆ์ž„์›Œํฌ ํ•™์Šต
โ€ข ๋ชจ๋ธ ์ •์˜์™€ ๊ด€๊ณ„ ์„ค์ •
โ€ข ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ์ž‘์„ฑ
โ€ข ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ด€๋ฆฌ

์ถ”์ฒœ ์ž๋ฃŒ:
- Django ๊ณต์‹ ๋ฌธ์„œ (Django ORM)
- SQLAlchemy ํŠœํ† ๋ฆฌ์–ผ
- Prisma ๊ณต์‹ ๊ฐ€์ด๋“œ
- Real Python ORM ๊ฐ•์ขŒ
3๋‹จ๊ณ„: ์‹ค์ „ ํ”„๋กœ์ ํŠธ (3-4๊ฐœ์›”) โ€ข ์‹ค์ œ ์„œ๋น„์Šค ์ˆ˜์ค€์˜ ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ
โ€ข ๋ณต์žกํ•œ ์ฟผ๋ฆฌ์™€ ๊ด€๊ณ„ ๋‹ค๋ฃจ๊ธฐ
โ€ข ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ ์šฉ
โ€ข ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ ๊ตฌํ˜„

ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด:
- ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ
- ์ „์ž์ƒ๊ฑฐ๋ž˜ ์‚ฌ์ดํŠธ
- SNS ์„œ๋น„์Šค
- ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ๋„๊ตฌ
4๋‹จ๊ณ„: ๊ณ ๊ธ‰ ๊ธฐ์ˆ  (์ง€์†์ ) โ€ข ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒค๋”ฉ๊ณผ ๋ ˆํ”Œ๋ฆฌ์ผ€์ด์…˜
โ€ข ์บ์‹ฑ ์ „๋žต
โ€ข ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜
โ€ข NoSQL๊ณผ RDBMS ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์‚ฌ์šฉ
โ€ข ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

์ถ”์ฒœ ์ž๋ฃŒ:
- "Designing Data-Intensive Applications" (์ฑ…)
- AWS Database ์„œ๋น„์Šค ๋ฌธ์„œ
- Redis ๊ณต์‹ ๋ฌธ์„œ
- MongoDB University

๐Ÿ’ก ์‹ค๋ฌด ํŒ

1. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ธฐ ์ฒ˜์Œ๋ถ€ํ„ฐ ํฐ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ ค๊ณ  ํ•˜์ง€ ๋งˆ. ๊ฐ„๋‹จํ•œ TODO ์•ฑ์ด๋‚˜ ๋ฉ”๋ชจ์žฅ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋‚˜๊ฐ€๋Š” ๊ฒŒ ์ข‹์•„!

2. ๊ณต์‹ ๋ฌธ์„œ ์ฝ๊ธฐ
๋ธ”๋กœ๊ทธ ๊ธ€๋„ ์ข‹์ง€๋งŒ, ๊ณต์‹ ๋ฌธ์„œ๊ฐ€ ๊ฐ€์žฅ ์ •ํ™•ํ•˜๊ณ  ์ตœ์‹  ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์–ด. ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒ ์ง€๋งŒ ์ต์ˆ™ํ•ด์ง€๋ฉด ๊ฐ€์žฅ ๋น ๋ฅธ ํ•™์Šต ๋ฐฉ๋ฒ•์ด์•ผ.

3. ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋ฐ›๊ธฐ
GitHub์— ์ฝ”๋“œ๋ฅผ ์˜ฌ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค์˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•„๋ด. ๋˜๋Š” ์˜จ๋ผ์ธ ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์งˆ๋ฌธํ•˜๊ณ  ๋‹ต๋ณ€ํ•˜๋ฉด์„œ ๋ฐฐ์›Œ!

4. ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ ์ฐธ์—ฌ
์‹ค์ œ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด์„œ ๋งŽ์ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด. ์ž‘์€ ๋ฒ„๊ทธ ์ˆ˜์ •์ด๋‚˜ ๋ฌธ์„œ ๊ฐœ์„ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ด.

5. ์„ฑ๋Šฅ ์ธก์ • ์Šต๊ด€ํ™”
ํ•ญ์ƒ ์ฟผ๋ฆฌ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ฒดํฌํ•˜๊ณ , ๋А๋ฆฐ ๋ถ€๋ถ„์„ ์ฐพ์•„์„œ ์ตœ์ ํ™”ํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ. ์ด๊ฒŒ ์‹ค๋ ฅ ํ–ฅ์ƒ์˜ ์ง€๋ฆ„๊ธธ์ด์•ผ!

๐ŸŒ ์œ ์šฉํ•œ ๋ฆฌ์†Œ์Šค

์˜จ๋ผ์ธ ๊ฐ•์˜ ํ”Œ๋žซํผ:
โ€ข Udemy - ๋‹ค์–‘ํ•œ ORM ๊ฐ•์ขŒ
โ€ข Coursera - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ „๋ฌธ ๊ณผ์ •
โ€ข Pluralsight - ์‹ค๋ฌด ์ค‘์‹ฌ ๊ฐ•์˜
โ€ข ์ธํ”„๋Ÿฐ - ํ•œ๊ตญ์–ด ๊ฐ•์˜

์ปค๋ฎค๋‹ˆํ‹ฐ:
โ€ข Stack Overflow - ์งˆ๋ฌธ๊ณผ ๋‹ต๋ณ€
โ€ข Reddit (r/django, r/webdev) - ํ† ๋ก ๊ณผ ์ •๋ณด ๊ณต์œ 
โ€ข Dev.to - ๊ฐœ๋ฐœ ๋ธ”๋กœ๊ทธ
โ€ข ์ƒํ™œ์ฝ”๋”ฉ ์ปค๋ฎค๋‹ˆํ‹ฐ

๋„๊ตฌ:
โ€ข DBeaver - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค GUI ๋„๊ตฌ
โ€ข Postman - API ํ…Œ์ŠคํŠธ
โ€ข Django Debug Toolbar - ์ฟผ๋ฆฌ ๋ถ„์„
โ€ข pgAdmin - PostgreSQL ๊ด€๋ฆฌ

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐Ÿ‘

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™๊ณผ ORM์€ ์›น ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ ๊ธฐ์ˆ ์ด์•ผ. ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ๊พธ์ค€ํžˆ ์—ฐ์Šตํ•˜๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ž์‹ ์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋  ๊ฑฐ์•ผ.

๐ŸŽ“ ํ•ต์‹ฌ ์š”์•ฝ โ€ข ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ
โ€ข SQL์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์†Œํ†ตํ•˜๋Š” ์–ธ์–ด
โ€ข ORM์€ ๊ฐ์ฒด์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๋„๊ตฌ
โ€ข ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ์‹ค๋ฌด์—์„œ ์ •๋ง ์ค‘์š”ํ•ด (N+1 ๋ฌธ์ œ, ์ธ๋ฑ์Šค, ์บ์‹ฑ)
โ€ข ๋ณด์•ˆ์€ ์ ˆ๋Œ€ ์†Œํ™€ํžˆ ํ•˜๋ฉด ์•ˆ ๋ผ (SQL ์ธ์ ์…˜, ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”)
โ€ข ์‹ค์ „ ๊ฒฝํ—˜์ด ๊ฐ€์žฅ ์ค‘์š”ํ•ด - ๋งŽ์ด ๋งŒ๋“ค์–ด๋ณด์ž!
์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ ์›น๊ฐœ๋ฐœ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, ์ด๋Ÿฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ORM ์ง€์‹์ด ์ •๋ง ํฐ ๋„์›€์ด ๋  ๊ฑฐ์•ผ. ์‹ค์ œ๋กœ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ด๋Ÿฐ ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•ด์„œ ๋‹ค์–‘ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๊ฑฐ๋“ ! ๐Ÿš€

๊ธฐ์–ตํ•ด! ์™„๋ฒฝํ•œ ๊ฐœ๋ฐœ์ž๋Š” ์—†์–ด. ๋ชจ๋‘ ๊ณ„์† ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ์ค‘์ด์•ผ. ์‹ค์ˆ˜๋ฅผ ๋‘๋ ค์›Œํ•˜์ง€ ๋ง๊ณ , ๋งŽ์ด ์‹œ๋„ํ•ด๋ณด๊ณ , ์‹คํŒจํ•˜๋ฉด์„œ ๋ฐฐ์›Œ๋‚˜๊ฐ€๋ฉด ๋ผ. ๐Ÿ’ช

๐Ÿ’Œ ๋งˆ์ง€๋ง‰ ์กฐ์–ธ ์ฝ”๋”ฉ์€ ๋งˆ๋ผํ†ค์ด์ง€ ๋‹จ๊ฑฐ๋ฆฌ ๋‹ฌ๋ฆฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ์•ผ. ๋งค์ผ ์กฐ๊ธˆ์”ฉ์ด๋ผ๋„ ๊พธ์ค€ํžˆ ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ํ•˜๋ฃจ์— 30๋ถ„์ด๋ผ๋„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ์ƒˆ๋กœ์šด ๊ฑธ ๋ฐฐ์šฐ๊ณ , ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•ด๋ด.

๊ทธ๋ฆฌ๊ณ  ํ˜ผ์ž ๊ณ ๋ฏผํ•˜์ง€ ๋ง๊ณ  ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์ฐธ์—ฌํ•ด์„œ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ์†Œํ†ตํ•ด. ์งˆ๋ฌธํ•˜๊ณ , ๋‹ต๋ณ€ํ•˜๊ณ , ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•˜๋ฉด์„œ ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฑฐ์•ผ!

ํ™”์ดํŒ…! ๋„Œ ํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽ‰
์•ž์œผ๋กœ์˜ ๊ฐœ๋ฐœ ์—ฌ์ •์— ํ–‰์šด์ด ๊ฐ€๋“ํ•˜๊ธธ ๋ฐ”๋ผ! ๊ถ๊ธˆํ•œ ๊ฒŒ ์žˆ์œผ๋ฉด ์–ธ์ œ๋“  ์ฐพ์•„๋ด. ์šฐ๋ฆฌ๋Š” ๋ชจ๋‘ ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ์ผ์›์ด๋‹ˆ๊นŒ! ๐Ÿ˜Šโœจ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.