QueryDSL์„ ์ด์šฉํ•ด ๋‹ค์ค‘ ํ•ด์‹œํƒœ๊ทธ ์กฐํšŒํ•˜๊ธฐ

ํ•ดํ”ผํ•˜์šฐ์Šค ๋•Œ์™€ ๋™์ผํ•˜๊ฒŒ ํƒœ๊ทธ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜์—ฌ ํŒŒํ‹ฐ๋ฃธ ํ…Œ๋งˆ๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ณ  ๋ชฉ์ ์— ๋”ฐ๋ฅธ ์กฐํšŒ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณ  ์‹ถ์—ˆ๋‹ค.

Spring + JPA ํ™˜๊ฒฝ์—์„œ ํƒœ๊ทธ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ๊ณ ๋ คํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ด 2๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ ๋ฐ”๋กœ ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ์ง€ํ‚ค๋Š” ๊ฒƒ, ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ jpa์— ๋งž๊ฒŒ ๋งคํ•‘ํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ๋งํ•˜๊ณ  ์‹ถ๋‹ค.

๊ฐ„๋‹จํ•ด ๋ณด์—ฌ๋„ ๋ง‰์ƒ ๊ตฌํ˜„ํ•˜๋‹ค๋ณด๋ฉด ๋ณต์žกํ•˜๋‹ˆ ์ด๋ฒˆ์— ์ž˜ ์ •๋ฆฌํ•ด๋‘๊ณ  ๋‹ค์Œ์— ๋‹ค์‹œ ์ฐพ์•„๋ณด๊ณ ์ž ํ•œ๋‹ค!

ํ…Œ์ด๋ธ” ๊ตฌ์„ฑํ•˜๊ธฐ

์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”(Join Table)

์ฐฌ์ฐฌํžˆ ์ƒ๊ฐํ•ด๋ณด์ž.

ํŒŒํ‹ฐ๋ฃธ์„ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•œ ์ˆ˜๋‹จ์œผ๋กœ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•˜๋‚˜์˜ ํŒŒํ‹ฐ๋ฃธ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๊ฑธ์–ด ๊ฐ๊ฐ์˜ ํ•ด์‹œํƒœ๊ทธ๊ฐ€ ๊ฐ€์ง„ ์˜๋ฏธ๋ฅผ ์กฐํ•ฉํ•ด ํŒŒํ‹ฐ๋ฃธ์„ ์†Œ๊ฐœํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ์ฆ‰, ํ•˜๋‚˜์˜ ํŒŒํ‹ฐ๋ฃธ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๋‹ฌ๊ณ ์‹ถ๋‹ค๋Š” ๋ง์ด๋‹ค.

๋ฐ˜๋Œ€๋กœ ํ•˜๋‚˜์˜ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๋†“๊ณ  ๋ณด๋ฉด ์–ด๋–จ๊นŒ? #๋ถˆ๊ธˆ ์ด๋ผ๋Š” ํ•ด์‹œํƒœ๊ทธ๊ฐ€ ๊ผญ ํ•˜๋‚˜์˜ ํŒŒํ‹ฐ๋ฃธ์—์„œ๋งŒ ์‚ฌ์šฉ๋˜์–ด์•ผํ• ๊นŒ? ๋งŒ์•ฝ ๊ทธ๋ ‡๋‹ค๋ฉด #๋ถˆ๊ธˆ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ๋žŒ๋“ค์€ ์›”์š”์ผ๋ถ€ํ„ฐ ๋ฏธ๋ฆฌ ํŒŒํ‹ฐ๋ฃธ์„ ๋งŒ๋“ค์–ด๋†“์•„์•ผ ํ• ์ง€๋„ ๋ชจ๋ฅธ๋‹ค. ํ•ด์‹œํƒœ๊ทธ ์ž…์žฅ์—์„œ ๋ณด๋ฉด ํ•˜๋‚˜์˜ ํ•ด์‹œํƒœ๊ทธ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ํŒŒํ‹ฐ๋ฃธ์— ์„ค์ •๋  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

