JavaRush /Курси /Модуль 5. Spring /Робота з репозиторіями: CrudRepository, JpaRepository

Робота з репозиторіями: CrudRepository, JpaRepository

Модуль 5. Spring
Рівень 5 , Лекція 3
Відкрита

Ласкаво просимо в одну з найкорисніших і найпопулярніших тем Spring Data — роботу з репозиторіями! У цій лекції ми розберемо, що таке репозиторії, навіщо вони потрібні, чим відрізняються CrudRepository і JpaRepository, як вони спрощують нам життя, і, звісно, впровадимо їх в наше застосунку. Все це буде корисно не тільки в ваших навчальних проєктах, а й у реальній роботі, бо репозиторії — це основа взаємодії з базою даних в Spring.


Що таке репозиторій?

Репозиторій (Repository) у цьому контексті — це шар застосунку, який відповідає за взаємодію з базою даних. Він абстрагує базові операції на кшталт створення, читання, оновлення і видалення даних (так звані CRUD-операції).

Якщо раніше взаємодія з базою даних вимагала ручного написання SQL-запитів, то Spring Data спрощує цей процес у рази. Тобі більше не потрібно "мочити руки" в SQL'і (хоча ніхто не забороняє, якщо хочеться). Замість цього репозиторії дозволяють фокусуватися на бізнес-логіці, довіривши рутинні задачі Spring.


CrudRepository і JpaRepository: відмінності

Spring Data надає кілька інтерфейсів для роботи з базою даних, але два з них використовуються найчастіше:

CrudRepository

CrudRepository — це базовий інтерфейс для виконання CRUD-операцій. Ось його основні методи:


public interface CrudRepository<T, ID> {
<S extends T> S save(S entity);           // Збереження (або оновлення) сутності
  Optional<T> findById(ID id);             // Пошук сутності за ідентифікатором
    boolean existsById(ID id);               // Перевірка на існування запису
    Iterable<T> findAll();                   // Отримати всі сутності
      void deleteById(ID id);                  // Видалити за ідентифікатором
      void delete(T entity);                   // Видалити передану сутність
}

Коли використовувати: якщо потрібно реалізувати лише базові CRUD-операції.

JpaRepository

JpaRepository розширює CrudRepository і додає нові можливості:

  • Пагінація (розбивка великого списку даних на сторінки, як у Google: 10 результатів на сторінці)
  • Сортування даних
  • JPA-специфічні методи на кшталт flush() для керування станом persistence context

Приклад пагінації:


// Отримати перших 20 користувачів, відсортованих за ім'ям
Page<User> users = repository.findAll(PageRequest.of(0, 20, Sort.by("name")));

Приклади методів з JpaRepository:


public interface JpaRepository<T, ID> extends CrudRepository<T, ID> {
List<T> findAll(Sort sort);          // Сортування результатів
  Page<T> findAll(Pageable pageable);  // Пагінація результатів
    void flush();                        // Збереження змін у базу даних
    <S extends T> S saveAndFlush(S entity); // Зберегти і одразу скинути контекст
}

Коли використовувати: якщо потрібні додаткові можливості DAO-рівня, такі як сортування або пагінація.


Використання репозиторіїв на практиці

Тепер, коли теорія стала трохи зрозумілішою, перейдемо до практики! Ми будемо працювати з застосунком, де вже є сутність User.

1. Сутність User

Наш клас User — це найпростіша сутність, яка зберігатиме дані про користувача:


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    public User() {}

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Геттери і сеттери
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

2. Створення репозиторію

Тепер створимо інтерфейс репозиторію. Spring Data зробить усе інше за нас:


import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    // Тут можна додавати кастомні методи
}

Ось і все! Репозиторій готовий. Тепер у нас є доступ до всіх CRUD-операцій, які надає JpaRepository.


Виконання CRUD-операцій

Спробуємо використати наш репозиторій для базових операцій.

Збереження користувача


@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(String name, String email) {
        User user = new User(name, email);
        return userRepository.save(user);
    }
}

Отримання користувача за ID


public Optional<User> getUserById(Long id) {
    return userRepository.findById(id);
}

Оновлення даних користувача


public User updateUser(Long id, String name, String email) {
    User user = userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
    user.setName(name);
    user.setEmail(email);
    return userRepository.save(user);
}

Видалення користувача


public void deleteUser(Long id) {
    userRepository.deleteById(id);
}

Використання в REST-контролері Створимо контролер, який буде використовувати наш UserService:


@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.createUser(user.getName(), user.getEmail());
        return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userService.getUserById(id)
            .map(user -> new ResponseEntity<>(user, HttpStatus.OK))
            .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        User updatedUser = userService.updateUser(id, user.getName(), user.getEmail());
        return new ResponseEntity<>(updatedUser, HttpStatus.OK);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

Кастомні методи репозиторіїв

Іноді стандартних операцій недостатньо, і потрібно написати щось своє. Наприклад, пошук користувача за ім'ям.

Реалізація з використанням імені методу


List<User> findByName(String name);

Spring Data "магічно" розуміє, що ти хочеш знайти користувачів за ім'ям, завдяки угодам про іменування методів.

Реалізація з використанням @Query


@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

Пагінація Якщо в тебе багато даних, ти можеш повертати їх невеликими порціями:


Page<User> findAll(Pageable pageable);

Використання:


Pageable pageable = PageRequest.of(0, 10, Sort.by("name"));
Page<User> page = userRepository.findAll(pageable);

Можливі помилки і як їх уникнути

Ось кілька поширених помилок, про які варто пам'ятати:

  • Помилка "No EntityManager with actual transaction": не забудь додати анотацію @Transactional у сервісні методи, які змінюють дані.
  • Відсутність конструктора без параметрів у сутності: переконайся, що у класів-сутностей є публічний конструктор без параметрів.
  • Проблема з lazy-завантаженням (LazyInitializationException): будь обережний з lazy-завантаженням і намагайся використовувати FetchType.LAZY тільки там, де це потрібно.

Навіщо все це потрібно?

Репозиторії — це класний спосіб зосередитися на логіці застосунку, не витрачаючи час на рутину. Працювати з репозиторіями швидше, безпечніше і набагато простіше, ніж писати SQL-запити вручну.

У реальній роботі ти будеш постійно використовувати репозиторії, хай то в простому CRUD-застосунку, хай то в складній мікросервісній системі з десятками баз даних.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