Рубрики
Без рубрики

Многие из лучших практик ассоциации JPA и Hibernate

Узнайте, как лучше всего сопоставить ассоциацию ManyToOne при использовании JPA и гибернации, чтобы обеспечить максимальную производительность при извлечении или обновлении сущностей.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье я собираюсь показать вам, как лучше всего сопоставлять ассоциации многих типов при использовании JPA и Hibernate.

Поскольку ассоциация @ManyToOne является наиболее распространенной связью, знание того, как правильно ее сопоставить, окажет значительное влияние на производительность приложения.

Отношение “один ко многим” является наиболее распространенным отношением в таблицах базы данных @vlad_mihalcea объясняет лучший способ сопоставления множества ассоциаций при использовании JPA и гибернации https://t.co/M1U9fwdTdo Отношение “один ко многим” является наиболее распространенным отношением в таблицах базы данных

Связи таблиц

Как объясняется в этой статье , существует три типа отношений таблиц:

  • один ко многим
  • один к одному
  • многие ко многим

Отношение таблицы “один ко многим” выглядит следующим образом:

Таблица post_comment содержит столбец post_id , который связан внешним ключом со столбцом id в родительской таблице post . Столбец post_id Внешний ключ определяет отношение таблицы “один ко многим”.

Ассоциация @ManyToOne JPA и Hibernate

При использовании JPA и гибернации аннотация @ManyToOne позволяет сопоставить столбец внешнего ключа:

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    @GeneratedValue
    private Long id;

    private String review;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;
    
    //Getters and setters omitted for brevity
}

Аннотация @JoinColumn позволяет указать имя столбца внешнего ключа. В нашем примере мы можем опустить аннотацию @JoinColumn , поскольку по умолчанию предполагается, что имя столбца внешнего ключа формируется путем объединения свойства @ManyToOne и идентификатора родительской сущности через символ _ .

Кроме того, очень важно явно установить стратегию выборки в FetchType. ЛЕНИВЫЙ Also, it's very important to set the fetch strategy explicitly to FetchType.LAZY . By default, @ManyToOne associations use the FetchType.EAGER strategy, which can lead to N+1 query issues

Для получения более подробной информации о том, почему вам следует избегать использования FetchType. НЕТЕРПЕЛИВЫЙ , ознакомьтесь с этой статьей .

Сохранение многовариантной связи с JPA и гибернацией

Давайте предположим, что мы ранее сохраняли родителя Должность организация:

entityManager.persist(
    new Post()
        .setId(1L)
        .setTitle("High-Performance Java Persistence")
);

Распространенной ошибкой разработчиков при сохранении дочерних сущностей является извлечение родительской сущности с помощью find :

Post post = entityManager.find(Post.class, 1L);

entityManager.persist(
    new PostComment()
        .setId(1L)
        .setReview("Amazing book!")
        .setPost(post)
);

Или, если вы используете Spring Data JPA, та же проблема возникает при использовании findById метода JpaRepository :

Post post = postRepository.findById(1L);

commentRepository.save(
    new PostComment()
        .setId(1L)
        .setReview("Amazing book!")
        .setPost(post)
);

При сохранении сущности Post Comment при использовании метода find Hibernate выполнит следующие инструкции SQL:

SELECT 
    p.id AS id1_0_0_, 
    p.title AS title2_0_0_ 
FROM post p 
WHERE p.id=1

INSERT INTO post_comment (
    post_id, 
    review, id
) 
VALUES (
    1, 
    'Amazing book!', 
    1
)

Запрос SELECT не нужен, так как мы не заинтересованы в извлечении объекта Post . Все, что мы хотим, это установить столбец post_id Внешний ключ.

Итак, вместо найти вам нужно использовать getReference :

Post post = entityManager.getReference(Post.class, 1L);

entityManager.persist(
    new PostComment()
        .setId(1L)
        .setReview("Amazing book!")
        .setPost(post)
);

Или метод getOne , если вы используете Spring Data JPA:

Post post = postRepository.getOne(1L);

commentRepository.save(
    new PostComment()
        .setId(1L)
        .setReview("Amazing book!")
        .setPost(post)
);

К сожалению, JpaRepository вызывается метод получить один и не находите прокси по идентификатору , так как разработчикам было бы намного проще догадаться о его назначении.

Теперь Hibernate не нужно выполнять инструкцию SELECT:

INSERT INTO post_comment (
    post_id, 
    review, id
) 
VALUES (
    1, 
    'Amazing book!', 
    1
)

Для получения более подробной информации о наилучшем способе использования сохраняйтесь и объединить , проверить эту статью .

Получение многотоновой ассоциации с JPA и гибернацией

Предполагая, что вы используете тип FetchType. ЛЕНИВАЯ стратегия, при извлечении Комментария к сообщению сущности и доступе к сообщению @ManyToOne ассоциации:

PostComment comment = entityManager.find(PostComment.class, 1L);

LOGGER.info(
    "The post '{}' got the following comment '{}'",
    comment.getPost().getTitle(),
    comment.getReview()
);

Hibernate запустит вторичную инструкцию SELECT:

SELECT 
    pc.id AS id1_1_0_, 
    pc.post_id AS post_id3_1_0_, 
    pc.review AS review2_1_0_ 
FROM post_comment pc 
WHERE pc.id = 1

SELECT 
    p.id AS id1_0_0_, 
    p.title AS title2_0_0_ 
FROM post p 
WHERE p.id = 1

The post 'High-Performance Java Persistence' got the following comment 'Amazing book!'

Чтобы избежать вторичного запроса на выбор, вам необходимо получить сообщение To avoid the secondary SELECT query, you need to fetch the post @ManyToOne association using the

PostComment comment = entityManager.createQuery("""
    select pc
    from PostComment pc
    join fetch pc.post
    where pc.id = :id
    """, PostComment.class)
.setParameter("id", 1L)
.getSingleResult();

LOGGER.info(
    "The post '{}' got the following comment '{}'",
    comment.getPost().getTitle(),
    comment.getReview()
);

Теперь Hibernate выполняет один SQL-запрос для извлечения дочерних и родительских сущностей:

SELECT 
    pc.id AS id1_1_0_, 
    p.id AS id1_0_1_, 
    pc.post_id AS post_id3_1_0_, 
    pc.review AS review2_1_0_, 
    p.title AS title2_0_1_ 
FROM post_comment pc
INNER JOIN post p ON pc.post_id = p.id  
WHERE pc.id = 1

The post 'High-Performance Java Persistence' got the following comment 'Amazing book!'

Директива JOIN FETCH может помочь вам избежать получения Исключение LazyInitializationException если вы попытаетесь получить доступ к ленивой @ManyToOne ассоциации после закрытия контекста сохранения.

Вывод

При использовании JPA и Hibernate очень важно знать, как сопоставлять и использовать ассоциацию ManyToOne, поскольку это самая важная связь.

Используя FetchType. LAZY по умолчанию является очень полезной практикой , так как стратегия извлечения должна устанавливаться для каждого конкретного случая использования, а не глобально.