๐ SQL UnvUs Style Convention โ
๊ฐ๋ ์ฑ ๋์ SQL ์์ฑ์ ์ํ UnvUs ์คํ์ผ ๊ฐ์ด๋
โจ ๊ฐ์ (Overview) โ
SQL์ ๋จ์ํ ๋ฐ์ดํฐ ์ง์ ์ธ์ด๊ฐ ์๋๋๋ค. ์ฐ๋ฆฌ๊ฐ ์์ฑํ๋ ์ฟผ๋ฆฌ๋ ํ๋์ ๋์์ธ์ด๋ฉฐ, ๊ทธ ๋์์ธ์ด ๋ช ํํ๊ณ ํ๋ฆ์ด ์์ด์ผ ๋ฐ์ดํฐ๋ ์๋ฆ๋ต๊ฒ ๋ค๊ฐ์ต๋๋ค.
UnvUs์ค๋ฝ๊ณ ์ง๊ด์ ์ธ ์ฟผ๋ฆฌ ์์ฑ์ ์ํ UnvUs Style SQL Convention์ ์๊ฐํฉ๋๋ค.
์ด ๊ท์น์ด ์ ์ฉ๋๋ ๋ฒ์
ํ์ฌ ๋๋ถ๋ถ์ ์ฟผ๋ฆฌ๋ Java DSL ๋ก ์์ฑํ๊ณ , XML ์๋ resultMap ๋ง ๋จ๊น๋๋ค (โ MyBatis Dynamic SQL).
- XML ์ ๋จ์ SQL โ ์ด ๋ฌธ์์ River Style ์ ๋ฐ๋ฆ ๋๋ค
- Java DSL โ ๊ฐ์ ์ ์ (ํค์๋ ์ ๋ ฌ)์ ์ฝ๋์ ์ ์ฉํ๊ณ , IDE ์๋ ํฌ๋งท์ด ๋ง๊ฐ๋จ๋ฆฌ์ง ์๋๋ก
//@formatter:offโฆ//@formatter:on์ผ๋ก ๊ฐ์๋๋ค
//@formatter:off
return select(b.allColumns())
.from(b, b.getAlias())
.where(b.enabled, isTrue())
.and(b.name, isLike(keyword))
.orderBy(b.desc(b.createdDt))
.build().render(RenderingStrategies.MYBATIS3);
//@formatter:on${} ๋ฌธ์์ด ์นํ์ ๊ธ์ง์
๋๋ค
${whereClause} / ${sortBy} ๊ฐ์ ํ ํฐ์ ๊ฐ์ด SQL ์ ๊ทธ๋๋ก ๋ฐํ ์ธ์ ์
๊ฒฝ๋ก๊ฐ ๋ฉ๋๋ค. ํ
์คํธ๊ฐ mapper XML ์ ์ด๋ฐ ํ ํฐ์ด ๋ค์ด์ค๋ ๊ฒ์ ์๋์ผ๋ก ๋ง์ต๋๋ค. ๋์ ์กฐ๊ฑด์ #{} ๋ฐ์ธ๋ฉ๊ณผ ๋ช
์์ <if>, ๋๋ Java DSL ๋ก ์์ฑํ์ธ์.
๐ UnvUs Style ์ด๋? โ
INFO
- River Style (๊ฐ๋ฌผ ์คํ์ผ): SQL ํค์๋์ ์ ์ ๋ง์น ๊ฐ๋ฌผ์ด ํ๋ฅด๋ฏ์ด ์ ๋ ฌํ์ฌ, ๊ฐ๋ ์ฑ๊ณผ ๋ ผ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๊ทน๋ํํ๋ ์คํ์ผ์ ๋๋ค.
- UnvUs Style: River Style์ ํต์ฌ ์์น์ ๊ธฐ๋ฐ์ผ๋ก, ํนํ MyBatis ํ๊ฒฝ์์์ ์ ์ฉ์ฑ๊ณผ ๊ฐ๋
์ฑ์ ๋์ฑ ํฅ์์ํค๊ธฐ ์ํด ๋ช ๊ฐ์ง ๊ท์น์ ์ฌ์ ์ํ๊ณ ๊ตฌ์ฒดํํ ์คํ์ผ์
๋๋ค. (์:
<include>ํ๊ทธ ์ฒ๋ฆฌ, ๋์ SQL๊ณผ์ ์กฐํ ๋ฑ)
| ๐ ์ผ์ชฝ ์ ๋ ฌ ํค์๋ (Main Flow) | ๐ ์ค๋ฅธ์ชฝ ์ ๋ ฌ ํค์๋ (์กฐ๊ฑด/์์ ๋ฑ) |
|---|---|
SELECT | AS |
INSERT / DELETE / UPDATE | IN, IS, LIKE, BETWEEN, EXISTS, DISTINCT |
FROM | TRUE / FALSE, NULL, NOT |
JOIN, LEFT JOIN, ON | ALL, ANY |
WHERE | |
AND / OR | |
GROUP BY | |
HAVING | |
ORDER BY | |
LIMIT, OFFSET | |
UNION, CASE, WHEN, THEN, ELSE, END |
๐ ์คํ์ผ ๊ฐ์ด๋๋ผ์ธ โ
โ ๊ธฐ๋ณธ ๊ตฌ์กฐ โ
SELECT u.username
, u.email
, u.user_id
FROM users AS u
WHERE u.is_active = TRUE
ORDER BY u.username ASC;
- ์ฃผ์ ํค์๋๋ ์ ์ค์์ ์์ํ๋ฉฐ ์ผ์ชฝ ์ ๋ ฌํฉ๋๋ค.
- ์ ํ ์ปฌ๋ผ, WHERE ์ ๋ด๋ถ ์กฐ๊ฑด ๋ฑ์ ํ ๋จ๊ณ ๋ค์ฌ์ฐ๊ธฐํฉ๋๋ค.
- AS, ํ ๋น(=), ๋น๊ต ์ฐ์ฐ์ (>=, <=, LIKE ๋ฑ)๋ ๊ฐ์ ์ค์์ ์์ฐ์ค๋ฝ๊ฒ ์ด์ด์ง๋๋ก ํฉ๋๋ค.
๐ค JOIN ๊ตฌ์กฐ โ
SELECT o.order_id
, o.order_date
, u.username
, u.email
FROM orders AS o
JOIN users AS u
ON o.user_id = u.user_id
LEFT JOIN user_profiles AS p -- LEFT JOIN๋ JOIN๊ณผ ๋์ผํ ๋ผ์ธ์ ์ ๋ ฌ
ON u.user_id = p.user_id -- ON ์ ์ JOIN์ ๋ง์ถฐ ๋ค์ฌ์ฐ๊ธฐ
AND p.is_primary = TRUE -- ON ์ ์ ์ฌ๋ฌ ์กฐ๊ฑด์ด ์ฌ ๊ฒฝ์ฐ AND๋ก ์ฐ๊ฒฐ
WHERE u.is_active = TRUE;
- JOIN๊ณผ ON์ ํ ์ธํธ๋ก,
JOINโON์์๋ก ๋ค์ฌ์ฐ๊ธฐํ์ฌ ๊ด๊ณ๋ฅผ ์๊ฐLEFT JOIN์ ๊ฒฝ์ฐ JOIN ๊น์ง๊ฐ river
๐งฉ ์๋ธ์ฟผ๋ฆฌ (Subquery) ์์ฑ ๋ฐฉ์ โ
(๊ดํธ๋ ์๋ก์ด ๋ฆฌ๋ฒ(River)์ ์์์ ์ ๋๋ค. ์๋ธ์ฟผ๋ฆฌ ๋ด๋ถ๋ ๋ฆฌ๋ฒ ์คํ์ผ์ ๊ทธ๋๋ก ์ ์งํฉ๋๋ค.
โ ์์ โ
SELECT u.username
, u.email
, u.user_id
FROM users AS u
WHERE u.user_id IN
(SELECT o.user_id
FROM orders AS o
WHERE o.order_date >= '2024-01-01'
AND o.order_date <= '2024-12-31'
AND o.status = 'COMPLETED')
AND u.is_active = TRUE
ORDER BY u.username ASC;TIP
- ์ฌ๋ ๊ดํธ
(๋ ์๋ธ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ํค์๋(์: IN, =, EXISTS)์ ๊ฐ์ ์ค์ ๋ค์ ์ค๋ก ๋ด๋ ค ํ ๋จ๊ณ ๋ค์ฌ์๋๋ค. (์ ์์๋ ๋ค์ ์ค๋ก ๋ด๋ฆฐ ํํ) - ์ด ๋ค์ฌ์ฐ๊ธฐ๋ฅผ ๊ธฐ์ค์ผ๋ก ์๋ธ์ฟผ๋ฆฌ ๋ด๋ถ์ River ์คํ์ผ์ ์ ์ฉํฉ๋๋ค.
- ์๋ธ์ฟผ๋ฆฌ ๋ด๋ถ์ SELECT, FROM, WHERE ๋ฑ์ ํค์๋๋ ์ด ์๋ก์ด ๋ค์ฌ์ฐ๊ธฐ ์์ค์์ ์ผ์ชฝ ์ ๋ ฌ๋ฉ๋๋ค.
- ๋ซ๋ ๊ดํธ
)๋ ์๋ธ์ฟผ๋ฆฌ ๋ด์ฉ์ด ๋๋ ํ ์ค์ ์ฝ์ ํฉ๋๋ค. - ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋๋ ํ ์ด์ด์ง๋ ์ธ๋ถ ์ฟผ๋ฆฌ์ ์ (์: AND, OR, ORDER BY ๋ฑ)์ ๊ธฐ์กด ์ธ๋ถ ์ฟผ๋ฆฌ์ River ํ๋ฆ๊ณผ ๋ค์ฌ์ฐ๊ธฐ ์์ค์ผ๋ก ๋ณต๊ทํฉ๋๋ค.
๐งฉ <include> ๊ตฌ๋ฌธ์ ๋ฆฌ๋ฒ ์ ์ฉ ๋ฐฉ์ โ
โ ์์ โ
<select id="selectActiveUsers" resultType="User">
<!--@formatter:off -->
SELECT u.user_id
, u.username
, u.email
FROM users AS u
WHERE u.is_active = TRUE
<include refId="A">
<if test="B == B">
<if test="C == C">
AND B = B
</if>
</if>
</include>
ORDER BY u.username ASC
<!--@formatter:on -->
</select>WARNING
<include>๋<if>์ ๊ฐ์ ํ๊ทธ๋ค์ xml indent convention ์ ๋ฐ๋ฆ ๋๋ค (4 blank spaces)- ํ๊ทธ์ indent ์ ์๊ด์์ด ๋ด๋ถ ์ฟผ๋ฆฌ๋ฌธ์ ๊ธฐ์กด river ์ ๋ง๊ฒ ์ ๋ ฌํฉ๋๋ค.
๐ก UserFilter.xml ๋ด๋ถ ์์ โ
<sql id="UserFilter">
AND u.created_at >= #{startDate}
AND u.created_at <= #{endDate}
</sql>๋ณ๋ ์ ์๋
<sql>๋ธ๋ก๋ ๋ฆฌ๋ฒ ์คํ์ผ๋ก ์์ฑํ์ฌ ์ผ๊ด์ฑ๊ณผ ๊ฐ๋ ์ฑ์ ์ ์งํฉ๋๋ค.