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

Приведи пример one-to-many

2.0 Middle🔥 151 комментариев
#ORM и Hibernate

Комментарии (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); // Сохранит и пользователя, и заказ

Лучшие практики

  1. Используй mappedBy — укажи обратное значение на Many side
  2. cascade с осторожностью — использование CascadeType.ALL может привести к непредвиденному удалению
  3. orphanRemoval=true — удаляет детей если их удалить из списка
  4. Lazy loading по умолчанию — избегай N+1 через EntityGraph или JOIN FETCH
  5. Helper методы — addBook(), removeBook() для синхронизации обеих сторон
  6. Collections: List vs Set — Set лучше для уникальности, List для порядка
  7. Не забывай двусторонний update — addBook должен устанавливать обе стороны отношения
Приведи пример one-to-many | PrepBro