## ๐Ÿ“˜ Spring Data JPA | @Query ์–ด๋…ธํ…Œ์ด์…˜๊ณผ JPQL ์ •๋ฆฌ

Yeeunยท2025๋…„ 4์›” 24์ผ
0

SpringBoot

๋ชฉ๋ก ๋ณด๊ธฐ
13/46

๐Ÿ“˜ Spring Data JPA | @Query ์–ด๋…ธํ…Œ์ด์…˜๊ณผ JPQL ์ •๋ฆฌ

Spring Data JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด @Query ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์ง์ ‘ JPQL(JPA Query Language)์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. SQL๊ณผ ๋น„์Šทํ•ด ๋ณด์ด์ง€๋งŒ ์—”ํ‹ฐํ‹ฐ ์ค‘์‹ฌ์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ช‡ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์ฐจ์ด์ ๊ณผ ์ฃผ์˜ํ•  ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” @Query๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ JPQL์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋“ค์„ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.


โ˜‘๏ธ JPQL๊ณผ SQL์˜ ์ฐจ์ด: from ์ ˆ์˜ ๋Œ€์ƒ

  • SQL: ํ…Œ์ด๋ธ”์„ ๊ธฐ์ค€์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑ โ†’ from board
  • JPQL: ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑ โ†’ from Board b
    โœ… Board๋Š” ์ž๋ฐ” ํด๋ž˜์Šค ์ด๋ฆ„์ด๋ฏ€๋กœ ๋Œ€๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋ฉฐ, ์•Œ๋ฆฌ์•„์Šค(b)๋ฅผ ๋ฐ˜๋“œ์‹œ ๋ถ™์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.
@Query("select b from Board b")
List<Board> findAllBoards();

โžก๏ธ select * from board ์™€ ๋น„์Šทํ•˜์ง€๋งŒ, ์—”ํ‹ฐํ‹ฐ ๊ธฐ์ค€์ด๋ผ๋Š” ์ ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.


๐Ÿงฒ ์ „์ฒด๊ฐ€ ์•„๋‹Œ ํ•„๋“œ๋งŒ ๊ฐ€์ ธ์˜ฌ ๋•Œ

@Query("select b.seq, b.title from Board b where b.title like %?1%")
List<Object[]> findSeqAndTitle(String keyword);
  • b.seq, b.title์ฒ˜๋Ÿผ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ์„ ํƒํ•˜๋ฉด ๋ฐ˜ํ™˜ ํƒ€์ž…์€ List<Object[]>๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • Object[]๋กœ ๋ฐ›๋Š” ์ด์œ : b.seq๋Š” Long, b.title์€ String ๋“ฑ ๋ฆฌํ„ด ํƒ€์ž…์ด ์„ž์—ฌ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ์ด๋Š” SQL์˜ select seq, title from board์™€ ์œ ์‚ฌํ•˜์ง€๋งŒ, JPQL์€ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜์œผ๋กœ ์ดํ•ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ Tip: ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ์—”ํ‹ฐํ‹ฐ ์ „์ฒด(Board)๋ฅผ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฆฌํ„ด ํƒ€์ž…์ด ๋ช…ํ™•ํ•˜๊ณ  ์ฝ”๋“œ๋„ ๊น”๋”ํ•ด์ง€๊ธฐ ๋•Œ๋ฌธ์— JPQL์˜ ํฐ ์žฅ์  ์ค‘ ํ•˜๋‚˜๋กœ ๊ผฝํž™๋‹ˆ๋‹ค.


๐Ÿ”„ ์œ„์น˜ ๊ธฐ๋ฐ˜ vs ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ํŒŒ๋ผ๋ฏธํ„ฐ

โœ… ์œ„์น˜ ๊ธฐ๋ฐ˜ ํŒŒ๋ผ๋ฏธํ„ฐ (๊ธฐ๋ณธ ๋ฐฉ์‹)

@Query("select b from Board b where b.title like %?1%")
List<Board> searchByTitle(String keyword);
  • ?1์€ ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์˜๋ฏธ
  • ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ฟผ๋ฆฌ๋„ ํ•จ๊ป˜ ๋ฐ”๊ฟ”์•ผ ํ•˜๋ฏ€๋กœ ์œ ์ง€๋ณด์ˆ˜์— ๋‹ค์†Œ ๋ถˆ๋ฆฌ

โœ… ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ํŒŒ๋ผ๋ฏธํ„ฐ (๊ฐ€๋…์„ฑ โ†‘)

