Аннотация @Query

Мы рассматривали запросы, генерируемые из имени метода. Так удобно писать простые запросы. Но если в запросе более 1-2 параметров или несколько join,  то удобнее использовать аннотацию @Query.
Для этого в репозитории создаем метод, аннотированный @Query, и внутри аннотации прописываем запрос — можно как JPQL, так и native SQL.

Ниже приведен пример.

Использование @Query

public interface PostRepository extends JpaRepository<Post, Long> {

    @Transactional(readOnly = true)
    @Query("select p from Post p where p.user.id=:id and p.title like :title")
    List<Post> findMySuperPosts(String title, long id);

}

Запрос выбирает посты данного пользователя по заголовку.

Посты и пользователи находятся в отношении @ManyToOne. Класс Post:

@Entity
@Data
@NoArgsConstructor
public class Post {
    @Id
    @GeneratedValue(generator = "sequence")
    private Long id;

    private String title;

    private String text;

    @ManyToOne
    private User user;
}

Класс User:

@Data
@Entity
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(generator = "sequence")
    private Long id;

    private String email;

    private String nickname;

    private String password;

    private String role = "ROLE_USER";

    private boolean locked = false;

}

Native Query

Такой же запрос можно написать на SQL, указав nativeQuery = true:

@Transactional(readOnly = true)
@Query(nativeQuery = true, value = "select * from post p where p.user_id=:id and p.title like :title")
List<Post> findMySuperPostsNative(String title, long id);

Pagination и Sorting

Можно получать результат постранично, указав в качестве параметра Pagable:

@Transactional(readOnly = true)
@Query("select p from Post p where p.title like :title")
List<Post> findSuperPosts(String title, Pageable pageable);

Создавать запрос страницы можно так:

Pageable pageable= PageRequest.of(0, 2, Sort.by("title"));

Выше приведен запрос первой страницы (страницы нумеруются с 0), при этом  на странице должно быть два элемента. Задано упорядочивание выборки по title. Подробнее о постраничном выводе и сортировке тут.

Формируемый SQL такой:

select post0_.id as id1_0_, post0_.text as text2_0_, post0_.title as title3_0_, post0_.user_id as user_id4_0_ 
from post post0_ 
where post0_.title like ? 
order by post0_.title asc 
limit ?

select user0_.id as id1_1_0_, user0_.email as email2_1_0_, user0_.locked as locked3_1_0_, user0_.nickname as nickname4_1_0_, user0_.password as password5_1_0_, user0_.role as role6_1_0_ 
from user user0_ where user0_.id=?

Аннотация  @Modifying

С помощью @Query можно не только читать, но и обновлять базу. Но в таких запросах необходима дополнительная к @Query аннотация @Modifying.

update

Пример обновления заголовков всех постов пользователя:

@Modifying
@Transactional
@Query("update Post p set p.title=:title where p.id=:id")
int setFixedPostTitleFor(String title, long id);
delete

Пример удаления всех постов пользователя:

@Modifying
@Transactional
@Query("delete from Post p where p.user.id=:id")
int deleteInBulkByUserId(long id);

Стоит отметить, что так посты удаляются пакетно, поскольку генерируется один SQL :

delete from post 
where user_id=?

Если же использовать генерируемый по имени метод, то посты удаляются по одному:

@Transactional
int  deleteByUserId(long id);

SQL отправляется свой для каждого поста:

Hibernate: delete from post where id=?
Hibernate: delete from post where id=?
Hibernate: delete from post where id=?

@Transactional 

Обратите внимание, что методы мы аннотировали @Transactional, поскольку писали их сами. (Если бы использовали стандартные методы репозитория, унаследованные от SimpleJpaRepository, то они уже @Transactional.)

Все методы должны быть в рамках транзакции, другое дело что @Transactional может быть на уровне сервиса или теста. Но в данном случае тест у нас не @Transactional, класс аннотирован @SpringBootTest, а не @DataJpaTest (внутри которого есть @Transactional). Это потому, что @DataJpaTest откатывает транзакции и некоторые SQL не логируются (возможно, потому что не выполняются?). А хотелось бы видеть все сгенерированные SQL.

@SpringBootTest
public class PostRepositoryTest {
    @Autowired
    private PostRepository postRepository;

    @DirtiesContext
    @DisplayName("посты должны удаляться  по user.id единым запросом в @Query")
    @Test
    void titlesShouldBeDeletedByIdInBulk() {
        int n = postRepository.deleteInBulkByUserId(1l);
        Assertions.assertEquals(3, n);
    }
       ....
}

А для того, чтобы подтвержденные транзакции не портили базу после каждого теста, каждый тест аннотирован @DirtiesContext — это пересоздает H2 базу. Не оптимально, зато наглядная консоль с выводом всех SQL-запросов.

Итоги

Исходный код есть на GitHub.

 

 

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *