Перейти к основному содержимому

Spring Data JPA и графы именованных сущностей

· 4 мин. чтения

1. Обзор

Проще говоря, Entity Graphs — это еще один способ описать запрос в JPA 2.1. Мы можем использовать их для формулирования более эффективных запросов.

В этом руководстве мы узнаем, как реализовать графики сущностей с помощью Spring Data JPA на простом примере.

2. Сущности

Во-первых, давайте создадим модель с именем Item , которая имеет несколько характеристик:

@Entity
public class Item {

@Id
private Long id;
private String name;

@OneToMany(mappedBy = "item")
private List<Characteristic> characteristics = new ArrayList<>();

// getters and setters
}

Теперь давайте определим объект характеристики C :

@Entity
public class Characteristic {

@Id
private Long id;
private String type;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Item item;

//Getters and Setters
}

Как мы видим в коде, как поле характеристик в сущности Item , так и поле элемента в сущности Characteristic загружаются лениво с использованием параметра fetch . Итак, наша цель здесь — жадно загружать их во время выполнения.

3. Графики сущностей

В Spring Data JPA мы можем определить граф сущностей, используя комбинацию аннотаций @NamedEntityGraph и @EntityGraph . Или мы также можем определить специальные графы сущностей, используя только аргумент attributePaths аннотации @EntityGraph .

Давайте посмотрим, как это можно сделать.

3.1. С @NamedEntityGraph

Во-первых, мы можем использовать аннотацию JPA @NamedEntityGraph непосредственно к нашей сущности Item :

@Entity
@NamedEntityGraph(name = "Item.characteristics",
attributeNodes = @NamedAttributeNode("characteristics")
)
public class Item {
//...
}

Затем мы можем прикрепить аннотацию @EntityGraph к одному из методов нашего репозитория:

public interface ItemRepository extends JpaRepository<Item, Long> {

@EntityGraph(value = "Item.characteristics")
Item findByName(String name);
}

Как видно из кода, мы передали имя графа сущности, который мы создали ранее для сущности Item , в аннотацию @EntityGraph . Когда мы вызываем метод, этот запрос будет использовать Spring Data.

Значение по умолчанию аргумента типа аннотации @EntityGraphEntityGraphType.FETCH . Когда мы используем это, модуль данных Spring применит стратегию FetchType.EAGER к указанным узлам атрибутов. А для остальных будет применяться стратегия FetchType.LAZY .

Таким образом, в нашем случае свойство характеристик будет загружаться с готовностью, даже несмотря на то, что стратегия выборки по умолчанию для аннотации @OneToMany является ленивой.

Одна загвоздка здесь в том, что если определенная стратегия выборки — EAGER, то мы не можем изменить ее поведение на LAZY . Это предусмотрено дизайном, поскольку последующим операциям могут понадобиться быстро извлеченные данные на более позднем этапе выполнения.

3.2. Без @NamedEntityGraph

Или мы также можем определить специальный граф сущностей с помощью attributePaths.

Давайте добавим в наш CharacteristicsRepository специальный граф сущностей, который жадно загружает родительский объект Item :

public interface CharacteristicsRepository 
extends JpaRepository<Characteristic, Long> {

@EntityGraph(attributePaths = {"item"})
Characteristic findByType(String type);
}

Это с готовностью загрузит свойство элемента сущности « Характеристика » , даже несмотря на то, что наша сущность объявляет стратегию ленивой загрузки для этого свойства.

Это удобно, поскольку мы можем определить граф сущностей в строке вместо ссылки на существующий именованный граф сущностей.

4. Тестовый случай

Теперь, когда мы определили наши графы сущностей, давайте создадим тестовый пример, чтобы проверить это:

@DataJpaTest
@RunWith(SpringRunner.class)
@Sql(scripts = "/entitygraph-data.sql")
public class EntityGraphIntegrationTest {

@Autowired
private ItemRepository itemRepo;

@Autowired
private CharacteristicsRepository characteristicsRepo;

@Test
public void givenEntityGraph_whenCalled_shouldRetrunDefinedFields() {
Item item = itemRepo.findByName("Table");
assertThat(item.getId()).isEqualTo(1L);
}

@Test
public void givenAdhocEntityGraph_whenCalled_shouldRetrunDefinedFields() {
Characteristic characteristic = characteristicsRepo.findByType("Rigid");
assertThat(characteristic.getId()).isEqualTo(1L);
}
}

В первом тесте будет использоваться граф сущностей, определенный с помощью аннотации @NamedEntityGraph .

Давайте посмотрим на SQL, сгенерированный Hibernate:

select 
item0_.id as id1_10_0_,
characteri1_.id as id1_4_1_,
item0_.name as name2_10_0_,
characteri1_.item_id as item_id3_4_1_,
characteri1_.type as type2_4_1_,
characteri1_.item_id as item_id3_4_0__,
characteri1_.id as id1_4_0__
from
item item0_
left outer join
characteristic characteri1_
on
item0_.id=characteri1_.item_id
where
item0_.name=?

Для сравнения удалим аннотацию @EntityGraph из репозитория и проверим запрос:

select 
item0_.id as id1_10_,
item0_.name as name2_10_
from
item item0_
where
item0_.name=?

Из этих запросов мы можем ясно видеть, что запрос, сгенерированный без аннотации @EntityGraph , не загружает никаких свойств объекта Characteristic . В результате загружается только объект Item .

Наконец, давайте сравним запросы Hibernate второго теста с аннотацией @EntityGraph :

select 
characteri0_.id as id1_4_0_,
item1_.id as id1_10_1_,
characteri0_.item_id as item_id3_4_0_,
characteri0_.type as type2_4_0_,
item1_.name as name2_10_1_
from
characteristic characteri0_
left outer join
item item1_
on
characteri0_.item_id=item1_.id
where
characteri0_.type=?

И запрос без аннотации @EntityGraph :

select 
characteri0_.id as id1_4_,
characteri0_.item_id as item_id3_4_,
characteri0_.type as type2_4_
from
characteristic characteri0_
where
characteri0_.type=?

5. Вывод

В этом руководстве мы узнали, как использовать графики сущностей JPA в Spring Data. С помощью Spring Data мы можем создать несколько методов репозитория, которые связаны с разными графами сущностей .

Примеры для этой статьи доступны на GitHub .