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

Как сделать join в HQL

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

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

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

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

Как сделать join в HQL

HQL (Hibernate Query Language) — это объектно-ориентированный язык запросов, похожий на SQL, но работающий с объектами JPA/Hibernate вместо таблиц. JOIN в HQL используется для связывания данных из связанных сущностей.

Основная синтаксис JOIN в HQL

HQL поддерживает несколько типов JOIN'ов:

// Базовая структура
// SELECT ... FROM EntityName e JOIN e.relationship r WHERE ...

Примеры моделей для демонстрации

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Post> posts;
    
    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;
}

@Entity
@Table(name = "posts")
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
    
    @OneToMany(mappedBy = "post")
    private List<Comment> comments;
}

@Entity
@Table(name = "comments")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String text;
    
    @ManyToOne
    @JoinColumn(name = "post_id")
    private Post post;
}

@Entity
@Table(name = "departments")
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToMany(mappedBy = "department")
    private List<User> users;
}

1. INNER JOIN

INNER JOIN возвращает только записи, которые имеют соответствие в обеих таблицах.

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    // Получить все посты с информацией об авторе
    @Query("SELECT p FROM Post p INNER JOIN p.user u WHERE u.id = :userId")
    List<Post> findPostsByUserWithJoin(@Param("userId") Long userId);
    
    // Более сложный пример: посты с комментариями
    @Query("SELECT p FROM Post p INNER JOIN p.comments c WHERE c.id > 0")
    List<Post> findPostsWithComments();
}
// Использование
List<Post> posts = postRepository.findPostsByUserWithJoin(1L);

2. LEFT JOIN (LEFT OUTER JOIN)

LEFT JOIN возвращает все записи из левой таблицы, даже если нет соответствий в правой.

@Query("SELECT u, COUNT(p) FROM User u LEFT JOIN u.posts p GROUP BY u")
List<Object[]> findUsersWithPostCount();

// Использование
List<Object[]> results = userRepository.findUsersWithPostCount();
for (Object[] row : results) {
    User user = (User) row[0];
    Long postCount = (Long) row[1];
    System.out.println(user.getName() + ": " + postCount + " posts");
}

Другой пример: получить всех пользователей со всеми их постами (даже если постов нет)

@Query("SELECT u FROM User u LEFT JOIN FETCH u.posts p ORDER BY u.id")
List<User> findAllUsersWithPosts();

3. RIGHT JOIN

RIGHT JOIN возвращает все записи из правой таблицы. Некоторые БД не поддерживают, но HQL позволяет.

// Все посты, даже если у них нет автора (редкий случай)
@Query("SELECT p FROM Post p RIGHT JOIN p.user u")
List<Post> findAllPostsWithOptionalUser();

4. CROSS JOIN (декартово произведение)

CROSS JOIN возвращает все возможные комбинации.

// Все комбинации пользователей и постов
@Query("SELECT u, p FROM User u, Post p")
List<Object[]> findAllCombinations();

5. Условия в JOIN

Можно добавить условие прямо в JOIN ON clausule.

// Получить пользователей с их постами, у которых ID > 10
@Query("SELECT u FROM User u LEFT JOIN u.posts p ON p.id > 10")
List<User> findUsersWithSpecificPosts();

// Более практичный пример
@Query("SELECT u FROM User u LEFT JOIN u.department d WHERE d.name = :deptName")
List<User> findUsersByDepartment(@Param("deptName") String deptName);

6. JOIN с фильтрацией

@Query("SELECT DISTINCT u FROM User u INNER JOIN u.posts p WHERE p.title LIKE :keyword")
List<User> findUsersWithPostsContaining(@Param("keyword") String keyword);

// Использование
List<User> authors = userRepository.findUsersWithPostsContaining("%Java%");

7. Множественные JOIN'ы

// Посты с информацией об авторе И комментариями
@Query("SELECT p FROM Post p " +
        "INNER JOIN FETCH p.user u " +
        "LEFT JOIN FETCH p.comments c " +
        "WHERE u.department.id = :deptId")
List<Post> findPostsWithUserAndCommentsByDept(@Param("deptId") Long deptId);

// Комментарии с информацией о посте, авторе и отделе
@Query("SELECT c FROM Comment c " +
        "INNER JOIN FETCH c.post p " +
        "INNER JOIN FETCH p.user u " +
        "INNER JOIN FETCH u.department d " +
        "WHERE d.name = :deptName")
List<Comment> findCommentsByUserDepartment(@Param("deptName") String deptName);

8. FETCH JOIN (optimizing N+1 queries)

FETCH JOIN — это специальный тип JOIN в HQL для оптимизации. Он загружает связанные сущности в одном запросе.

// Плохо: N+1 problem
List<User> users = userRepository.findAll(); // 1 запрос
for (User user : users) {
    System.out.println(user.getPosts()); // N дополнительных запросов!
}

// Хорошо: Fetch JOIN
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.posts ORDER BY u.id")
List<User> findAllUsersWithPosts(); // 1 запрос, всё загруженно

9. Отличие между JOIN и FETCH JOIN

// обычный JOIN не загружает related объекты
@Query("SELECT u FROM User u LEFT JOIN u.posts")
List<User> findUsersWithJoin(); // Posts НЕ загружены

// FETCH JOIN загружает related объекты
@Query("SELECT u FROM User u LEFT JOIN FETCH u.posts")
List<User> findUsersWithFetchJoin(); // Posts ЗАГРУЖЕНЫ

10. GROUP BY с JOIN

// Количество постов по пользователям
@Query("SELECT u.name, COUNT(p) FROM User u LEFT JOIN u.posts p GROUP BY u.id, u.name")
List<Object[]> findUserPostCounts();

// Использование
List<Object[]> stats = userRepository.findUserPostCounts();
for (Object[] row : stats) {
    String userName = (String) row[0];
    Long postCount = (Long) row[1];
    System.out.println(userName + ": " + postCount + " posts");
}

11. Пример: сложный запрос с несколькими JOIN'ами

@Query("SELECT p FROM Post p " +
        "INNER JOIN FETCH p.user u " +
        "LEFT JOIN FETCH p.comments c " +
        "INNER JOIN FETCH u.department d " +
        "WHERE d.id = :deptId " +
        "AND p.title LIKE :title " +
        "ORDER BY p.id DESC")
List<Post> findPostsByDepartmentAndTitle(
    @Param("deptId") Long deptId,
    @Param("title") String title
);

Best Practices

  • Используй FETCH JOIN для оптимизации — избегай N+1 queries
  • DISTINCT нужен при LEFT JOIN если есть коллекции — для избежания дубликатов
  • Используй INNER JOIN когда связь обязательна — это быстрее
  • LEFT JOIN когда связь опциональна — получишь запись даже без связанных данных
  • Ограничивай результаты в WHERE — не загружай лишние данные
  • Будь осторожен с множественными LEFT JOIN'ами — может привести к декартову произведению

В production коде я часто использую FETCH JOIN с @EntityGraph для более читаемого кода и правильной оптимизации запросов.

Как сделать join в HQL | PrepBro