← Назад к вопросам
Как устроена технология Spring Data?
2.0 Middle🔥 171 комментариев
#Spring Boot и Spring Data
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как устроена технология Spring Data
Spring Data — это проект Spring, который упрощает работу с данными (базами данных, кешами, поисковыми системами) через единый API. Основная идея: минимум кода для доступа к данным.
Архитектура Spring Data
┌─────────────────────────────────────┐
│ Spring Data Modules │
├─────────────────────────────────────┤
│ Spring Data JPA │
│ Spring Data MongoDB │
│ Spring Data Redis │
│ Spring Data Elasticsearch │
│ Spring Data REST │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Spring Data Commons │
│ (Repository, CrudRepository, etc) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ ORM / Database Drivers │
│ (Hibernate, MongoDB driver, Redis) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Underlying Data Store │
│ (PostgreSQL, MySQL, MongoDB, etc) │
└─────────────────────────────────────┘
1. Repository Pattern
Основное понятие — Repository интерфейс:
// Создаёшь интерфейс, Spring генерирует реализацию
public interface UserRepository extends JpaRepository<User, Long> {
// CRUD операции уже встроены (save, findById, delete, etc)
// Кастомные методы с query generation
List<User> findByEmail(String email);
List<User> findByAgeGreaterThan(int age);
List<User> findByFirstNameAndLastName(String firstName, String lastName);
}
// Использование:
@Service
public class UserService {
@Autowired
private UserRepository repository;
public void saveUser(User user) {
repository.save(user); // INSERT или UPDATE
}
public User getUser(Long id) {
return repository.findById(id).orElse(null); // SELECT
}
public List<User> searchByEmail(String email) {
return repository.findByEmail(email); // Query generation!
}
}
2. Query Generation
Spring Data парсит имена методов и генерирует SQL:
public interface ProductRepository extends JpaRepository<Product, Long> {
// Метод: SQL:
findByName(String name) → SELECT * FROM product WHERE name = ?
findByPriceGreaterThan(double) → SELECT * FROM product WHERE price > ?
findByPriceBetween(d, d) → SELECT * FROM product WHERE price BETWEEN ? AND ?
findByNameAndCategory(s, s) → SELECT * FROM product WHERE name = ? AND category = ?
findByNameOrEmail(s, s) → SELECT * FROM product WHERE name = ? OR email = ?
findByNameStartingWith(s) → SELECT * FROM product WHERE name LIKE ?
findByNameContaining(s) → SELECT * FROM product WHERE name LIKE %?%
findByNameIgnoreCase(s) → SELECT * FROM product WHERE LOWER(name) = LOWER(?)
findByNameOrderByPrice(s) → SELECT * FROM product WHERE name = ? ORDER BY price
findTop10ByPrice() → SELECT * FROM product ORDER BY price LIMIT 10
}
3. Иерархия Repository интерфейсов
Repository (marker interface)
├── CrudRepository
│ ├── PagingAndSortingRepository
│ │ └── JpaRepository (самый полный, рекомендуется)
│ └── ReactiveCrudRepository
│
└── ListCrudRepository (возвращает List вместо Iterable)
Что даёт каждый:
// Repository — пустой интерфейс, только маркер
public interface Repository<T, ID> {}
// CrudRepository — CRUD операции
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID id);
Iterable<T> findAll();
long count();
void deleteById(ID id);
// ...
}
// PagingAndSortingRepository — добавляет пагинацию и сортировку
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Page<T> findAll(Pageable pageable);
Iterable<T> findAll(Sort sort);
}
// JpaRepository — добавляет batch операции и flush
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
<S extends T> List<S> saveAll(Iterable<S> entities);
void flush();
<S extends T> S saveAndFlush(S entity);
void deleteInBatch(Iterable<T> entities);
List<T> getById(Iterable<ID> ids);
}
4. CRUD Example
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// CREATE (INSERT)
public User createUser(String name, String email) {
User user = new User(name, email);
return userRepository.save(user); // INSERT
}
// READ (SELECT)
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
// UPDATE
public User updateUser(Long id, String email) {
User user = getUserById(id);
user.setEmail(email);
return userRepository.save(user); // UPDATE (same method as INSERT)
}
// DELETE
public void deleteUser(Long id) {
userRepository.deleteById(id); // DELETE
}
// LIST ALL
public List<User> getAllUsers() {
return userRepository.findAll(); // SELECT * FROM user
}
}
5. Пагинация и сортировка
public interface UserRepository extends JpaRepository<User, Long> {
// Встроенная поддержка пагинации
}
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository repository;
@GetMapping
public Page<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy
) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return repository.findAll(pageable);
// SELECT * FROM user LIMIT 10 OFFSET 0 ORDER BY id
}
}
6. Custom Queries с @Query
public interface UserRepository extends JpaRepository<User, Long> {
// Кастомный JPQL запрос
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional<User> findUserByEmail(String email);
// Native SQL запрос
@Query(value = "SELECT * FROM user WHERE age > ?", nativeQuery = true)
List<User> findAdults(int age);
// С named параметрами (лучше для читаемости)
@Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.lastName = :lastName")
List<User> findByName(@Param("firstName") String first, @Param("lastName") String last);
// С обновлением данных
@Modifying
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.age > 65")
void deactivateOldUsers();
}
7. Associations (Relationships)
// One-to-Many
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Book> books;
}
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
}
// Repository для Author будет получать связанные Book-и
public interface AuthorRepository extends JpaRepository<Author, Long> {
// Spring Data обработает joins
List<Author> findByBooksTitle(String bookTitle);
// SELECT a FROM Author a WHERE EXISTS (SELECT 1 FROM Book b WHERE b.author = a AND b.title = ?)
}
8. Specifications (Advanced Filtering)
// Для сложных динамических фильтров
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
@Service
public class UserService {
@Autowired
private UserRepository repository;
public List<User> searchUsers(String firstName, Integer ageFrom, Integer ageTo) {
Specification<User> spec = Specification
.where((root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (firstName != null) {
predicates.add(cb.equal(root.get("firstName"), firstName));
}
if (ageFrom != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("age"), ageFrom));
}
if (ageTo != null) {
predicates.add(cb.lessThanOrEqualTo(root.get("age"), ageTo));
}
return cb.and(predicates.toArray(new Predicate[0]));
});
return repository.findAll(spec);
}
}
9. Spring Data в других системах
Spring Data MongoDB:
public interface ProductRepository extends MongoRepository<Product, String> {
List<Product> findByCategory(String category);
}
// Генерирует MongoDB запрос db.product.find({category: "electronics"})
Spring Data Redis:
public interface UserCacheRepository extends CrudRepository<User, String> {
List<User> findByAge(int age);
}
// Работает с Redis, автоматически сериализует объекты
10. Производительность и Best Practices
Проблема 1: N+1 queries
// ❌ Плохо: 1 запрос + N запросов на связанные данные
List<Author> authors = repository.findAll();
for (Author author : authors) {
System.out.println(author.getBooks()); // ← Отдельный запрос для каждого!
}
// ✅ Хорошо: JOIN в одном запросе
@Query("SELECT DISTINCT a FROM Author a LEFT JOIN FETCH a.books")
List<Author> findAllWithBooks();
Проблема 2: Слишком большие результаты
// ❌ Плохо: получаем все данные
List<User> users = repository.findAll();
// ✅ Хорошо: используем пагинацию
Page<User> users = repository.findAll(PageRequest.of(0, 20));
Проблема 3: Ненужные поля
// ✅ Хорошо: получаем только нужные поля
@Query("SELECT new UserDTO(u.id, u.name) FROM User u")
List<UserDTO> findAllUsers();
Вывод
Spring Data абстрагирует работу с базами данных и позволяет фокусироваться на бизнес-логике. Основные преимущества:
- Минимум boilerplate кода
- Query generation из имён методов
- Встроенная пагинация, сортировка, фильтрация
- Поддержка множества БД с одним API
- Хороший баланс между удобством и контролем