๋”ฐ๋ผ์„œ ํŒŒํ‹ฐ๋ฃธ๊ณผ ํ•ด์‹œํƒœ๊ทธ๋Š” ๋‹ค๋Œ€๋‹ค(n:m) ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„๋‹ค. ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋Š” ํ•ด์†Œ๋˜์–ด์•ผํ•œ๋‹ค. ํ…Œ์ด๋ธ” ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ์˜ ์ค‘๋ณต์ด ๋ฐœ์ƒํ•˜๊ณ  ํ…Œ์ด๋ธ”์ด ๋ณต์žกํ•ด์ง€๋ฉฐ ์กฐํšŒ๊ฐ€ ์–ด๋ ค์›Œ์ง€๊ธฐ๋•Œ๋ฌธ์ด๋‹ค. ์ด ๊ด€๊ณ„๋ฅผ ํ•ด์†Œํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํŒŒํ‹ฐ๋ฃธ๊ณผ ํ•ด์‹œํƒœ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ๊ฐ ํ•˜๋‚˜์”ฉ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ์—ญํ• ์ด ํ•„์š”ํ•˜๊ณ , ์ด ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์ด ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”(์กฐ์ธ ํ…Œ์ด๋ธ”)์ด๋‹ค. ๊ณ ์ž‘ ๋‘ ํ…Œ์ด๋ธ”์„ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ์šฉ๋„๋กœ ํ…Œ์ด๋ธ”์„ ์ƒˆ๋กญ๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์–ด์ƒ‰ํ•˜๊ฒŒ ๋“ค๋ฆด์ง€ ๋ชฐ๋ผ๋„ ์ด ํ…Œ์ด๋ธ”์˜ ์—ญํ• ์€ ์–ด๋งˆ์–ด๋งˆํ•˜๋‹ค.

Join Table

์—ฐ๊ฒฐ ํ…Œ์ด๋ธ” ๋•๋ถ„์— ํŒŒํ‹ฐ๋ฃธ๊ณผ ํ•ด์‹œํƒœ๊ทธ ๋‚ด๋ถ€์—๋Š” ๋ณต์žกํ•œ ๋‘˜์˜ ๊ด€๊ณ„๋Š” ๋นผ๋‚ด๊ณ  ๊ฐ๊ฐ ์ž์‹ ์˜ ๊ณ ์œ ํ•œ ์ •๋ณด๋งŒ์„ ๋ณด์œ ํ•  ์ˆ˜ ์žˆ๋‹ค. room_tag ํ…Œ์ด๋ธ”์—๋Š” ํŒŒํ‹ฐ๋ฃธ id์™€ ํ•ด์‹œํƒœ๊ทธ id๋ฅผ ํ•˜๋‚˜์”ฉ ์—ฐ๊ฒฐํ•˜์—ฌ ํ•˜๋‚˜์˜ ๋ฐฉ์— ์„ค์ •๋๋˜ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๋ชจ๋‘ ์ €์žฅํ•˜๊ณ , ๋ฐ˜๋Œ€๋กœ ํ•˜๋‚˜์˜ ํ•ด์‹œํƒœ๊ทธ๋„ ์—ฌ๋Ÿฌ ํŒŒํ‹ฐ๋ฃธ์— ์„ค์ •๋˜์—ˆ๋˜ ๊ด€๊ณ„๋ฅผ ํ‘œํ˜„ํ•˜์—ฌ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฟผ๋ฆฌ๋ฌธ ์ž‘์„ฑ

์•ˆํƒ€๊น๊ฒŒ๋„ ๋ฌธ์ œ์˜ ์‹œ์ž‘์€ ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๋‹ค.

