← Назад к вопросам

Как работает репозиторий 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.

Итоги

  1. JpaRepository — предоставляет CRUD методы
  2. Method Query Derivation — генерирует запросы по имени
  3. @Query — для кастомных JPQL/SQL запросов
  4. @Transactional — управляет транзакциями
  5. Batch операции — используй saveAll для производительности
  6. JOIN FETCH — избегай N+1 проблем
  7. Specifications — для гибкой фильтрации
  8. Pagination — для больших результатов
Как работает репозиторий Spring Data JPA? | PrepBro