Какие знаешь способы делать запросы к базе данных через Spring Data?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы делать запросы к БД через Spring Data
Spring Data предоставляет мощный и гибкий API для работы с БД. Расскажу о различных способах, от самых простых до продвинутых.
1. Query Methods (Производные запросы)
Наиболее простой и удобный способ. Spring автоматически генерирует реализацию по имени метода.
public interface UserRepository extends JpaRepository<User, UUID> {
// Простой поиск
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
// Поиск с условиями
List<User> findByAgeGreaterThan(int age);
List<User> findByAgeBetween(int minAge, int maxAge);
List<User> findByStatusEquals(UserStatus status);
// Contains / StartingWith / EndingWith (для строк)
List<User> findByUsernameContaining(String username);
List<User> findByUsernameStartingWith(String prefix);
List<User> findByEmailEndingWith(String domain);
// Case insensitive
Optional<User> findByUsernameIgnoreCase(String username);
// NOT условия
List<User> findByStatusNot(UserStatus status);
List<User> findByEmailIsNull();
List<User> findByEmailIsNotNull();
// Boolean проверки
List<User> findByActiveTrue();
List<User> findByActiveFalse();
// Сортировка
List<User> findByActiveOrderByCreatedAtDesc(boolean active);
List<User> findByStatusOrderByUsernameAsc(UserStatus status);
// Лимит результатов
List<User> findFirst10ByOrderByCreatedAtDesc();
Optional<User> findFirstByOrderByUpdatedAtDesc();
}
2. @Query Annotation (JPQL)
Для более сложных запросов используем JPQL с явно написанными SQL запросами.
public interface UserRepository extends JpaRepository<User, UUID> {
// Базовый запрос
@Query("SELECT u FROM User u WHERE u.username = :username")
Optional<User> findByUsernameJpql(@Param("username") String username);
// С JOIN
@Query("SELECT u FROM User u " +
"JOIN FETCH u.profile p " +
"WHERE u.status = :status")
List<User> findActiveUsersWithProfiles(
@Param("status") UserStatus status
);
// С GROUP BY и агрегацией
@Query("SELECT new map(u.country as country, COUNT(u) as count) " +
"FROM User u GROUP BY u.country")
List<Map<String, Object>> countUsersByCountry();
// С CASE WHEN
@Query("SELECT new map(u.id as id, u.username as username, " +
"CASE WHEN u.age < 18 THEN 'minor' ELSE 'adult' END as ageGroup) " +
"FROM User u")
List<Map<String, Object>> findUsersWithAgeGroup();
// Subquery
@Query("SELECT u FROM User u WHERE u.id IN " +
"(SELECT p.user.id FROM Purchase p WHERE p.totalPrice > :amount)")
List<User> findUsersByMinimumPurchaseAmount(
@Param("amount") BigDecimal amount
);
}
3. Native Queries (SQL)
Для специфичных для БД оптимизаций или когда JPQL недостаточно.
public interface UserRepository extends JpaRepository<User, UUID> {
// Простой native query
@Query(
value = "SELECT * FROM users WHERE created_at > :date",
nativeQuery = true
)
List<User> findRecentUsers(@Param("date") LocalDateTime date);
// С projection
@Query(
value = "SELECT id, username, email FROM users WHERE active = true",
nativeQuery = true
)
List<UserProjection> findActiveUsersProjection();
// Сложный запрос с оконными функциями
@Query(
value = "SELECT id, username, " +
"ROW_NUMBER() OVER (ORDER BY created_at DESC) as row_num " +
"FROM users",
nativeQuery = true
)
List<UserWithRowNumber> findAllWithRowNumbers();
}
4. Specifications (Dynamic Queries)
Для построения динамичных запросов во время выполнения.
// Спецификация для User
public class UserSpecifications {
public static Specification<User> hasUsername(String username) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.like(
criteriaBuilder.lower(root.get("username")),
"%" + username.toLowerCase() + "%"
);
}
public static Specification<User> hasEmail(String email) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("email"), email);
}
public static Specification<User> hasStatus(UserStatus status) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.equal(root.get("status"), status);
}
public static Specification<User> createdAfter(LocalDateTime date) {
return (root, query, criteriaBuilder) ->
criteriaBuilder.greaterThan(root.get("createdAt"), date);
}
}
// Repository с поддержкой Specification
public interface UserRepository extends JpaRepository<User, UUID>,
JpaSpecificationExecutor<User> {
}
// Использование
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> searchUsers(UserSearchCriteria criteria) {
Specification<User> spec = Specification.where(null);
if (criteria.getUsername() != null) {
spec = spec.and(UserSpecifications.hasUsername(criteria.getUsername()));
}
if (criteria.getStatus() != null) {
spec = spec.and(UserSpecifications.hasStatus(criteria.getStatus()));
}
if (criteria.getCreatedAfter() != null) {
spec = spec.and(UserSpecifications.createdAfter(criteria.getCreatedAfter()));
}
return userRepository.findAll(spec);
}
}
5. Criteria API (Type-safe Queries)
Полностью type-safe способ строить запросы программно.
@Service
public class UserSearchService {
@PersistenceContext
private EntityManager entityManager;
public List<User> findUsersWithCriteria(String username, UserStatus status) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (username != null && !username.isEmpty()) {
predicates.add(
cb.like(cb.lower(root.get("username")),
"%" + username.toLowerCase() + "%")
);
}
if (status != null) {
predicates.add(
cb.equal(root.get("status"), status)
);
}
if (!predicates.isEmpty()) {
query.where(cb.and(predicates.toArray(new Predicate[0])));
}
return entityManager.createQuery(query).getResultList();
}
}
6. QueryDSL
Мощная библиотека для type-safe запросов с удобным API.
// Нужна зависимость
// <dependency>
// <groupId>com.querydsl</groupId>
// <artifactId>querydsl-jpa</artifactId>
// </dependency>
@Repository
public interface UserRepository extends JpaRepository<User, UUID>,
QuerydslPredicateExecutor<User> {
}
// Использование
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> searchWithQueryDSL(String username, int minAge) {
QUser user = QUser.user;
BooleanBuilder predicate = new BooleanBuilder();
if (username != null) {
predicate.and(user.username.containsIgnoreCase(username));
}
predicate.and(user.age.goe(minAge));
predicate.and(user.active.isTrue());
return userRepository.findAll(predicate);
}
}
7. Custom Repository Methods
Для совсем сложных случаев, когда нужен полный контроль.
public interface UserRepositoryCustom {
List<User> findWithComplexLogic(String criteria);
}
public class UserRepositoryImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<User> findWithComplexLogic(String criteria) {
String jpql = "SELECT u FROM User u " +
"WHERE u.username LIKE :criteria " +
"OR u.email LIKE :criteria " +
"ORDER BY u.createdAt DESC";
return entityManager.createQuery(jpql, User.class)
.setParameter("criteria", "%" + criteria + "%")
.setMaxResults(100)
.getResultList();
}
}
public interface UserRepository extends JpaRepository<User, UUID>,
UserRepositoryCustom {
}
8. Pagination и Sorting
public interface UserRepository extends JpaRepository<User, UUID> {
Page<User> findByStatus(UserStatus status, Pageable pageable);
Slice<User> findByActiveTrue(Pageable pageable);
}
// Использование
@GetMapping("/users")
public Page<UserDTO> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "createdAt,desc") String[] sort
) {
Sort sortOrder = Sort.by(Sort.Direction.DESC, "createdAt");
Pageable pageable = PageRequest.of(page, size, sortOrder);
return userRepository.findByStatus(ACTIVE, pageable)
.map(UserDTO::fromEntity);
}
9. Batch Operations
public interface UserRepository extends JpaRepository<User, UUID> {
@Modifying
@Transactional
@Query("UPDATE User u SET u.status = :newStatus WHERE u.status = :oldStatus")
int updateStatus(
@Param("oldStatus") UserStatus oldStatus,
@Param("newStatus") UserStatus newStatus
);
@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.createdAt < :date")
int deleteOlderThan(@Param("date") LocalDateTime date);
}
10. Projections
Если нужны только определённые поля.
// DTO Projection
public interface UserSummary {
UUID getId();
String getUsername();
String getEmail();
}
public interface UserRepository extends JpaRepository<User, UUID> {
List<UserSummary> findByStatus(UserStatus status);
@Query("SELECT new com.example.dto.UserDTO(u.id, u.username, u.email) " +
"FROM User u WHERE u.active = true")
List<UserDTO> findActiveUsersDTO();
}
Сравнение методов
| Метод | Простота | Производительность | Гибкость |
|---|---|---|---|
| Query Methods | Очень высокая | Хорошая | Средняя |
| @Query (JPQL) | Высокая | Хорошая | Высокая |
| Native SQL | Средняя | Отличная | Очень высокая |
| Specifications | Средняя | Хорошая | Очень высокая |
| Criteria API | Низкая | Хорошая | Очень высокая |
| QueryDSL | Средняя | Хорошая | Высокая |
Рекомендации
- Начинай с Query Methods — они покрывают 80% случаев
- Переходи на @Query когда методов недостаточно
- Используй Native SQL только для БД-специфичных оптимизаций
- Specifications для динамических запросов из UI фильтров
- QueryDSL когда нужна мощь и читаемость
Заключение
Spring Data предоставляет инструменты для любого сценария. Выбор метода зависит от сложности запроса и требований к гибкости.