Использование произвольного запроса в качестве проекции в весеннем проекте отдыха данных - PullRequest
1 голос
/ 10 марта 2020

Как можно использовать произвольный sql запрос (я имею в виду нативный sql запрос) в каком-то хранилище? Моя настоящая проблема заключается в следующем:

@Data //lombok thing
@Entity
public class A extends AuditModel {
  private long id;
  private String name;

  @OneToMany(mappedBy="a") //Comments.a is owning side of association, i.e. comments table does have column called a_id as foreign key
  @ToString.Exclude
  private Set<Comments> comments = new HashSet();

  @OneToMany(mappedBy="a") //SimpleFile.a is owning side of association
  private Set<SimpleFile> comments = new HashSet();
}

Чем у меня есть репозиторий, который предоставляет хороший интерфейс crud с использованием представления HAL + json. Я пытаюсь обогатить его некоторыми проекциями / представлениями, особенно из-за веб-интерфейса для загрузки данных одной страницы в одном запросе. Мне известны выдержки и проекции, но они, кажется, недостаточно мощны.

@Repository
@RepositoryRestResource
@Transactional(readOnly = true)
public interface ARepository extends PagingAndSortingRepository<A, Long> {
  Page<A> findByNameContaining(String namePart, Pageable pageable);
  @Query(
    value = "SELECT a.name,\n" +
      "(SELECT CAST(count(ac.id) AS int) FROM COMMENTS ac WHERE ac.a_id = a.id),\n" +
      "(SELECT listagg(asf.id) FROM SIMPLE_FILES asf WHERE asf.a_id = a.id)\n" +
      "FROM AS a\n" +
      "WHERE a.id = :id",
    nativeQuery = true
  )
  Optional<ACustomPage42DTO> getByIdProjectedForScreen42(Long id);
}

Я также пытался использовать JPQL, но там у меня была проблема с выборочным соединением (так как я не знаком с JPQL ). Мой последний оценочный запрос был примерно таким:

@Query("SELECT new sk.qpp.qqq.documents.projections.ACustomPage42DTO(" +
  "a " +
  "(SELECT CAST(count(ac) AS int) FROM COMMENTS ac WHERE ac.a = a)" +
  ")\n" +
  "FROM A a\n" +
  "LEFT JOIN FETCH a.simpleFiles\n" +
  "WHERE a.id = :id"
)

Я хотел бы получить несколько общих советов о том, какой подход лучше всего подходит для реализации нестандартного и сложного запроса, который должен быть возвращен в DTO (в идеале с некоторыми указанными c ссылки на действия при необходимости).

PS: Реализация интерфейса и возврат простых (примитивных) данных работают. Также работает JPQL для создания пользовательских экземпляров DAO (например, с простыми типами и с одним экземпляром типа A). Метод использования данного метода запроса появляется в методах поиска данной конечной точки объекта. Я хотел бы иметь что-то более разумное, поэтому я хотел бы иметь проекцию , как это определено в проекте data rest rest .

Я полностью контролирую свой объект DTO. Я предпочитаю использовать аннотацию @Value или @Data из проекта lombok, но в этом нет необходимости. Я пробовал также эти версии определения DTO (используя интерфейс работает для простых данных и аналогично класс работает для простых данных).

interface ACustomPage42DTO {
    String getName();
    long getCommentsCount();
    Object getAsdf();
}

Или используя эквивалентный класс с некоторым бонусом, как возможный пользовательский метод toString (), или какой-то другой пользовательский метод получения вычисляемых данных:

@Value //lombok thing, imutable "POJO"
public class ACustomPage42DTO {
    String name;
    long commentsCount;
    Set<SimpleFile> simpleFiles;
    public ACustomPage42DTO(A a, long count) {
        // constructor used by JPQL, if it works
        name = a.getName();
        this.commentsCount = count;
        this.simpleFiles = a.getSimpleFiles(); // should be already fetched, due to fetch join in JPQL
    }
}

Оба рабочих подхода можно вызывать с использованием URL-адреса поиска вместо проекции. Я вижу свой метод getByIdProjectedForScreen42 по URL http://localhost: 9091 / api / a / search листинг. Я хотел бы использовать его как (я думаю, что это "правильный" способ) http://localhost: 8080 / api / a? Projection = ACustomPage42DTOProjection .

1 Ответ

1 голос
/ 13 марта 2020

Вопрос довольно широкий и затрагивает несколько аспектов:

  • пользовательский метод репозитория JPA с использованием @Query
  • выбора результатов при отображении @Query
  • @Query приводит к интерфейсу
  • , предоставляя новый метод хранилища с помощью @RepositoryRestResource

TLDR: написал пример того, о чем говорится с парой базовых c тестов https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http

пользовательский метод репозитория JPA с использованием @Query

Как вы уже упоминали, это довольно просто, просто аннотируйте метод с помощью @Query и убедитесь, что вы возвращаете тип соответствует тому, что возвращается из запроса, например:

public interface FooRepository extends JpaRepository<FooEntity, Long> {
    @Query(nativeQuery = true, value = "select f from foo f where f.name = :myParam")
    Optional<FooEntity> getInSomeAnotherWay(String myParam);
}

выбор результатов в вашем @Query

Вы уже привели пример, но я упрощу, чтобы сделать его проще и короче.

Данные сущности FooEntity.java и BarEntity.java:

@Entity
@Table(name = "foo")
public class FooEntity {

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @OneToMany(mappedBy = "foo")
    private Set<BarEntity> bars = new HashSet<>();

    // getter setters excluded for brevity
}

@Entity
@Table(name = "bar")
public class BarEntity {

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @ManyToOne(targetEntity = FooEntity.class)
    @JoinColumn(name = "foo_id", nullable = false, foreignKey = @ForeignKey(name = "fk_bar_foo"))
    private FooEntity foo;

    // getter setters excluded for brevity
}

Теперь мы хотим вернуть пользовательский набор результатов, который содержит FooEntity.name и количество FooEntity.bars:

SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id


+-----------------+----------+
| name            | barCount |
+-----------------+----------+
| Jonny tables    | 1        |
+-----------------+----------+

отображение @Query результатов на интерфейс

T o для отображения вышеупомянутого набора результатов нам нужен интерфейс, в котором получатели хорошо отражают то, что выбирается:

public interface ProjectedFooResult {
    String getName();
    Long getBarCount();
}

Теперь мы можем переписать наш метод репозитория так:

@Query(nativeQuery = true, 
    value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
Optional<ProjectedFooResult> getByIdToProjected(Long id);

, открывая новый метод репозитория через @RepositoryRestResource

Я не очень знаком с этим, но после добавления зависимости org.springframework.data:spring-data-rest-hal-browser я получил этот приятный интерфейс, который отображал доступные методы после того, как репозиторий был помечен @RepositoryRestResource. Для данного репозитория, который содержит вышеупомянутые детали:

@RepositoryRestResource(path = "foo")
public interface FooRepository extends JpaRepository<FooEntity, Long> {
    @Query(nativeQuery = true, value = "SELECT f.name as name, count(b.id) as barCount FROM foo f, bar b WHERE f.id = :id AND b.foo_id = :id")
    Optional<ProjectedFooResult> getByIdToProjected(Long id);
}

, метод будет доступен через http://localhost:8080/foo/search/getByIdToProjected?id=1 при локальном запуске.

Как упоминалось выше, эталонная реализация находится на Github https://github.com/ivarprudnikov/test-spring-jpa-repository-query-exposed-through-http

Дополнительная полезная документация для «Пользовательских реализаций для репозиториев данных Spring»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...