«1. Обзор

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

В этом руководстве мы узнаем, как реализовать Entity Graphs с помощью 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
}

Теперь давайте определим сущность Characteristic:

@Entity
public class Characteristic {

    @Id
    private Long id;
    private String type;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Item item;

    //Getters and Setters
}

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

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.

Значением по умолчанию аргумента типа аннотации @EntityGraph является EntityGraphType.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. Тестовый пример

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

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

@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);
    }
}

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

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

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, не загружает какие-либо свойства объекта Characteristic. В результате загружается только объект Item.

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

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

И запрос без аннотации @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=?

5. Заключение

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

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

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

«