← Назад к вопросам
Как работает репозиторий Spring Data JPA?
1.0 Junior🔥 251 комментариев
#ORM и Hibernate#Spring Boot и Spring Data
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает репозиторий Spring Data JPA
Spring Data JPA — это абстракция над Hibernate для работы с БД. Репозиторий предоставляет CRUD операции и возможность писать кастомные запросы.
Основные компоненты
// 1. Сущность JPA
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String name;
@CreationTimestamp
private LocalDateTime createdAt;
}
// 2. Репозиторий Spring Data JPA
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
// Автоматически реализуется
}
Как работает под капотом
1. Spring создаёт Proxy для интерфейса UserRepository
↓
2. Proxy перехватывает вызовы методов
↓
3. Анализирует имя метода (findByEmail, findAllByName...)
↓
4. Генерирует JPQL или SQL запрос
↓
5. Выполняет запрос через Hibernate
↓
6. Возвращает результат
Method Query Derivation
Spring понимает методы по имени:
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
// Поиск по email
User findByEmail(String email);
// SQL: SELECT * FROM users WHERE email = ?
// Поиск по имени и почте
List<User> findByNameAndEmail(String name, String email);
// SQL: SELECT * FROM users WHERE name = ? AND email = ?
// Поиск с OR
List<User> findByNameOrEmail(String name, String email);
// SQL: SELECT * FROM users WHERE name = ? OR email = ?
// Поиск с сравнением
List<User> findByCreatedAtAfter(LocalDateTime date);
// SQL: SELECT * FROM users WHERE created_at > ?
// Поиск с LIKE
List<User> findByNameContaining(String name);
// SQL: SELECT * FROM users WHERE name LIKE '%?%'
// Сортировка
List<User> findByNameOrderByCreatedAtDesc(String name);
// SQL: SELECT * FROM users WHERE name = ? ORDER BY created_at DESC
// Ограничение результатов
List<User> findTop10ByOrderByCreatedAtDesc();
// SQL: SELECT * FROM users ORDER BY created_at DESC LIMIT 10
}
Использование @Query
Для сложных запросов используй аннотацию:
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
// JPQL (работает с сущностями)
@Query("SELECT u FROM User u WHERE u.email = :email")
User findByEmailCustom(@Param("email") String email);
// Native SQL (работает с таблицами)
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);
// С условиями
@Query("SELECT u FROM User u WHERE u.name LIKE %:name% AND u.createdAt > :date")
List<User> findByNameAndDate(
@Param("name") String name,
@Param("date") LocalDateTime date
);
// С JOIN
@Query("""n SELECT u FROM User u
JOIN u.posts p
WHERE p.title LIKE :title
""")
List<User> findUsersWithPostTitle(@Param("title") String title);
}
EntityManager и Hibernate
Под капотом работает так:
public class UserRepositoryImpl {
@Autowired
private EntityManager entityManager;
public User findByEmail(String email) {
// Spring Data генерирует этот код
return entityManager
.createQuery("SELECT u FROM User u WHERE u.email = :email", User.class)
.setParameter("email", email)
.getSingleResult();
}
public void save(User user) {
entityManager.persist(user);
}
public void update(User user) {
entityManager.merge(user);
}
public void delete(User user) {
entityManager.remove(user);
}
}
Кеширование и N+1 проблема
// ❌ ПЛОХО: N+1 запросы
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
@Query("SELECT u FROM User u")
List<User> findAll(); // Запрос 1: все пользователи
// Потом в коде:
List<User> users = userRepository.findAll();
for (User u : users) {
System.out.println(u.getPosts()); // Запрос 2, 3, 4... по одному на каждого
}
}
// ✅ ХОРОШО: JOIN FETCH
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.posts")
List<User> findAllWithPosts(); // Один запрос с JOIN
}
Транзакции
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional // Открывает транзакцию
public void createUser(String email, String name) {
User user = new User(email, name);
userRepository.save(user); // INSERT
// При выходе из метода транзакция коммитится
}
@Transactional(readOnly = true) // Только чтение
public User findUser(UUID id) {
return userRepository.findById(id).orElse(null);
}
@Transactional(rollbackFor = Exception.class)
public void transferMoney(UUID fromId, UUID toId, BigDecimal amount) {
// Если выкинется исключение — откат
User from = userRepository.findById(fromId).orElseThrow();
User to = userRepository.findById(toId).orElseThrow();
from.decreaseBalance(amount);
to.increaseBalance(amount);
userRepository.saveAll(List.of(from, to));
}
}
Batch операции
// ❌ НЕЭФФЕКТИВНО: 1000 отдельных INSERT
for (int i = 0; i < 1000; i++) {
User user = new User("user" + i);
userRepository.save(user);
}
// ✅ ЭФФЕКТИВНО: батч INSERT
List<User> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
users.add(new User("user" + i));
}
userRepository.saveAll(users); // Batch insert
// Или ещё лучше — native SQL
@Query(value = """n INSERT INTO users (email, name) VALUES
(:emails), (:names)
""", nativeQuery = true)
void batchInsert(@Param("emails") List<String> emails,
@Param("names") List<String> names);
Спецификации (Specifications)
// Для сложной фильтрации
@Repository
public interface UserRepository extends
JpaRepository<User, UUID>,
JpaSpecificationExecutor<User> {
}
// Спецификация
public class UserSpecifications {
public static Specification<User> hasEmail(String email) {
return (root, query, cb) -> cb.equal(root.get("email"), email);
}
public static Specification<User> createdAfter(LocalDateTime date) {
return (root, query, cb) -> cb.greaterThan(root.get("createdAt"), date);
}
}
// Использование
Specification<User> spec = UserSpecifications.hasEmail("john@example.com")
.and(UserSpecifications.createdAfter(LocalDateTime.now().minusDays(7)));
List<User> users = userRepository.findAll(spec);
Pagination и Sort
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
Page<User> findAll(Pageable pageable);
}
// Использование
PageRequest pageRequest = PageRequest.of(
0, // номер страницы (с 0)
20, // размер страницы
Sort.by("createdAt").descending()
);
Page<User> page = userRepository.findAll(pageRequest);
List<User> users = page.getContent();
int totalPages = page.getTotalPages();
long totalElements = page.getTotalElements();
Жизненный цикл сущности
ОТСУТСТВУЮЩАЯ (Transient)
↓ save()
УПРАВЛЯЕМАЯ (Managed) ← → БД
↓ ↓
DETACHED REMOVED
(отсоединена)(удалена)
В Managed состоянии Hibernate следит за изменениями.
При flush() (конец транзакции) отправляет UPDATE.
Итоги
- JpaRepository — предоставляет CRUD методы
- Method Query Derivation — генерирует запросы по имени
- @Query — для кастомных JPQL/SQL запросов
- @Transactional — управляет транзакциями
- Batch операции — используй saveAll для производительности
- JOIN FETCH — избегай N+1 проблем
- Specifications — для гибкой фильтрации
- Pagination — для больших результатов