Spring Data JPA и NamedEntityGraphs

в настоящее время я пытаюсь получить только те данные, которые мне нужны. Метод findAll () должен получать данные в зависимости от того, откуда они были вызваны. Я не хочу писать разные методы для каждого графа сущностей. Кроме того, я бы не стал вызывать менеджеров сущностей и сам формировать (повторяющиеся) запросы. В основном я хочу использовать сборку в методе findAll, но с графом сущностей, который мне нравится. Любой шанс?

@Entity
@Table(name="complaints")
@NamedEntityGraphs({
    @NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
            @NamedAttributeNode("customer"),
            @NamedAttributeNode("handling_employee"),
            @NamedAttributeNode("genre")
    }),
    @NamedEntityGraph(name="allJoins", attributeNodes = {
            @NamedAttributeNode("customer"),
            @NamedAttributeNode("handling_employee"),
            @NamedAttributeNode("genre"),
            @NamedAttributeNode("complaintMessages")
    }),
    @NamedEntityGraph(name="noJoins", attributeNodes = {

    })
})
public class Complaint implements Serializable{
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private long id;

    private Timestamp date;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer")
    private User customer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "handling_employee")
    private User handling_employee;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="genre")
    private Genre genre;

    private boolean closed;

    @OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();

//getters and setters
}

И мой JPARepository

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

    List<Complaint> findByClosed(boolean closed);

    @EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
    @Override
    List<Complaint> findAll(Sort sort);
}

person dendimiiii    schedule 11.08.2015    source источник
comment
Думаю, подходящим было бы решение с другими названиями методов.   -  person K.Nicholas    schedule 28.09.2017


Ответы (6)


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

1) Приставки. Data jpa предоставляет несколько префиксов (найти, получить, ...) для имени метода. Одна из возможностей - использовать разные префиксы с разными именованными графами. Это минимум работы, но он скрывает смысл метода от разработчика и может вызвать некоторые неочевидные проблемы с загрузкой неправильных сущностей.

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);

    @EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
    User readByUserId(int id);
}

2) CustomRepository. Еще одно возможное решение - создать собственные методы запроса и внедрить EntityManager. Это решение дает вам самый чистый интерфейс для вашего репозитория, потому что вы можете называть свои методы чем-то значимым, но это значительная сложность, которую нужно добавить в свой код, чтобы предоставить решение, И вы вручную захватываете диспетчер сущностей вместо использования магии Spring.

interface UserRepositoryCustom {
    public User findUserWithMembershipYearsById(int id);
}

class UserRepositoryImpl implements UserRepositoryCustom {
    @PersistenceContext
    private EntityManager em;
    @Override
    public User findUserWithMembershipYearsById(int id) {
        User result = null;
        List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
                .setParameter("id", id)
                .setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
                .getResultList();
        if(users.size() >= 0) {
            result = users.get(0);
        }
        return result;
    }
}

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);
}

3) JPQL. По сути, это просто отказ от графов именованных сущностей и использование JPQL для обработки ваших объединений за вас. На мой взгляд, неидеально.

@Repository
@Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
    @EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
    User findByUserID(int id);

    @Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
    User findUserWithTags(@Param("id") final int id);
}

Мы выбрали вариант 1, потому что он самый простой в реализации, но это означает, что, когда мы используем наши репозитории, мы должны смотреть на методы выборки, чтобы убедиться, что мы используем тот, который имеет правильный граф сущностей. Удачи.

Источники:

У меня недостаточно репутации, чтобы публиковать все свои источники. Извини :(

person Chris Spencer    schedule 27.10.2015
comment
вместо изменения глагола метода можно также добавить произвольную строку между глаголом и наподобие findEagerById или findLessEagerById - person Jens Schauder; 21.06.2018
comment
@JensSchauder есть ли подобное решение для findAll? - person Jordan Mackie; 09.08.2018
comment
@JordanMackie Это приводит к несколько странным именам, но вы можете закончить имя после By, что делает его вариантом findAll: findAllBy, findEverythingBy ... - person Jens Schauder; 10.08.2018
comment
для № 3, вы должны делать соединения? Я думал, что график был для этого - person Matt Broekhuis; 22.01.2019
comment
Странно, что я могу использовать findOneWithMembershipByUserId с аннотацией, но не findAllWithMemberShip. - person LunaticJape; 04.02.2019
comment
Комментарий от @JensSchauder должен быть принятым ответом. Также работает как findAllWithThisAndThatFieldByOtherField - person Hans Wouters; 26.11.2019
comment
@JordanMackie, да, вы можете просто написать findAllEagerBy, getAllWithAssociationsBy и т. Д. Если вы просто закончите свое имя с помощью By (как в следующем шаблоне find\get\read... + All + ... + By), оно будет обрабатываться так же, как метод по умолчанию findAll. - person Max; 22.05.2021

У нас была такая же проблема, и мы создали расширение Spring Data JPA для ее решения:

https://github.com/Cosium/spring-data-jpa-entity-graph

Это расширение позволяет передавать именованный или динамически созданный EntityGraph в качестве аргумента любого метода репозитория.

С этим расширением вы сразу получите доступ к этому методу:

List<Complaint> findAll(Sort sort, EntityGraph entityGraph);

И иметь возможность вызывать его с помощью EntityGraph, выбранного во время выполнения.

person Réda Housni Alaoui    schedule 25.11.2016
comment
Я уточнил свой ответ. - person Réda Housni Alaoui; 26.11.2016
comment
Это очень полезно. У меня есть выдающийся билет Spring Data по этой проблеме jira.spring.io/browse / DATAJPA-645? Filter = -2. Думали ли вы о присоединении к проекту Spring Data? Работает ли он с методами QueryDsl Predicate? - person Alan Hay; 01.02.2017
comment
Привет, Алан, перед созданием github.com/Cosium/spring-data-jpa-entity -graph, я попросил включить динамический entitygraph в тикет jira.spring.io/browse / DATAJPA-749. Но ему отказали. Включена поддержка QueryDsl. Для этого есть несколько тестовых примеров. Но я не использую QueryDsl в своих проектах. Если вы обнаружите ошибки, не стесняйтесь сообщать о них на трекере;) - person Réda Housni Alaoui; 02.02.2017
comment
Здорово. Спасибо. Интересно читать обсуждение в вашем билете! - person Alan Hay; 03.02.2017
comment
Привет, Реда, спасибо за решение, выглядит многообещающим, однако оно несовместимо с версией Java 12. - person Oleg Baranenko; 05.11.2019
comment
Привет, @OlegBaranenko, пожалуйста, взгляните на github.com/Cosium / spring-data-jpa-entity-graph / issues / 23 - person Réda Housni Alaoui; 06.11.2019