์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž๋Š” #๋ถˆ๊ธˆ์ด๋ผ๋Š” ํ•ด์‹œํƒœ๊ทธ์™€ #์•ผ๊ทผ์ด๋ผ๋Š” ํ•ด์‹œํƒœ๊ทธ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ๋น„๋ก ๊ธˆ์š”์ผ์— ์•ผ๊ทผ ์ค‘์ด์ง€๋งŒ ์ž์‹ ๊ณผ ๊ฐ™์€ ์ฒ˜์ง€์˜ ์‚ฌ๋žŒ๋“ค๊ณผ ๋‚ด์  ํฅ ๋ฐœ์‚ฐ ํŒŒํ‹ฐ๋ฅผ ์ฆ๊ธฐ๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ #๋ถˆ๊ธˆ ๋˜๋Š” #์•ผ๊ทผ ์ด๋ผ๋Š” ํ•ด์‹œํƒœ๊ทธ๋ฅผ ์„ค์ •ํ•œ ํŒŒํ‹ฐ๋ฃธ์„ ๋ชจ๋‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋ฉด ์–ด๋–ค ํŒŒํ‹ฐ๋ฃธ์€ ๋“ค์–ด๊ฐˆ ์ˆ˜๋„ ์—†์„ ์ •๋„๋กœ ์‹œ๋„๋Ÿฌ์šธ ๊ฒƒ์ด๊ณ , ์–ด๋–ค ํŒŒํ‹ฐ๋ฃธ์€ ํŒŒํ‹ฐ๋ฃธ์„ ๊ฐ€์žฅํ•œ ์‚ฌ๋‚ด ํšŒ์˜๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ์„ ์ˆ˜๋„ ์žˆ๋‹ค. #๋ถˆ๊ธˆ ํŒŒํ‹ฐ๋ฃธ๋งŒ ๋ฐ˜ํ™˜ํ•ด์„œ๋„ ์•ˆ ๋˜๊ณ  #์•ผ๊ทผ ํŒŒํ‹ฐ๋ฃธ๋งŒ ๋ฐ˜ํ™˜ํ•ด์„œ๋„ ์•ˆ ๋œ๋‹ค. #๋ถˆ๊ธˆ๊ณผ #์•ผ๊ทผ์ด๋ผ๋Š” ํƒœ๊ทธ๋ฅผ ๋™์‹œ์— ์„ค์ •ํ•œ ํŒŒํ‹ฐ๋ฃธ๋งŒ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค. ์—ฌ๊ธฐ์— ์ถ”๊ฐ€์ ์œผ๋กœ ๋‹ค๋ฅธ ํƒœ๊ทธ๊ฐ€ ๋ถ™์–ด์žˆ๋Š” ๊ฒƒ์€ ์ƒ๊ด€์ด ์—†๋‹ค.

roomtag ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์—์„œ ํŒŒํ‹ฐ๋ฃธ์„ ์กฐํšŒํ•˜๊ณ  ์‹ถ์ง€๋งŒ #๋ถˆ๊ธˆ๊ณผ #์•ผ๊ทผ์ด๋ผ๋Š” ํƒœ๊ทธ๋ฅผ ๋ชจ๋‘ ์„ค์ •ํ•œ ํŒŒํ‹ฐ๋ฃธ์ด์–ด์•ผ ํ•œ๋‹ค. ํ•ฉ์ง‘ํ•ฉ์ด ์•„๋‹ˆ๋ผ ๊ต์ง‘ํ•ฉ์ด์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. roomtag ํ…Œ์ด๋ธ”์˜ ๊ฐ ํ–‰์€ ํ•˜๋‚˜์˜ ๋ฐฉ๊ณผ ํ•˜๋‚˜์˜ ํ•ด์‹œํƒœ๊ทธ๋งŒ์„ ์—ฐ๊ฒฐํ•˜๊ณ  ์žˆ๊ธฐ๋•Œ๋ฌธ์— ๋‹จ์ˆœํžˆ AND ์—ฐ์‚ฐ์„ ์ทจํ•œ๋‹ค๊ณ  ํ•ด๊ฒฐ๋  ์ผ์ด ์•„๋‹ˆ๋‹ค. ๊ฐ ํ–‰์€ #๋ถˆ๊ธˆ๊ณผ #์•ผ๊ทผ์ด๋ผ๋Š” ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๋™์‹œ์— ์˜๋ฏธํ•  ์ˆ˜ ์—†๋‹ค(์• ์ดˆ์— ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ํ•ด์†Œํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์ด์—ˆ์œผ๋ฏ€๋กœ ๋‹น์—ฐํ•˜๋‹ค).

๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค์Œ์œผ๋กœ ์ƒ๊ฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ #๋ถˆ๊ธˆ ํƒœ๊ทธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ณ  #์•ผ๊ทผ ํƒœ๊ทธ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์™€ JOINํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•๋„ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์กฐํ•ฉํ•˜๊ณ  ์‹ถ์€ ํƒœ๊ทธ๊ฐ€ 2๊ฐœ๋ฟ์ด๋ผ๋Š” ๋ณด์žฅ์ด ์—†์œผ๋ฉฐ ํƒœ๊ทธ๊ฐ€ ๋Š˜์–ด๋‚ ์ˆ˜๋ก JOIN ์—ฐ์‚ฐ๋„ ๋™์ ์œผ๋กœ ๋Š˜์–ด๋‚˜์•ผํ•œ๋‹ค. ๐Ÿ˜ฃโ€ฆ

๊ฒฐ๊ตญ ์ฑ„ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ฟผ๋ฆฌ๋ฌธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๊ฐ€ ๋œ๋‹ค.

SELECT room_id
FROM room_tag
WHERE tag_id in (1, 2, 3)
GROUP BY room_id
HAVING count(room_id) = 3;

WHERE ๋ฌธ์˜ in ์—ฐ์‚ฐ์„ ํ†ตํ•ด ์›ํ•˜๋Š” ํ•ด์‹œํƒœ๊ทธ์™€ ๊ด€๋ จ๋œ room_tag ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜จ๋‹ค. ์ฟผ๋ฆฌ๋ฌธ 3๋ฒˆ์งธ ๋ผ์ธ์ธ WHERE ๋ฌธ ๊นŒ์ง€๋งŒ ์‹คํ–‰ํ•˜๋ฉด A๋ฐฉ์—์„œ 1, 2, 3์„ ๋ชจ๋‘ ์„ค์ •ํ•œ ๊ฒฝ์šฐ (A, 1), (A, 2), (A, 3)์˜ ํ˜•ํƒœ๋กœ ๋ฐฉ ๋ฒˆํ˜ธ๊ฐ€ ์ค‘๋ณต์ ์œผ๋กœ ์กฐํšŒ๋  ๊ฒƒ์ด๋‹ค. B๋ฐฉ์—์„œ 1, 2๋ฅผ ์„ค์ •ํ•œ ๊ฒฝ์šฐ (B, 1), (B, 2)์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์กฐํšŒ๋  ๊ฒƒ์ด๋‹ค.

์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋‹ค๋ณด๋ฉด ํ•œ๊ฐ€์ง€ ์ƒ๊ฐ์ด ๋– ์˜ค๋ฅธ๋‹ค. ๋ฐฉ ๋ฒˆํ˜ธ๋กœ ๋ฌถ์–ด๋ฒ„๋ฆฌ๊ณ  ์‹ถ๋‹ค! (A, 1 2 3), (B, 1 2) ์ด๋ ‡๊ฒŒ ๋ง์ด๋‹ค. ์ด๋ฅผ ์œ„ํ•œ ์ฟผ๋ฆฌ๊ฐ€ ์ฟผ๋ฆฌ๋ฌธ 4๋ฒˆ์งธ ๋ผ์ธ์ธ GROUP BY ์ด๋‹ค.

