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

Как работают взаимосвязи в Entity

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

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работают взаимосвязи в Entity

Взаимосвязи в JPA/Hibernate - это отношения между сущностями (Entity), которые отображаются на внешние ключи в базе данных.

1. OneToMany - один ко многим

Одна сущность может иметь много связанных сущностей:

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    
    // Один пользователь может иметь много заказов
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private Set<Order> orders = new HashSet<>();
}

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    private String description;
    
    // Множество заказов принадлежит одному пользователю
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

// Использование
User user = new User();
user.setName("John");

Order order1 = new Order();
order1.setDescription("Laptop");
order1.setUser(user);

Order order2 = new Order();
order2.setDescription("Mouse");
order2.setUser(user);

user.getOrders().add(order1);
user.getOrders().add(order2);

session.save(user); // Спасет user и оба order

2. ManyToOne - много к одному

Это обратная сторона OneToMany. На это нужна сущность с внешним ключом:

@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", nullable = false)
    private Category category;
}

@Entity
public class Category {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "category")
    private List<Post> posts;
}

3. OneToOne - один к одному

Две сущности имеют взаимно однозначное отношение:

// Вариант 1: внешний ключ на одной стороне
@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "profile_id", unique = true)
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id
    @GeneratedValue
    private Long id;
    private String bio;
    private String avatar;
}

// Использование
User user = new User();
user.setName("Alice");

UserProfile profile = new UserProfile();
profile.setBio("Software Engineer");

user.setProfile(profile);
session.save(user);

4. ManyToMany - много ко многим

Через промежуточную таблицу:

@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    
    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
}

@Entity
public class Course {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
}

// Использование
Student student = new Student();
student.setName("Bob");

Course course = new Course();
course.setTitle("Java Basics");

student.getCourses().add(course);
session.save(student);

5. Fetch Strategy - стратегия загрузки

Как загружать связанные сущности:

// LAZY - загрузить по требованию (ленивая загрузка)
@Entity
public class User {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private Set<Order> orders;
}

// Первый запрос - только User
User user = session.get(User.class, 1L);

// Второй запрос - только когда обратиться к orders
for (Order order : user.getOrders()) {
    System.out.println(order.getName());
}

// EAGER - загрузить немедленно
@Entity
public class User {
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
    private Set<Order> orders;
}

// Один запрос с JOIN - загружает User и все Orders
User user = session.get(User.class, 1L);

6. Cascade Type - каскадные операции

Что делать со связанными объектами при изменении основной сущности:

// PERSIST - сохранять связанные объекты
@Entity
public class User {
    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
    private Set<Order> orders;
}

User user = new User();
user.setName("Alice");

Order order = new Order();
order.setUser(user);
user.getOrders().add(order);

session.save(user); // Спасет и Order

// REMOVE - удалять связанные объекты
@Entity
public class User {
    @OneToMany(cascade = CascadeType.REMOVE, mappedBy = "user")
    private Set<Order> orders;
}

User user = session.get(User.class, 1L);
session.delete(user); // Удалит User и все Orders

// MERGE - обновлять связанные объекты
@Entity
public class User {
    @OneToMany(cascade = CascadeType.MERGE, mappedBy = "user")
    private Set<Order> orders;
}

User detachedUser = // из другой сессии
session.merge(detachedUser); // Обновит User и Orders

// ALL - все операции
@Entity
public class User {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
    private Set<Order> orders;
}

7. Orphan Removal - удаление сирот

Удалять связанные объекты, если удалить ссылку на них:

@Entity
public class User {
    @OneToMany(
        mappedBy = "user",
        cascade = CascadeType.ALL,
        orphanRemoval = true  // Удалить заказ, если удалить из коллекции
    )
    private Set<Order> orders = new HashSet<>();
}

User user = session.get(User.class, 1L);
Order order = user.getOrders().iterator().next();

user.getOrders().remove(order); // Order будет удален из БД!
session.update(user);

8. Bidirectional Relationships - двусторонние отношения

Поддерживание обеих сторон отношения:

@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private Set<Book> books = new HashSet<>();
    
    // Вспомогательный метод для синхронизации
    public void addBook(Book book) {
        book.setAuthor(this);
        this.books.add(book);
    }
}

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "author_id")
    private Author author;
}

// Правильное использование
Author author = new Author();
Book book = new Book();
author.addBook(book); // Синхронизирует обе стороны
session.save(author);

9. Lazy Loading и N+1 Problem

// ❌ Проблема: N+1
List<User> users = session.createQuery("FROM User").list(); // 1 запрос
for (User user : users) {
    System.out.println(user.getOrders()); // N запросов (по одному на пользователя)
}
// Итого: 1 + N запросов

// ✅ Решение: JOIN FETCH
List<User> users = session.createQuery(
    "SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders"
).list();
for (User user : users) {
    System.out.println(user.getOrders()); // Нет доп. запросов
}

// ✅ Альтернатива: @EntityGraph
@Entity
public class User {
    @NamedEntityGraph(
        name = "user-with-orders",
        attributeNodes = @NamedAttributeNode("orders")
    )
    private Long id;
    
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private Set<Order> orders;
}

10. Пример полной структуры

// Один автор, много книг, много жанров
@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private Set<Book> books = new HashSet<>();
}

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    
    @ManyToOne
    @JoinColumn(name = "author_id")
    private Author author;
    
    @ManyToMany
    @JoinTable(
        name = "book_genre",
        joinColumns = @JoinColumn(name = "book_id"),
        inverseJoinColumns = @JoinColumn(name = "genre_id")
    )
    private Set<Genre> genres = new HashSet<>();
}

@Entity
public class Genre {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    
    @ManyToMany(mappedBy = "genres")
    private Set<Book> books = new HashSet<>();
}

Вывод: взаимосвязи в JPA/Hibernate - это мощный механизм для отображения структур БД на объекты. Главное - правильно выбирать Fetch Strategy, Cascade Type и использовать JOIN FETCH для избежания N+1 проблемы.