در ابتدا نگاهی میکنیم به کلمات کلیدی که میتوان با آن کوئری ایجاد کرد و بعد به استفاده از Query@ و name و parameter میپردازیم
فرض کنید یک جدول بنام Movie و یک کلاس Entity به همین نام داریم :
@Entity public class Movie { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; private String title; private String director; private String rating; private int duration; // standard getters and setters }
و یکسری نمونه از آن ذخیره میکنیم :
INSERT INTO movie(id, title, director, rating, duration) VALUES(1, 'Godzilla: King of the Monsters', ' Michael Dougherty', 'PG-13', 132); INSERT INTO movie(id, title, director, rating, duration) VALUES(2, 'Avengers: Endgame', 'Anthony Russo', 'PG-13', 181); INSERT INTO movie(id, title, director, rating, duration) VALUES(3, 'Captain Marvel', 'Anna Boden', 'PG-13', 123); INSERT INTO movie(id, title, director, rating, duration) VALUES(4, 'Dumbo', 'Tim Burton', 'PG', 112); INSERT INTO movie(id, title, director, rating, duration) VALUES(5, 'Booksmart', 'Olivia Wilde', 'R', 102); INSERT INTO movie(id, title, director, rating, duration) VALUES(6, 'Aladdin', 'Guy Ritchie', 'PG', 128); INSERT INTO movie(id, title, director, rating, duration) VALUES(7, 'The Sun Is Also a Star', 'Ry Russo-Young', 'PG-13', 100);
حال ببینیم برای پیدا کردن فیلم مورد نظر و کوئری آن چه راه کار هایی داریم :
ابتدا با چند کلمه کلیدی آشنا شویم :
- Containing
- Contains
- IsContaining
- Like
این کلمات کلیدی توسط Spring شناخته شده هستند و میتوانیم در Repository از آنها برای ایجاد متد های Repository استفاده کنیم
کوئری Like زیر در در نظر بگیرید :
SELECT * FROM movie WHERE title LIKE '%in%';
حال متدهایی برای استفاده از کلمات کلیدی تعریف میکنیم که Spring آنها را میشناسد و برای ما در پشت پرده پیاده سازی میکند :
List<Movie> findByTitleContaining(String title); List<Movie> findByTitleContains(String title); List<Movie> findByTitleIsContaining(String title);
حالا میتوانیم از این متد ها استفاده کنیم :
List<Movie> results = movieRepository.findByTitleContaining("in"); assertEquals(3, results.size()); results = movieRepository.findByTitleIsContaining("in"); assertEquals(3, results.size()); results = movieRepository.findByTitleContains("in"); assertEquals(3, results.size());
*میتوان انتطار داشت که هر یک از این سه متد جوابی مشابه هم داشته باشند
اما کلمه کلیدی Like جا موند ، Spring همچنین این کلمه کلیدی را میشناسد ولی میتواند رفتاری متفاوت داشته باشد چون میتوانیم از wildcard استفاده کنیم
شکل کلی :
List<Movie> findByTitleLike(String title);
و استفاده از آن همراه با wildcard :
results = movieRepository.findByTitleLike("%in%"); assertEquals(3, results.size());
شکل دیگری استفاده از wildcard با استفاده از کلمه کلیدی StartsWith :
List<Movie> findByRatingStartsWith(String rating);
با استفاده از StartsWith میتوانیم کوئری مشابه زیر را استفاده کنیم :
SELECT * FROM Movie WHERE Rating LIKE 'PG%';
و تست استفاده از آن :
List<Movie> results = movieRepository.findByRatingStartsWith("PG"); assertEquals(6, results.size());
برعکس StartsWith که با استفاده از کلمه کلیدی EndsWith میباشد :
SELECT * FROM Movie WHERE director LIKE '%Burton';
List<Movie> findByDirectorEndsWith(String director);
List<Movie> results = movieRepository.findByDirectorEndsWith("Burton"); assertEquals(1, results.size());
کلمه کلیدی IgnoreCase برای مواقعی که میخواهیم در کوئری بزرگ و کوچک بودن حروف را نادیده بگیرد :
List<Movie> findByTitleContainingIgnoreCase(String title);
List<Movie> results = movieRepository.findByTitleContainingIgnoreCase("the"); assertEquals(2, results.size());
ترکیب کوئری های جستجو با NOT برای مواقعی که میخواهیم نتیجه به غیر از شرایط ذکر شده باشد :
ما میتوانیم Not را بصورت NotContains, NotContaining, NotLike استفاده کنیم
List<Movie> findByRatingNotContaining(String rating);
List<Movie> results = movieRepository.findByRatingNotContaining("PG"); assertEquals(1, results.size());
List<Movie> findByDirectorNotLike(String director);
List<Movie> results = movieRepository.findByDirectorNotLike("An%"); assertEquals(5, results.size());
استفاده از Query@ :
گاهی اوقات کوئری ما پیچیدگی زیادی دارد و پارامتر هایی را به عنوان ورودی دریافت میکند و نیاز داریم آنرا بصورت سفارشی طراحی کنیم و روی یک متد تعریف کنیم
Named Parameter :
پارامتر هایی هستند که با نام مشخص میشوند و هم نام فیلد مورد نظر در کلاس Entity است
@Query("SELECT m FROM Movie m WHERE m.title LIKE %:title%") List<Movie> searchByTitleLike(@Param("title") String title);
* به سینتکس دو نقطه ':' قبل از Named Parameter توجه کنید
در این کوئری از wildcard هم استفاده شده است
این کوئری مشابه متد findByTitleContaining که قبلا صحبت کردیم رفتار میکند که با Query@ پیاده سازی شده
Ordered Parameter :
پارامتر هایی هستند که بر اساس شماره الویت بندی شده اند و طبق آرگومان های ورودی متد نیز جای گزاری میشوند
@Query("SELECT m FROM Movie m WHERE m.rating LIKE ?1%") List<Movie> searchByRatingStartsWith(String rating);
* به سینتکس ? و بعد یک عدد بعد از علامت سوال دقت کنید
این کوئری مشابه findByRatingStartsWith است که قبلا بحث شد و از wildcard هم استفاده شده
تست آن :
List<Movie> results = movieRepository.searchByRatingStartsWith("PG"); assertEquals(6, results.size());
** هنگامی که ما از Ordered Parameter استفاده میکنیم باید احتیاط کنیم چون ورودی که به عنوان پارامتر جای گزاری میشود از نظر SQL Injection چک نمیشود و میتواند بسیار خطرناک باشد برای همین میتوانیم از escape method در SpeL استفاده کنیم
اگر از Spring Boot 2.4.1 به بعد استفاده میکنید میتوانیم از SpEL استفاده کنیم و خطر SQL Injection را پوشش دهیم
@Query("SELECT m FROM Movie m WHERE m.director LIKE %?#{escape([0])} escape ?#{escapeCharacter()}") List<Movie> searchByDirectorEndsWith(String director);
تست :
List<Movie> results = movieRepository.searchByDirectorEndsWith("Burton"); assertEquals(1, results.size());