์—ฌ๊ธฐ์„œ ๋ฉˆ์ถ˜๋‹ค๋ฉด B๋ฐฉ์€ 1, 2 ๋ฒˆ ํƒœ๊ทธ๋งŒ ์„ค์ •ํ–ˆ๋Š”๋ฐ๋„ ์กฐํšŒ ๊ฒฐ๊ณผ์— ์ถ”๊ฐ€๋  ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์˜ค๋กœ์ง€ 3๊ฐ€์ง€ ํƒœ๊ทธ๋งŒ์„ ์„ค์ •ํ•œ ๋ฐฉ์„ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด HAVING ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•œ๋‹ค. GROUP BY๋กœ ๊ทธ๋ฃนํ™”ํ•œ ๊ฒฐ๊ณผ์— ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜๋Š” ์šฉ๋„์ด๋‹ค. ๊ฐ ๋ฐฉ์œผ๋กœ ๊ทธ๋ฃนํ™”ํ•œ ์ƒํƒœ๋กœ ๊ฐ ๋ฐฉ์˜ ํƒœ๊ทธ ๊ฐœ์ˆ˜๋ฅผ ๊ตฌํ•œ๋‹ค๋ฉด ๊ฐ ๋ฐฉ์€ ํƒœ๊ทธ 1, 2, 3 ์ค‘ ๋ช‡ ๊ฐ€์ง€์˜ ํƒœ๊ทธ๋ฅผ ์„ค์ •ํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์กฐ๊ธˆ ๋ณต์žกํ•˜์ง€๋งŒ join์ด๋‚˜ ์„œ๋ธŒ์ฟผ๋ฆฌ ์—ฐ์‚ฐ ์—†์ด ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

JPA QueryDSL ๊ตฌํ˜„ํ•˜๊ธฐ

QueryDsl ๋ฐฉ์‹์œผ๋กœ ์กฐํšŒํ•˜๊ธฐ

์œ„์˜ ์ฟผ๋ฆฌ๋ฌธ์€ ์•„์ฃผ ๋‹จ์ˆœํ•œ ํ˜•ํƒœ๋Š” ์•„๋‹ˆ๋‹ค. ์•„์ฃผ ๋‹จ์ˆœํ•œ ์กฐํšŒ ์ฟผ๋ฆฌ๋Š” Query Method ๋ฐฉ์‹์„ ์ด์šฉํ•ด ๋ฉ”์„œ๋“œ ๋ช…์œผ๋กœ ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค๋ฉด ์‰ฝ์ง€๋งŒ, ์œ„์˜ ์ฟผ๋ฆฌ๋Š” in ์ฒดํฌ๋„ ํ•ด์•ผํ•˜๊ณ  ๊ทธ๋ฃนํ™”๋„ ํ•ด์•ผํ•˜๋ฉฐ ๊ทธ๋ฃนํ™” ๊ฒฐ๊ณผ์— ์กฐ๊ฑด๊นŒ์ง€ ์ถ”๊ฐ€์ ์œผ๋กœ ๋‹ฌ์•„์ค˜์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ JPA๋ฅผ ํ™œ์šฉํ•œ ๋ฐฉ๋ฒ• ์ค‘ QueryDsl์„ ์ด์šฉํ•˜์—ฌ ๊ฐ ์—ฐ์‚ฐ์„ ๋ฉ”์„œ๋“œ๋กœ ์ฒด์ด๋‹ํ•˜์—ฌ ๊ตฌํ˜„ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

JPA๋Š” ๋‹จ์ˆœ ์ฟผ๋ฆฌ mapper๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ข€ ๋” ๊ฐ์ฒด์ง€ํ–ฅ์ ์œผ๋กœ ์ ‘๊ทผํ•˜์—ฌ ์„œ๋น„์Šค ํ”„๋กœ๊ทธ๋žจ์˜ ๊ฐ์ฒด์™€ DB ๋ฐ์ดํ„ฐ๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ORM ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์šฐ๋ฆฌ๋Š” ํ…Œ์ด๋ธ”์˜ pk ์ปฌ๋Ÿผ์ด๋‚˜ id๋ฅผ ๊ธฐ์ค€์œผ๋กœ sql๋ฌธ์„ ์ƒ๊ฐํ•˜์ง€ ์•Š๊ณ , ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ๊ฐํ•ด์•ผ ํ•œ๋‹ค.

