Лекция 7: Написание пользовательских SQL запросов в Java

Основные подходы к созданию запросов в Java включают: автоматически генерируемые запросы из имени метода, аннотацию @Query для JPQL и нативных SQL запросов, Specification API для динамических запросов, QueryDSL для типобезопасных запросов и нативные запросы через EntityManager. Рассмотрим каждый из этих подходов подробно.

Автоматически генерируемые запросы

Spring Data JPA может автоматически генерировать запросы на основе имен методов репозитория. Это самый простой способ, не требующий написания JPQL или SQL.

public interface UserRepository extends JpaRepository<User, Long> {
    // Найти по email
    User findByEmail(String email);
    // Найти всех с фамилией, игнорируя регистр
    List<User> findByLastNameIgnoreCase(String lastName);
    // Найти по имени и фамилии
    List<User> findByFirstNameAndLastName(String firstName, String lastName);
    // Найти пользователей с зарплатой больше указанной
    List<User> findBySalaryGreaterThan(BigDecimal salary);
    // Найти пользователей с зарплатой в диапазоне
    List<User> findBySalaryBetween(BigDecimal minSalary, BigDecimal maxSalary);
    // Найти по подстроке в имени
    List<User> findByFirstNameContaining(String pattern);
    // Посчитать количество пользователей с указанной фамилией
    long countByLastName(String lastName);
    // Удалить пользователей по фамилии
    void deleteByLastName(String lastName);
}

Поддержка сортировки и пагинации

public interface UserRepository extends JpaRepository<User, Long> {
    // Сортировка по одному полю
    List<User> findByLastNameOrderByFirstNameAsc(String lastName);
    // Сортировка по нескольким полям
    List<User> findByLastNameOrderByFirstNameAscSalaryDesc(String lastName);
    // Пагинация
    Page<User> findByLastName(String lastName, Pageable pageable);
    // Сортировка через параметр Sort
    List<User> findByLastName(String lastName, Sort sort);
}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public Page<User> getUsersByLastName(String lastName, int page, int size) {
        PageRequest pageRequest = PageRequest.of(page, size, Sort.by("firstName").ascending());
        return userRepository.findByLastName(lastName, pageRequest);
    }
}

Аннотация @Query для JPQL и нативных запросов

Когда автоматически генерируемых запросов недостаточно, можно использовать аннотацию @Query.

JPQL запросы

public interface UserRepository extends JpaRepository<User, Long> {
    // Простой JPQL запрос
    @Query("SELECT u FROM User u WHERE u.email = ?1")
    User findByEmailAddress(String email);
    
    // Именованные параметры
    @Query("SELECT u FROM User u WHERE u.firstName = :firstName OR u.email = :email")
    List<User> findByFirstNameOrEmail(@Param("firstName") String firstName, @Param("email") String email);
    
    // JOIN в JPQL
    @Query("SELECT u FROM User u JOIN u.department d WHERE d.name = :departmentName")
    List<User> findByDepartmentName(@Param("departmentName") String departmentName);
    
    // Модифицирующий запрос
    @Modifying
    @Query("UPDATE User u SET u.salary = u.salary * 1.1 WHERE u.department.id = :deptId")
    int giveRaiseToDepartment(@Param("deptId") Long departmentId);
}

Нативные SQL запросы

public interface UserRepository extends JpaRepository<User, Long> {
    // Нативный запрос
    @Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
    User findByEmailNative(String email);
    
    // Нативный запрос с JOIN
    @Query(value = "SELECT u.* FROM users u JOIN departments d ON u.department_id = d.id WHERE d.name = :departmentName", nativeQuery = true)
    List<User> findByDepartmentNameNative(@Param("departmentName") String departmentName);
    
    // Нативный модифицирующий запрос
    @Modifying
    @Query(value = "UPDATE users SET salary = salary * 1.1 WHERE department_id = :deptId", nativeQuery = true)
    int giveRaiseToDepartmentNative(@Param("deptId") Long departmentId);
}

Проекции (DTO)

// Интерфейсная проекция
public interface UserNameOnly {
    String getFirstName();
    String getLastName();
}

public interface UserRepository extends JpaRepository<User, Long> {
    // Проекция через интерфейс
    @Query("SELECT u.firstName as firstName, u.lastName as lastName FROM User u WHERE u.department.id = :deptId")
    List<UserNameOnly> findUserNamesByDepartmentId(@Param("deptId") Long departmentId);
}

Specification API для динамических запросов

public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public List<User> findUsersByCriteria(String firstName, String lastName, BigDecimal minSalary) {
        return userRepository.findAll((root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (firstName != null) {
                predicates.add(cb.like(root.get("firstName"), "%" + firstName + "%"));
            }
            if (lastName != null) {
                predicates.add(cb.equal(root.get("lastName"), lastName));
            }
            if (minSalary != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("salary"), minSalary));
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        });
    }
}

QueryDSL для типобезопасных запросов

public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public List<User> findUsersByQueryDsl(String firstName, String lastName) {
        QUser user = QUser.user;
        BooleanBuilder predicate = new BooleanBuilder();
        if (firstName != null) {
            predicate.and(user.firstName.containsIgnoreCase(firstName));
        }
        if (lastName != null) {
            predicate.and(user.lastName.eq(lastName));
        }
        return (List<User>) userRepository.findAll(predicate);
    }
}

Нативные запросы через EntityManager

@Repository
public class CustomUserRepositoryImpl implements CustomUserRepository {
    @PersistenceContext
    private EntityManager em;
    
    @Override
    public List<User> findCustomUsers() {
        return em.createNativeQuery("SELECT * FROM users WHERE salary > 1000", User.class).getResultList();
    }
}

Заключение

Spring Data JPA предлагает множество способов создания запросов: от простых автоматически генерируемых до сложных динамических с Specification API и QueryDSL. Выбор метода зависит от сложности запроса и требований к гибкости и типобезопасности. Использование правильного подхода позволяет эффективно работать с данными, минимизируя boilerplate-код и снижая вероятность ошибок.