SQL-запросы для операций с данными в большинстве своём однообразны. А что умные программисты делают с однообразными операциями? Правильно, автоматизируют!
В мире Spring, репозиторий (repository) – это специальный компонент, отвечающий за взаимодействие с базой данных. Он предоставляет методы для выполнения операций типа CRUD (Create, Read, Update, Delete). Давайте разберёмся, как создать репозитории для сущностей.
Создание интерфейса Repository
После создания классов-сущностей встаёт вопрос: а что нам с ними делать? Откуда их загружать, куда их сохранять? На эти вопросы даёт ответ слой repository – один из слоёв структуры Spring-проекта.
В общем-то, вот как взаимодействуют между собой БД, репозитории, сервисы и контроллеры:
В Spring для создания репозитория используется интерфейс. Не класс. Вы, наверное, подумаете – интерфейс, значит, его придётся где-то реализовывать. Но нет! Это невероятно, но вы просто напишете интерфейс, а Spring его реализует самостоятельно. Да, Spring Data JPA (Java Persistence API) возьмёт на себя эту задачу и автоматически предоставит реализацию во время выполнения.
Для того, чтобы создать репозиторий, нужно унаследовать интерфейс от одного из интерфейсов-шаблонов репозиториев. В Spring есть два основных интерфейса для репозиториев: CrudRepository и JpaRepository. Основное отличие в том, что JpaRepository предоставляет дополнительные методы для работы с JPA. В общем, можете просто не заморачиваться с этим и использовать JpaRepository:
1 2 3 |
@Repository public interface UserRepository extends JpaRepository<User, Long> { } |
В треугольных скобках после JpaRepository нужно указать 2 типа. Первый – это тип сущности (т.е. класс пользователя, User). Второй – тип идентификатора. Так как у класса User поле id имеет тип данных Long, то указываем здесь Long.
Что дальше? Так и всё. Хе-хе. Нет, правда. Да, даже методы можно не писать. И уж точно не реализовывать интерфейс. Думаете, он слишком пустой? Ну, давайте проверим.
Создадим в другом месте в коде переменную типа UserRepository. И поставим точку – чтобы среда разработки IntelliJ IDEA заботливо предоставила список подсказок, что можно вызвать у объекта UserRepository. Вы тоже это видите?
Ага, именно так. Весь этот огромный список – это автоматически сгенерированные методы для оперирования с сущностью User. И это всё уже готово. Причём видно, что некоторые методы сразу сделаны именно под класс сущности User. Например, метод findAll() возвращает List<User>. Можно удалить пользователя по id методом deleteByID(Long id). А можно найти его по id методом findById(Long id). Можно сохранить или проверить на существование. Но это ещё не всё. Это не все возможности, которые предоставляет Spring Data и JpaRepository в частности.
Что примечательно, так это то, что метод save() у репозитория может работать двумя способами: на INSERT и на UPDATE. Создание новой строки в таблице он производит, если идентификатор (id) объекта сущности пуст (равен null). А если нет – тогда он проводит обновление уже имеющейся сущности с имеющимся id.
Давайте узнаем, как создавать свои методы в репозитории.
Собственные методы
Итак, хотя JpaRepository воистину творит магию даже при пустом интерфейсе, его может не хватить для задач вашего проекта. Но этот интерфейс легко дополнить. Дело в том, что даже добавление новых методов не потребует их реализации. Spring Data JPA предоставляет механизм создания собственных методов в репозитории на основе соглашений именования. Эти методы автоматически преобразуются в соответствующие запросы базы данных.
То есть если вы хотите осуществить поиск по каким-либо критериям, вы начинаете метод со слов “findBy“. И дальше среда разработки вам подскажет варианты в зависимости от полей, имеющихся в сущности:
Так как у класса User есть несколько полей (id, email, password, username, posts), то по ним репозиторий и предлагает искать. С этими полями, как видно в подсказках IDE, можно делать практически всё что угодно. Например, с email среда разработки предлагает и простой поиск совпадения, и email-ы, содержащие подстроку, и пустые email-ы, и email-ы, которые заканчиваются подстрокой… В общем, полёт фантазии. Вы можете выбирать, что вам нужно для вашей задачи. Но чаще всего дело ограничивается простым поиском по полю.
Например, метод поиска по email-у будет выглядеть так:
1 2 3 4 |
@Repository public interface UserRepository extends JpaRepository<User, Long> { public User findByEmail(String email); } |
Кстати, здесь уже не список пользователей, List<User>, а просто User, так как мы уверены, что у каждого пользователя будет уникальный email. Как и раньше – метод реализовывать не нужно. Он будет сделан автоматически. SQL-запросы тоже. Ну красота, не правда ли?
Можно комбинировать несколько условий. Это делается при помощи слова And (если нужно одновременное совпадение). Также можно с помощью слова Or помечать, что должно выполняться одно из условий.
1 2 3 4 5 |
@Repository public interface UserRepository extends JpaRepository<User, Long> { public List<User> findByEmailOrUsername(String email, String password); public List<User> findByEmailContainsOrUsernameContains(String emailSubstring, String usernameSubstring); } |
Вот ещё парочка примеров методов:
1 2 3 4 5 |
public void deleteByEmail(String email); public List<User> findByPasswordIsEmpty(); public User findTopByEmailContains(String emailSubstring); public List<User> findByIdGreaterThan(Long idToCompare); |
Запросы с @Query
Соглашения именования в Spring Data JPA позволяют создавать множество методов без необходимости написания явных запросов SQL. Однако в случае более сложных запросов всегда есть возможность использовать аннотацию @Query для написания своих SQL-запросов.
Например, поиск по возрасту:
1 2 3 4 |
public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT u FROM users u WHERE u.age > :age") List<User> findUsersByAgeGreaterThan(@Param("age") int age); } |
Здесь u – это алиас (псевдоним) таблицы пользователей: u представляет объект пользователя (User), который является экземпляром класса, а не непосредственно таблицей в базе данных. Таким образом, запрос ассоциируется с объектами сущности User.
Если вы хотите написать привычный SQL-запрос без вот этих вот алиасов, нужно установить параметр nativeQuery в true:
1 2 |
@Query(value = "SELECT * FROM users WHERE age > :age", nativeQuery = true) List<User> findUsersByAgeGreaterThan(@Param("age") int age); |
В основном @Query используется для сложных запросов, но используется редко. Для запросов, по сложности сопоставимых с такими:
1 2 |
@Query("SELECT u FROM User u WHERE u.age > :age AND u.lastName = :lastName AND u.email LIKE %:domain%") List<User> findUsersByAgeAndLastNameAndEmailDomain(@Param("age") int age, @Param("lastName") String lastName, @Param("domain") String domain); |
Пример с users и posts
Вот мы и подошли к созданию репозиториев для нашего примера: для созданных ранее в БД таблиц users и posts, которым соответствуют сущности User и Post.
Для пользователя User практически достаточно методов из JpaRepository. Введём лишь один дополнительный метод, позволяющий искать по email-у:
1 2 3 4 |
@Repository public interface UserRepository extends JpaRepository<User, Long> { public User findByEmail(String email); } |
Для записи Post также почти нет необходимости вводить новые методы. Только один – получение постов от конкретного пользователя.
1 2 3 4 |
@Repository public interface PostRepository extends JpaRepository<Post, Long> { public List<Post> findByUserId(Long userId); } |
Вот так и происходит работа с БД средствами Spring Data. По-моему, это прикольно и удобно. Далее репозитории будут использоваться сервисами, которые являются оплотом бизнес-логики Spring-приложения.