์ฝ”๋“œ ๋จผ์ € ํ™•์ธํ•ด๋ณด์ž.

Java
// ์‹œํ–‰์ฐฉ์˜ค ์ฝ”๋“œ
public Page<Room> getRoomByTag(List<Tag> hashtags, Pageable sort) {
    JPQLQuery<Room> query = jpaQueryFactory.select(qRoomTag.room).from(qRoomTag)
            .where(qRoomTag.tag.in(hashtags))
            .groupBy(qRoom)
            .having(qRoomTag.tag.count().eq((long) hashtags.size()));

    List<Room> roomList = getQuerydsl().applyPagination(sort, query).fetch();
    return new PageImpl<>(roomList, sort, query.fetchCount());
}

// ์™„์„ฑ ์ฝ”๋“œ
public Page<Room> getRoomTagByTagName(String[] hashtags, Pageable sort) {
    List<Room> roomList = jpaQueryFactory.select(qRoomTag.room).from(qRoomTag)
            .where(qRoomTag.tag.tagName.in(hashtags))
            .groupBy(qRoomTag.room)
            .having(qRoomTag.tag.count().eq((long) hashtags.length)).fetch();

    return new PageImpl<>(roomList, sort, query.fetchCount());
}

์ฒด์ด๋‹ํ•˜์—ฌ ์‚ฌ์šฉํ•œ ๋ฉ”์„œ๋“œ๋งŒ ๋ณด๋ฉด ์ฟผ๋ฆฌ๋ฌธ๊ณผ ๋™์ผํ•˜๋‹ค. ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€ ์‹ค์ˆ˜ํ•œ ๋ถ€๋ถ„์„ ๋ณด๋ฉด sql ์—ญํ• ์„ ํ•˜๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด์ง€ํ–ฅ์ ์œผ๋กœ ์ƒ๊ฐํ•˜์ง€ ์•Š์•˜๊ธฐ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ์‹ค์ˆ˜๋ผ๋Š”๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋จผ์ € ๊ฐ€์žฅ ๋ฐ”๊นฅ ์Šค์ฝ”ํ”„์˜ 2๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด์ž. ๋‘ ๋ฉ”์„œ๋“œ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋‹ค๋ฅธ ํ˜•ํƒœ๋กœ ๋ฐ›๋Š”๋‹ค. ํ”„๋ก ํŠธ์—์„œ ์š”์ฒญ์œผ๋กœ ๋“ค์–ด์˜จ ํ•ด์‹œํƒœ๊ทธ์˜ ํ˜•ํƒœ์— ๋Œ€ํ•œ ์„ค๋ช…์„ ๋ง๋ถ™์ด๋ฉด ํ•ด์‹œํƒœ๊ทธ๋Š” ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ ๋ฐฐ์—ด์— ์ €์žฅ๋˜์–ด์žˆ๋‹ค(String[] hashtags). ์ด๊ฒƒ์œผ๋กœ ๋ฏธ๋ฃจ์–ด ์ƒ๊ฐํ•ด๋ณด๋ฉด ์™„์„ฑ ์ฝ”๋“œ์—์„œ๋Š” ์š”์ฒญ์˜ ํ˜•์‹์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ–ˆ๊ณ , ์‹œํ–‰์ฐฉ์˜ค ์ฝ”๋“œ์—์„œ๋Š” ์ด ํ•จ์ˆ˜๋ฅผ ๋ถ€๋ฅด๊ธฐ ์ „์— ์š”์ฒญ์„ ๋ฐ”ํƒ•์œผ๋กœ Tag ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ–ˆ์„ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์‹ค์ œ๋กœ ์‹œํ–‰์ฐฉ์˜ค ์ฝ”๋“œ ์ด์ „์—๋Š” ์š”์ฒญ์œผ๋กœ ๋ฐ›์€ (String[] hashtags)ํ˜•ํƒœ ํƒœ๊ทธ๋ฅผ tag ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒํ•˜์—ฌ tag ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด์™”๋‹ค.

๋ญ”๊ฐ€ ์ด์ƒํ•˜์ง€ ์•Š์€๊ฐ€? ์ด๋ฏธ ํƒœ๊ทธ๋ช…์„ ์•Œ๊ณ ์žˆ๋Š”๋ฐ ํƒœ๊ทธ ๋ฒˆํ˜ธ๋ฅผ ๋ชจ๋ฅธ๋‹ค๊ณ  ๋˜ ๋‹ค์‹œ ํƒœ๊ทธ๋ฅผ ์กฐํšŒํ•ด์•ผํ•œ๋‹ค๋‹ˆโ€ฆ ๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ตฌ์„ฑํ–ˆ๋‹ค๋ฉด ์ด๋ฏธ Tag ๊ฐ์ฒด ๋‚ด๋ถ€์—๋Š” ํƒœ๊ทธ ๋ฒˆํ˜ธ์™€ ํƒœ๊ทธ๋ช…์ด ํ•„๋“œ๋กœ ํ‘œํ˜„๋˜์–ด์žˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ roomTag ๊ฐ์ฒด ๋‚ด๋ถ€์—๋„ ํƒœ๊ทธ๋ช…์€ ์•„๋‹ˆ์ง€๋งŒ ํƒœ๊ทธ๋ช…์„ ํฌํ•จํ•œ tag ๊ฐ์ฒด๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ•„๋“œ์— ์ ‘๊ทผํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. qRoomTag.tag.tagName.in(hashtags) ์ฒ˜๋Ÿผ ๋ง์ด๋‹ค.

๋‹ค์Œ์œผ๋กœ group by() ๋ฉ”์„œ๋“œ๋ฅผ ๋ณด์ž. ์‹œํ–‰์ฐฉ์˜ค ์ฝ”๋“œ์—์„œ๋Š” ๋‹จ์ˆœํžˆ room ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฃนํ™” ์กฐ๊ฑด์œผ๋กœ ์„ค์ •ํ–ˆ์ง€๋งŒ ์™„์„ฑ ์ฝ”๋“œ์—์„œ๋Š” roomTag ๊ฐ์ฒด ๋‚ด๋ถ€์— ํ•„๋“œ๋กœ ์กด์žฌํ•˜๋Š” room ๊ฐ์ฒด๋ฅผ ๊ทธ๋ฃน ์กฐ๊ฑด์œผ๋กœ ์„ค์ •ํ•œ๋‹ค. JPA์˜ QueryDsl์€ ํ”„๋กœ๊ทธ๋žจ ๊ฐ์ฒด์™€ DB๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๊ณ  ์žˆ๊ธฐ๋•Œ๋ฌธ์— ์•„๋ฌด๋ฆฌ ๊ฐ™์€ room ํƒ€์ž…์ด๋ผ๊ณ  ํ• ์ง€๋ผ๋„ roomTag ๊ฐ์ฒด์˜ ํ•„๋“œ์— ์กด์žฌํ•˜๋Š” room ๊ฐ์ฒด๋ฅผ ๋ช…์‹œํ•ด์•ผ DB์™€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์—ฐ๊ฒฐํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