Используйте @EntityGraph вместе с @Query

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

   @EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
   @Query("SELECT c FROM Complaint ORDER BY ..")
   @Override
   List<Complaint> findAllJoinsButMessages();

   @EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
   @Query("SELECT c FROM Complaint ORDER BY ..")
   @Override
   List<Complaint> findAllJoin();

   ...

}

person Grigory Kislin    schedule 17.10.2016
comment
Мне нравится ваше решение. Но у меня возникла проблема с использованием NamedEntityGraphs - я не могу вернуть такой пользовательский объект @Query("SELECT new DTO(f) from Entity f") - person Oleg Kuts; 19.11.2016
comment
Ответ @ ChrisSpencer, похоже, предполагает, что объединение jpql и графов сущностей не работает, вы можете подтвердить, что это работает? - person Jordan Mackie; 09.08.2018
comment
@JordanMackie Я использовал его в своем проекте: github.com/gkislin/topjava/blob/master/src/main/java/ru/ - person Grigory Kislin; 10.08.2018

Использование аннотации @EntityGraph в производном запросе возможно, как я узнал из Эта статья. В статье есть пример:

@Repository
public interface ArticleRepository extends JpaRepository<Article,Long> {
   @EntityGraph(attributePaths = "topics")
   Article findOneWithTopicsById(Long id);
}

Но я не думаю, что в "with" есть что-то особенное, и на самом деле вы можете иметь что-нибудь между find и By. Я пробовал их, и они работают (это код Kotlin, но идея та же):

interface UserRepository : PagingAndSortingRepository<UserModel, Long> {
    @EntityGraph(attributePaths = arrayOf("address"))
    fun findAnythingGoesHereById(id: Long): Optional<UserModel>

    @EntityGraph(attributePaths = arrayOf("address"))
    fun findAllAnythingGoesHereBy(pageable: Pageable): Page<UserModel>
}

В статье упоминалось предостережение о том, что вы не можете создать метод, аналогичный findAll, который будет запрашивать все записи без условия By и использует findAllWithTopicsByIdNotNull() в качестве примера. Я обнаружил, что достаточно просто включить By в конце имени: findAllWithTopicsBy(). Немного более кратко, но может быть немного более запутанным для чтения. Использование имен методов, которые заканчиваются только на By без каких-либо условий, может оказаться в опасности, что в будущих версиях Spring может возникнуть опасность, поскольку это не похоже на предполагаемое использование имени производных запросов.

Похоже, что код для синтаксического анализа производных имен запросов в Spring: здесь, на github. Вы можете посмотреть там, если вам интересно, что возможно для имен методов репозитория производных запросов.

Эти - это весенняя документация для производных запросов.

Это было протестировано с помощью spring-data-commons-2.2.3.RELEASE.

person mowwwalker    schedule 18.12.2019

Можете ли вы попробовать создать имя EntiyGraph с дочерним элементом, который вы будете запрашивать, и дать то же имя методу find all. Бывший:

@EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
 Employee getProfileAddressRecordById(long id);

Для вашего случая:

@NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
        @NamedAttributeNode("customer"),
        @NamedAttributeNode("handling_employee"),
        @NamedAttributeNode("genre")
})

имя метода в репозитории

@EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
 findAllCustomerHandlingEmployeeGenre

Таким образом, вы можете отслеживать различные методы findAll.

person smile    schedule 09.02.2017

РЕДАКТИРОВАТЬ: это на самом деле не работает. В итоге пришлось пойти с https://github.com/Cosium/spring-data-jpa-entity-graph. Метод по умолчанию ВЫГЛЯДИТ правильным, но не отменяет аннотации.

Используя JPA, я обнаружил, что работает метод по умолчанию с другой аннотацией EntityGraph:

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{

    List<Complaint> findByClosed(boolean closed);

    @EntityGraph(attributePaths = {"customer", "genre", "handling_employee" }, type=EntityGraphType.FETCH)
    @Override
    List<Complaint> findAll(Sort sort);

    @EntityGraph(attributePaths = {"customer", "genre", "handling_employee", "messages" }, type=EntityGraphType.FETCH)
    default List<Complaint> queryAll(Sort sort){
      return findAll(sort);
    }
}

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

person Femi    schedule 05.04.2021