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

Как связать сущности в JPA

2.2 Middle🔥 201 комментариев
#Основы Java

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

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

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

Связи между сущностями в JPA

В JPA существует четыре основных типа связей между сущностями. Выбор зависит от типа отношения: один-ко-многим, много-ко-одному, много-ко-многим и один-к-одному.

1. @ManyToOne (Много-к-одному)

Самая частая связь. Несколько сущностей ссылаются на одну.

@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String title;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User author;
}

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String username;
    
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Post> posts = new ArrayList<>();
}

Ключевые моменты:

  • @JoinColumn определяет внешний ключ в таблице Post
  • mappedBy = "author" во втором классе указывает, что связь уже определена в Post
  • cascade = CascadeType.ALL — удаление пользователя удаляет его посты
  • fetch = FetchType.LAZY — загружать посты только при обращении

2. @OneToMany (Один-ко-многим)

Обратная сторона @ManyToOne. Обычно используется как обратная ссылка.

@Entity
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String name;
    
    @OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE)
    private List<Product> products = new ArrayList<>();
}

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String name;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;
}

3. @ManyToMany (Много-ко-многим)

Требует промежуточную таблицу связи.

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String name;
    
    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
}

@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String title;
    
    @ManyToMany(mappedBy = "courses")
    private List<Student> students = new ArrayList<>();
}

4. @OneToOne (Один-к-одному)

Обычно используется для расширения данных сущности.

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String username;
    
    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    private String bio;
    private String avatarUrl;
    
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", unique = true)
    private User user;
}

Параметры аннотаций

cascade — как изменения родителя влияют на потомков:

  • CascadeType.PERSIST — сохранение
  • CascadeType.REMOVE — удаление
  • CascadeType.MERGE — обновление
  • CascadeType.ALL — все операции

fetch — когда загружать:

  • FetchType.EAGER — сразу при загрузке родителя
  • FetchType.LAZY — только при обращении (рекомендуется)

orphanRemoval — удалять детей, если они не связаны с родителем:

@OneToMany(mappedBy = "user", orphanRemoval = true)
private List<Comment> comments;

Практический пример: Blog System

@Entity
public class Blog {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    @OneToMany(mappedBy = "blog", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Article> articles = new ArrayList<>();
}

@Entity
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "blog_id")
    private Blog blog;
    
    @OneToMany(mappedBy = "article", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
    
    @ManyToMany
    @JoinTable(
        name = "article_tag",
        joinColumns = @JoinColumn(name = "article_id"),
        inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private List<Tag> tags = new ArrayList<>();
}

@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "article_id")
    private Article article;
}

@Entity
public class Tag {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    
    @ManyToMany(mappedBy = "tags")
    private List<Article> articles = new ArrayList<>();
}

Хорошие практики

  • Используй FetchType.LAZY по умолчанию, чтобы избежать N+1 проблемы
  • Всегда инициализируй коллекции: private List<T> items = new ArrayList<>()
  • Используй orphanRemoval = true для удаления сирот
  • Определяй cascade аккуратно — PERSIST и REMOVE могут иметь серьёзные последствия
  • На стороне владельца связи используй @JoinColumn, на обратной — mappedBy

Эти основные связи позволяют моделировать практически любые отношения между данными в приложении.

Как связать сущности в JPA | PrepBro