Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# One-to-Many отношение в JPA/Hibernate
One-to-Many (один-ко-многим) — это отношение, когда один объект связан с несколькими объектами другого типа. Классический пример: один автор может написать много книг.
Базовый пример
// Родительская сущность (One side)
@Entity
@Table(name = "authors")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// One-to-Many: один автор имеет много книг
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books = new ArrayList<>();
public void addBook(Book book) {
books.add(book);
book.setAuthor(this);
}
public void removeBook(Book book) {
books.remove(book);
book.setAuthor(null);
}
}
// Дочерняя сущность (Many side)
@Entity
@Table(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String isbn;
// Many-to-One: каждая книга принадлежит одному автору
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private Author author;
}
SQL результат
-- Таблица авторов
CREATE TABLE authors (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255)
);
-- Таблица книг (с внешним ключом на автора)
CREATE TABLE books (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255),
isbn VARCHAR(255),
author_id BIGINT NOT NULL,
FOREIGN KEY (author_id) REFERENCES authors(id)
);
Использование
@Service
public class AuthorService {
@Autowired
private AuthorRepository authorRepository;
public void createAuthorWithBooks() {
// Создание автора
Author author = new Author();
author.setName("Isaac Asimov");
author.setEmail("asimov@example.com");
// Добавление книг
Book book1 = new Book();
book1.setTitle("Foundation");
book1.setIsbn("978-0553293357");
author.addBook(book1);
Book book2 = new Book();
book2.setTitle("I, Robot");
book2.setIsbn("978-0553294385");
author.addBook(book2);
// Сохранение (cascades автоматически сохранит и книги)
authorRepository.save(author);
}
// Получение автора с книгами
public Author getAuthorWithBooks(Long authorId) {
return authorRepository.findById(authorId).orElse(null);
// books будут загружены лениво (LAZY)
}
// Удаление книги (orphanRemoval удалит её из БД)
public void removeBook(Long authorId, Long bookId) {
Author author = authorRepository.findById(authorId).orElse(null);
if (author != null) {
author.books.removeIf(book -> book.getId().equals(bookId));
authorRepository.save(author);
}
}
}
Параметры аннотации
@OneToMany(
mappedBy = "author", // Имя поля в Book которое ссылается на Author
cascade = CascadeType.ALL, // Применять операции: PERSIST, MERGE, DELETE
orphanRemoval = true, // Удалять Book если его удалить из списка
fetch = FetchType.LAZY // Загружать книги при запросе (по умолчанию)
)
private List<Book> books;
Проблема N+1 запросов
// Плохо: Приведёт к N+1 запросам
List<Author> authors = authorRepository.findAll(); // 1 запрос
for (Author author : authors) {
System.out.println(author.getBooks().size()); // N запросов (по одному на каждого автора)
}
// Хорошо: Eager loading с JOIN
@Query("SELECT a FROM Author a JOIN FETCH a.books")
List<Author> findAllWithBooks();
// Или EntityGraph
@EntityGraph(attributePaths = {"books"})
List<Author> findAll();
Реальный пример: User и Orders
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String username;
private String email;
@OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<Order> orders = new HashSet<>();
}
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private LocalDateTime createdAt;
private BigDecimal totalAmount;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
// Использование
User user = new User();
user.setUsername("john_doe");
Order order1 = new Order();
order1.setTotalAmount(BigDecimal.valueOf(99.99));
order1.setCreatedAt(LocalDateTime.now());
user.orders.add(order1);
order1.setUser(user);
userRepository.save(user); // Сохранит и пользователя, и заказ
Лучшие практики
- Используй mappedBy — укажи обратное значение на Many side
- cascade с осторожностью — использование CascadeType.ALL может привести к непредвиденному удалению
- orphanRemoval=true — удаляет детей если их удалить из списка
- Lazy loading по умолчанию — избегай N+1 через EntityGraph или JOIN FETCH
- Helper методы — addBook(), removeBook() для синхронизации обеих сторон
- Collections: List vs Set — Set лучше для уникальности, List для порядка
- Не забывай двусторонний update — addBook должен устанавливать обе стороны отношения