Автор оригинала: 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
по умолчанию является очень полезной практикой , так как стратегия извлечения должна устанавливаться для каждого конкретного случая использования, а не глобально.