@Query("select b from Board b where b.title like %:title%")
List<Board> searchByTitle(@Param("title") String keyword);
  • :title โ†’ ๋ณ€์ˆ˜๋ช…์„ ๊ธฐ์ค€์œผ๋กœ ๋งคํ•‘
  • ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋ฉฐ, ์ด๋ฆ„์ด ๊ฐ™์œผ๋ฉด @Param ์ƒ๋žต๋„ ๊ฐ€๋Šฅ:
    @Query("select b from Board b where b.title like %:keyword%")
    List<Board> searchByTitle(String keyword); // keyword ์ด๋ฆ„์ด ๊ฐ™์œผ๋ฉด @Param ์ƒ๋žต ๊ฐ€๋Šฅ

๐Ÿ’ก ํ•ต์‹ฌ ์š”์•ฝ

ํ•ญ๋ชฉJPQLSQL
๋Œ€์ƒ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์ŠคDB ํ…Œ์ด๋ธ”
FROM ์ ˆfrom Board bfrom board
SELECT ์ ˆb ๋˜๋Š” b.seq, b.title*, seq, title
๋ฆฌํ„ด ํƒ€์ž…List<Board>, List<Object[]>ResultSet
ํŒŒ๋ผ๋ฏธํ„ฐ์œ„์น˜ ๊ธฐ๋ฐ˜ ?1, ์ด๋ฆ„ ๊ธฐ๋ฐ˜ :paramName?, :paramName

๐Ÿ“ฆ ์˜ˆ์ œ: ํ•„๋“œ ์ผ๋ถ€๋งŒ ์„ ํƒํ•œ JPQL

@Query("select b.seq, b.title, b.writer, b.createDate "
     + "from Board b "
     + "where b.title like %?1% "
     + "order by b.seq desc")
List<Object[]> queryAnnotationTest2(String searchKeyword);

๐Ÿ“ ์„ค๋ช…:

  • select b.seq, b.title ... โ†’ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ์„ ํƒ
  • ๋ฆฌํ„ด ํƒ€์ž…์ด Object[]์ธ ์ด์œ : ๋ชจ๋“  ํ•„๋“œ์˜ ํƒ€์ž…์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ
  • ํƒ€์ž…์„ ๋ช…ํ™•ํžˆ ์ •์˜ํ•œ DTO๋กœ ๋ฐ›๋Š” ๋ฐฉ๋ฒ•๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ํ˜•ํƒœ๋Š” Object[]

๐Ÿงฉ ์ฐธ๊ณ  ํŒ

  • @Query๋Š” JPQL ๊ธฐ๋ฐ˜์ž…๋‹ˆ๋‹ค. ๋„ค์ดํ‹ฐ๋ธŒ SQL์„ ์“ฐ๊ณ  ์‹ถ๋‹ค๋ฉด nativeQuery = true ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • Page<T>์™€ ํ•จ๊ป˜ ํŽ˜์ด์ง• ์ฟผ๋ฆฌ๋„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • JPA๋Š” ํƒ€์ž… ์•ˆ์ •์„ฑ๊ณผ ๊ตฌ์กฐ์ ์ธ ์ฟผ๋ฆฌ ๊ตฌ์„ฑ์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, @Query๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ํ•จ๊ป˜ ์žก์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


1๏ธโƒฃ like %?1%์—์„œ ํผ์„ผํŠธ %๋Š” ๋ญ์•ผ?

์ด๊ฑด SQL์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด ๋งค์นญ ๊ธฐํ˜ธ์˜ˆ์š”.

๊ธฐํ˜ธ์˜๋ฏธ
%"๋ชจ๋“  ๋ฌธ์ž์—ด"์„ ๋œปํ•ด์š” (0๊ธ€์ž ์ด์ƒ)

์˜ˆ์‹œ๋กœ ์„ค๋ช…ํ• ๊ฒŒ์š”:

@Query("select b from Board b where b.title like %?1%")

์œ„ ์ฟผ๋ฆฌ์—์„œ ?1์€ ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๊ณ , %๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜๋ฏธ๊ฐ€ ์žˆ์–ด์š”:

  • %?1% โ†’ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ๋ฌธ์ž์—ด

  • ์˜ˆ: ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ "์ œ๋ชฉ"์„ ๋„˜๊ธฐ๋ฉด
    โ†’ like '%์ œ๋ชฉ%'๊ฐ€ ๋˜์–ด
    โ†’ "ํ…Œ์ŠคํŠธ ์ œ๋ชฉ1" "๊ณต์ง€ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค" ๊ฐ™์€ ๊ฒƒ๋“ค์ด ๊ฒ€์ƒ‰๋ผ์š”.


2๏ธโƒฃ @Param ์ƒ๋žต ๊ฐ€๋Šฅํ•œ ์กฐ๊ฑด: ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์ด ๊ฐ™์œผ๋ฉด ์ƒ๋žต ๊ฐ€๋Šฅ

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜ ์ฝ”๋“œ:

@Query("select b from Board b where b.title like %:keyword%")
List<Board> searchByTitle(@Param("keyword") String keyword);

์—ฌ๊ธฐ์„œ:

  • :keyword โ† JPQL ์ฟผ๋ฆฌ ์•ˆ์—์„œ ์“ฐ๋Š” ์ด๋ฆ„
  • @Param("keyword") โ† ์ž๋ฐ” ์ฝ”๋“œ์—์„œ ์ด ์ด๋ฆ„์„ ๋ฐ”์ธ๋”ฉ

์ด๋•Œ ์ž๋ฐ”์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„(keyword)์ด JPQL ์•ˆ์˜ ์ด๋ฆ„(:keyword)๊ณผ ์™„์ „ํžˆ ๊ฐ™๋‹ค๋ฉด @Param("keyword")๋ฅผ ์ƒ๋žตํ•ด๋„ OK์˜ˆ์š”!


โœ… ์ƒ๋žต ๊ฐ€๋Šฅํ•œ ์˜ˆ:

@Query("select b from Board b where b.title like %:keyword%")
List<Board> searchByTitle(String keyword); // ์ƒ๋žต ๊ฐ€๋Šฅ: ๋ณ€์ˆ˜๋ช…์ด ๊ฐ™์Œ


โŒ ์ƒ๋žต ๋ถˆ๊ฐ€๋Šฅํ•œ ์˜ˆ (์ด๋ฆ„์ด ๋‹ค๋ฆ„):

@Query("select b from Board b where b.title like %:keyword%")
List<Board> searchByTitle(String title); // ๋ณ€์ˆ˜๋ช…(title)๊ณผ ํŒŒ๋ผ๋ฏธํ„ฐ(keyword)๊ฐ€ ๋‹ค๋ฅด๋‹ˆ๊นŒ ์ƒ๋žต ๋ถˆ๊ฐ€!

์ด ๊ฒฝ์šฐ์—๋Š” ๊ผญ ์ด๋ ‡๊ฒŒ ์จ์•ผ ํ•ด์š”:

List<Board> searchByTitle(@Param("keyword") String title);

๐Ÿ”š ์š”์•ฝ ์ •๋ฆฌ

๊ตฌ๋ถ„์„ค๋ช…
%SQL์—์„œ "์•„๋ฌด ๊ธ€์ž"๋ฅผ ์˜๋ฏธํ•จ. like %์ œ๋ชฉ% โ†’ ์ œ๋ชฉ์ด ํฌํ•จ๋œ ๋ชจ๋“  ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰
@Param ์ƒ๋žต๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„๊ณผ JPQL ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์ด ๊ฐ™์„ ๋•Œ๋งŒ ์ƒ๋žต ๊ฐ€๋Šฅ

์ฟผ๋ฆฌ๋ฌธ ๋„์–ด์“ฐ๊ธฐ ์ฃผ์˜ํ•˜๊ธฐ.

how to use multiple like conditions with @Query.

Letโ€™s clarify step by step. ๐Ÿ˜„


๐Ÿ”ธ You wrote:

List<Board> boardList = boardRepo.queryAnnotationTest1("์ œ๋ชฉ10", "333");

You expect this to run a JPQL query like:

select b from Board b 
where b.title like %?1% and ... like %?2%

But you canโ€™t write like %?1% directly like that.


โœ… Correct way to write JPQL with multiple like %...% patterns:

@Query("select b from Board b where b.title like %?1% and b.writer like %?2%")
List<Board> queryAnnotationTest1(String titleKeyword, String writerKeyword);

โœ… Yes! This is the correct way to use two like filters with position-based parameters (?1, ?2).


๐Ÿ’ก BUT: Even better โ†’ use string concatenation in JPQL

Spring Data JPA actually doesnโ€™t allow %?1% directly in all cases.
Instead, you should write it like this:

@Query("select b from Board b where b.title like concat('%', ?1, '%') and b.writer like concat('%', ?2, '%')")
List<Board> queryAnnotationTest1(String titleKeyword, String writerKeyword);

๐Ÿ”Ž Whatโ€™s happening here?

  • concat('%', ?1, '%') โ†’ dynamically builds '%์ œ๋ชฉ10%'
  • Safer and cleaner!

๐Ÿ” ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ๋ฒ„์ „ (optional):

@Query("select b from Board b where b.title like concat('%', :title, '%') and b.writer like concat('%', :writer, '%')")
List<Board> queryAnnotationTest1(@Param("title") String titleKeyword, @Param("writer") String writerKeyword);

โœ… Summary:

Your caseWhat to write
Want to filter title and writer both with %keyword%Use concat('%', ?1, '%') for position-based or :title with @Param for name-based

0๊ฐœ์˜ ๋Œ“๊ธ€