ํ•˜โ€ฆ ์•„๋ฌด๊ฒƒ๋„ ๋ชจ๋ฅด๊ณ  ๋‹จ์ˆœํ•˜๊ฒŒ room ๊ฐ์ฒด๋ฅผ ์ง€์ •ํ–ˆ๋‹ค๊ฐ€ jpa๋กœ๋ถ€ํ„ฐ ์ž๊พธ๋งŒ ์ž˜๋ชป๋œ ์ปฌ๋Ÿผ, ์—†๋Š” ์ปฌ๋Ÿผ, ์ดํ•ดํ•  ์ˆ˜ ์—†๋Š” ์ปฌ๋Ÿผ์ด๋ผ๋Š” ์—๋Ÿฌ๋ฅผ ๋ฐ›์•„์•ผ ํ–ˆ๋‹ค. ๋ถ„๋ช… sql๋ฌธ๊ณผ ๋™์ผํ•œ๋ฐ ์™œ ์•ˆ๋ ๊นŒ ํ–ˆ๋Š”๋ฐ ์—๋Ÿฌ๋ฅผ ์žก์„ ๋•Œ๋„ sql๋ฌธ๋ฒ• ์˜ค๋ฅ˜๊ฐ€ ์•„๋‹Œ ์ด์ƒ sql๋ฌธ์„ ๊ธฐ์ค€์œผ๋กœ ์ƒ๊ฐํ•  ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ฐ์ฒด์ง€ํ–ฅ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ๊ฐํ–ˆ์–ด์•ผ ํ–ˆ๋‹ค.

jpa๊ฐ€ ์‰ฝ์ง„ ์•Š์ง€๋งŒ ์ •๋ง ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋‹ค์‹œํ•œ๋ฒˆ ๊นจ๋‹ฌ์•˜๋‹ค. ์™œ๋ƒ๋ฉด 6์‹œ ์ „๊นŒ์ง€ ๊ธฐ๋Šฅ ๊ตฌํ˜„์€ ๋๋‚ด์•ผ๊ฒ ๋Š”๋ฐ DB ์กฐํšŒ๋Š” ์ž๊พธ๋งŒ ์‹คํŒจํ•˜๊ธธ๋ž˜ ์ฟผ๋ฆฌ๋ฌธ ์ž์ฒด๋ฅผ ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•˜์—ฌ ๊ตฌํ˜„ํ–ˆ์—ˆ๋‹ค. ์•„๋ž˜๊ฐ€ ๊ทธ ์ฝ”๋“œ๊ณ  ๋ณ„๋กœ ์„ค๋ช…ํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹คโ€ฆ ๐Ÿ™„

String[] hashtags = word.substring(1).split("#");
HashMap<Room, Integer> roomTagMap = new HashMap<>();

// tag ํ…Œ์ด๋ธ”์—์„œ hashtags์— ํ•ด๋‹นํ•˜๋Š” ํƒœ๊ทธ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ด
List<Tag> tagList = tagRepository.findByTagNameIn(hashtags);

for (Tag tag : tagList) {
    // roomTag ํ…Œ์ด๋ธ”์—์„œ ๊ฐ hashtag์— ํ•ด๋‹นํ•˜๋Š” ๋ฃธ ๋ฆฌ์ŠคํŠธ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ด
    List<RoomTag> searchedRooms = roomTagRepository.findByTag(tag);

    // ๋งต์— ๋ฃธ๊ณผ ์ฐพ๋Š” ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๋ช‡๊ฐœ ํฌํ•จํ•˜๋Š”์ง€ ์ €์žฅ
    for (RoomTag searchedRoom : searchedRooms) {
        Room curRoom = searchedRoom.getRoom();
        roomTagMap.computeIfPresent(curRoom, (k, v) -> v+1);
        roomTagMap.putIfAbsent(curRoom, 1);
    }
}

// ์กฐํšŒํ•˜๋Š” ํ•ด์‹œํƒœ๊ทธ ๊ฐœ์ˆ˜๋งŒํผ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ํฌํ•จํ•œ ๋ฃธ๋งŒ ๋ฐ˜ํ™˜
List<Room> roomContainingHashtags = new ArrayList<>();
int size = hashtags.length;
for (Map.Entry<Room, Integer> entry : roomTagMap.entrySet()) {
    if (entry.getValue() == size) roomContainingHashtags.add(entry.getKey());
}

roomList = new PageImpl<>(roomContainingHashtags, sort, roomContainingHashtags.size());