Как сопоставить набор результатов JPA NativeQuery с POJO с помощью SqlResultSetMapping

Я пытаюсь сопоставить результаты собственного запроса с POJO, используя @SqlResultSetMapping с @ConstructorResult. Вот мой код:

@SqlResultSetMapping(name="foo",
    classes = {
        @ConstructorResult(
                targetClass = Bar.class,
                columns = {
                    @ColumnResult(name = "barId", type = Long.class),
                    @ColumnResult(name = "barName", type = String.class),
                    @ColumnResult(name = "barTotal", type = Long.class)
                })
    })

public class Bar {

private Long barId;
private String barName;
private Long barTotal;

...

И затем в моем DAO:

Query query = em.createNativeQueryBar(QUERY, "foo");
... set some parameters ...
List<Bar> list = (List<Bar>) query.getResultList();

Я читал, что эта функциональность поддерживается только в JPA 2.1, но это то, что я использую. Вот моя зависимость:

        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.1-api</artifactId>
            <version>1.0.0.Final</version>
        </dependency>

Я нашел несколько ресурсов, в том числе этот: @ConstructorResult mapping в jpa 2.1. Но мне все равно не везет.

Что мне не хватает? Почему не удается найти SqlResultSetMapping?

javax.persistence.PersistenceException: org.hibernate.MappingException: Unknown SqlResultSetMapping [foo]

person mhlandry    schedule 07.08.2014    source источник
comment
в вашем примере имена переменных экземпляра pojo не соответствуют тому, что вы указали в @ColumnResult.   -  person Nathan Hughes    schedule 07.08.2014
comment
Если Bar не является сущностью, аннотации постоянства не подхватываются, поскольку он не является частью единицы постоянства. Поместите свою аннотацию на сущность.   -  person Chris    schedule 08.08.2014
comment
Я бы предпочел не иметь накладных расходов на работу с сущностью. Спецификация JPA 2.1 предусматривает прямое сопоставление с POJO. Я просто не могу заставить его работать. См. раздел ConstructorResult (JPA 2.1): ссылка. Также см.: ссылка. И: ссылка   -  person mhlandry    schedule 08.08.2014
comment
Вы нашли решение? у меня похожая проблема   -  person Joel    schedule 15.03.2015
comment
@Hexe, да, выбранный ответ был решением.   -  person mhlandry    schedule 18.03.2015


Ответы (11)


@SqlResultSetMapping аннотацию не следует помещать в POJO. Поместите его в (любой) @Entity класс. «Неизвестный SqlResultSetMapping [foo]» сообщает вам, что провайдер JPA не видит никакого сопоставления под именем «foo». Пожалуйста, смотрите другой мой ответ для правильного примера

person zbig    schedule 08.08.2014
comment
Я не понимаю, почему объект должен быть сущностью. Вы сами говорите, что это не нужно: сопоставление с классом POJO с помощью @ConstructorResult было добавлено в версии 2.1 JPA. - person mhlandry; 08.08.2014
comment
Объект, на который вы сопоставляете результаты (targetClass из @ConstructorResult), не обязательно должен быть сущностью. Однако @SqlResultSetMapping должен аннотировать класс Entity. причина в том, что провайдер JPA сканирует все классы Entity и может подобрать оттуда @SqlResultSetMapping конфигурацию. Не-сущностные классы не сканируются. - person zbig; 08.08.2014
comment
@zbig, так нам придется добавить новую таблицу в нашу БД? если да, возможно ли альтернативное решение, при котором нам не нужно создавать новую таблицу в БД? - person ziMtyth; 27.03.2018
comment
Стоит отметить, что вы можете аннотировать свой POJO с помощью @MappedSuperClass, и тогда будет учитываться @SqlResultSetMapping. Не самое элегантное решение, но для меня предпочтительнее, чем аннотировать другой случайный объект. - person Ghurdyl; 11.12.2019

Краткий рабочий пример:

  • Класс DTO POJO

    @lombok.Getter
    @lombok.AllArgsConstructor
    public class StatementDto {
        private String authorName;
        private Date createTime;
    }
    
  • Компонент репозитория:

    @Repository
    public class StatementNativeRepository {      
        @PersistenceContext private EntityManager em;
    
        static final String STATEMENT_SQLMAP = "Statement-SQL-Mapping";
    
        public List<StatementDto> findPipelinedStatements() {
            Query query = em.createNativeQuery(
                "select author_name, create_time from TABLE(SomePipelinedFun('xxx'))",
                STATEMENT_SQLMAP);
            return query.getResultList();
        }
    
        @SqlResultSetMapping(name= STATEMENT_SQLMAP, classes = {
            @ConstructorResult(targetClass = StatementDto.class,
                columns = {
                    @ColumnResult(name="author_name",type = String.class),
                    @ColumnResult(name="create_time",type = Date.class)
                }
            )
        }) @Entity class SQLMappingCfgEntity{@Id int id;} // <- workaround
    
    }
    
person kinjelom    schedule 20.03.2016
comment
Отличное решение, оно помогло мне пройти 90% пути, но я продолжал получать ошибки о проверке таблицы (таблица не найдена). Чтобы исправить это, я добавил аннотацию к SQLMappingCfgEntity @Table(name = DUAL) и случайно заменил «int id» на «String dummy». Конечно, это исправление для Oracle, но мне нравится, что оно не зависит от конкретной таблицы, что было бы альтернативным вариантом. - person AaronP; 06.01.2017
comment
Сначала я не понял, что класс SQLMappingCfgEntity отличается от класса StatementDto. SqlResultSetMapping не должен аннотировать класс, который вы пытаетесь создать. Он должен аннотировать класс Entity, но вы можете добавить его в ЛЮБОЙ класс Entity. - person ChetPrickles; 27.12.2018
comment
Хорошее решение, но было бы намного лучше, если бы вы могли поместить аннотацию @SqlResultSetMapping в класс вместо создания нового объекта сущности, который на самом деле не используется. - person DaithiG; 25.08.2020

Я могу сделать это следующим образом:

Session session = em().unwrap(Session.class);
SQLQuery q = session.createSQLQuery("YOUR SQL HERE");
q.setResultTransformer( Transformers.aliasToBean( MyNotMappedPojoClassHere.class) );
List<MyNotMappedPojoClassHere> postList = q.list();
person Emerson Moretto    schedule 09.01.2015
comment
Преобразователь результата будет работать, но это решение для спящего режима, а не JPA. - person isaranchuk; 24.03.2015
comment
также он устарел, начиная с спящего режима 5.2 - person Dirk; 12.03.2020

Проблема с добавлением @Entity в ваш DTO POJO заключается в том, что он создаст таблицу в вашей базе данных, которая вам не нужна. Добавление @Id и временного ключевого слова в необходимые поля также вызывает затруднения. Простое решение — переместить ваш @SqlResultSetMapping в абстрактный класс.

@MappedSuperclass
@SqlResultSetMapping(name="foo",
    classes = {
        @ConstructorResult(
                targetClass = Bar.class,
                columns = {
                    @ColumnResult(name = "barId", type = Long.class),
                    @ColumnResult(name = "barName", type = String.class),
                    @ColumnResult(name = "barTotal", type = Long.class)
                })
    })

public abstract class sqlMappingCode {}

Не забудьте добавить @MappedSuperclass. Это гарантирует, что Hibernate автоматически подключит ваше сопоставление.

Обновление: эта функция не работает в Hibernate 5.4.30. Final, тикет Jira создан HHH-14572

person Vlad Palnik    schedule 19.08.2019

@Entity
@SqlResultSetMapping(name="ConnexionQueryBean",
   entities={
         @EntityResult(entityClass=com.collecteJ.business.bean.ConnexionQueryBean.class, fields={ 
        @FieldResult(name="utilisateurId", column="UTILISATEUR_ID"),
        @FieldResult(name="nom", column="NOM"),
        @FieldResult(name="prenom", column="PRENOM"),
        @FieldResult(name="nomConnexion", column="NOM_CONNEXION"),
        @FieldResult(name="codeAgence", column="CODE_AGENCE"),
        @FieldResult(name="codeBanque", column="CODE_BANQUE"),
        @FieldResult(name="codeDevise", column="CODE_DEVISE"),
        @FieldResult(name="codeCollecteur", column="CODE_COLLECTEUR")})
   })
public class ConnexionQueryBean implements Serializable {

@Id
private long utilisateurId;
private String codeCollecteur;
private String nom;
private String prenom;
private String nomConnexion; 
private String codeAgence;
private String codeBanque;
private String codeDevise;


public ConnexionQueryBean() {
}


public long getUtilisateurId() {
    return utilisateurId;
}

public void setUtilisateurId(long utilisateurId) {
    this.utilisateurId = utilisateurId;
}

public String getCodeCollecteur() {
    return codeCollecteur;
}

public void setCodeCollecteur(String codeCollecteur) {
    this.codeCollecteur = codeCollecteur;
}


public String getNom() {
    return nom;
}

public void setNom(String nom) {
    this.nom = nom;
}

public String getPrenom() {
    return prenom;
}

public void setPrenom(String prenom) {
    this.prenom = prenom;
}

public String getNomConnexion() {
    return nomConnexion;
}

public void setNomConnexion(String nomConnexion) {
    this.nomConnexion = nomConnexion;
}

public String getCodeAgence() {
    return codeAgence;
}

public void setCodeAgence(String codeAgence) {
    this.codeAgence = codeAgence;
}

public String getCodeBanque() {
    return codeBanque;
}

public void setCodeBanque(String codeBanque) {
    this.codeBanque = codeBanque;
}

public String getCodeDevise() {
    return codeDevise;
}

public void setCodeDevise(String codeDevise) {
    this.codeDevise = codeDevise;
}

@Override
public String toString() {
    return "ConnexionQueryBean{" + "utilisateurId=" + utilisateurId + ", codeCollecteur=" + codeCollecteur + ", nom=" + nom + ", prenom=" + prenom + ", nomConnexion=" + nomConnexion + ", codeAgence=" + codeAgence + ", codeBanque=" + codeBanque + ", codeDevise=" + codeDevise + '}';
}

На самом деле это не сущность, поскольку она не соответствует ни одной таблице базы данных. Но аннотации @Entity и @Id являются обязательными для JPA, чтобы понять отображение. Если вы действительно не хотите иметь @Entity / @Id в этом классе, вы можете удалить аннотацию @SqlResultSetMapping и поместить ее в любой другой объект, насколько JPA может его сканировать.

Вы также должны убедиться, что ваш @ComponentScan содержит соответствующий пакет, если вы используете конфигурацию Spring на основе Java, вы должны явно объявить свою сущность в persistence.xml/orm.xml в каталоге META-INF.

это звонок

String connexionQuery = "SELECT u.UTILISATEUR_ID, u.NOM, u.PRENOM, u.NOM_CONNEXION, a.CODE_AGENCE, a.CODE_BANQUE, a.CODE_DEVISE, c.CODE_COLLECTEUR FROM UTILISATEUR u, AGENCE a, COLLECTEUR c "
            + " WHERE (a.CODE_AGENCE = c.CODE_AGENCE AND u.UTILISATEUR_ID = c.UTILISATEUR_ID AND u.NOM_CONNEXION = '"+nomConnextion+"')";

    ConnexionQueryBean ConnexionResults = (ConnexionQueryBean) defaultService.getEntityManager().createNativeQuery(connexionQuery,"ConnexionQueryBean").getSingleResult();
    System.out.println(ConnexionResults.toString());

Я использую Spring, JPA 2.1, Hibernate 5 и Oracle, я думаю, что это может быть невозможно с более низкой версией JPA, найти больше http://www.thoughts-on-java.org/result-set-mapping-complex-mappings/

person benito    schedule 25.11.2016
comment
имейте в виду, что @ComponentScan является собственностью Spring и не требуется в развернутом приложении контейнера EE. - person him; 29.05.2018

У меня есть немного другой ответ, который только что получен из ответа wildloop.
Вот мой ответ:

Класс констант: Constants.java

public class Constants {
    public final String TESTQUERYRESULT_MAPPING_NAME = "TestQueryResultMapping";
}

Класс сопоставления результатов: TestQueryResult.java

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.EntityResult;
import javax.persistence.FieldResult;
import javax.persistence.Id;
import javax.persistence.SqlResultSetMapping;

@Getter
@Setter
@SqlResultSetMapping(
    //name = "TestQueryResultMapping"
    name = Constants.TESTQUERYRESULT_MAPPING_NAME
    ,entities = @EntityResult(
        entityClass = TestQueryResult.class
        ,fields = {
            @FieldResult(name = "rowId", column = "row_id")
            ,@FieldResult(name = "rowName", column = "row_name")
            ,@FieldResult(name = "value", column = "row_value")
        }
    )
)
@Entity
public class TestQueryResult {
    @Id
    private Integer rowId;

    private String rowName;

    private String value;

}

Затем... где-то в моем коде реализации репозитория:

public class TestQueryRepository {
    //... some code here to get the entity manager here

    public TestQueryResult getTopMost(Integer rowName) {
        //... some code here

        String queryString = "... some query string here" + rowName;
        TestQueryResult testQueryResult = null;

        //this.entityManager.createNativeQuery(queryString ,"TestQueryResultMapping").getResultList();
        List<TestQueryResult> results = this.entityManager.createNativeQuery(queryString ,Constants.TESTQUERYRESULT_MAPPING_NAME).getResultList();
        if (results != null && !results.isEmpty()) {
            testQueryResult = results.get(0);
        }

        return testQueryResult;
    }

}


...тогда виола! Я получил некоторые результаты: D!

С уважением,
Артанис Зератул

person Artanis Zeratul    schedule 14.11.2018

Альтернативой может быть QLRM: http://simasch.github.io/qlrm/

Он не связан с конкретной реализацией JPA и также работает с JDBC.

person Simon Martinelli    schedule 25.11.2016

Модель домена

Предположим, что в нашей базе данных есть следующие таблицы post и post_comment:

JPA SqlResultSetMapping таблицы post и post_comment

JPA SqlResultSetMapping

Аннотация SqlResultSetMapping JPA выглядит следующим образом:

@Repeatable(SqlResultSetMappings.class)
@Target({TYPE}) 
@Retention(RUNTIME)
public @interface SqlResultSetMapping { 

    String name(); 

    EntityResult[] entities() default {};

    ConstructorResult[] classes() default {};

    ColumnResult[] columns() default {};
}

Аннотация SqlResultSetMapping повторяема и применяется на уровне класса сущностей. Помимо использования уникального имени, которое используется Hibernate для регистрации сопоставления, есть три варианта сопоставления:

  • EntityResult
  • ConstructorResult
  • ColumnResult

Далее мы рассмотрим, как работают все эти три варианта сопоставления, а также варианты использования, в которых вам нужно будет их использовать.

JPA SqlResultSetMapping — EntityResult

Параметр EntityResult позволяет сопоставить столбцы JDBC ResultSet с одним или несколькими объектами JPA.

Предположим, мы хотим получить первые 5 объектов Post вместе со всеми связанными с ними объектами PostComment, которые соответствуют заданному шаблону title.

Как я объяснял в этой статье, мы можем использовать DENSE_RANK Оконная функция SQL, чтобы узнать, как фильтровать объединенные записи post и post_comment. , как показано в следующем SQL-запросе:

SELECT *
FROM (
  SELECT
    *,
    DENSE_RANK() OVER (
    ORDER BY
      "p.created_on",
      "p.id"
    ) rank
  FROM (
    SELECT
      p.id AS "p.id", p.created_on AS "p.created_on",
      p.title AS "p.title", pc.post_id AS "pc.post_id",
      pc.id as "pc.id", pc.created_on AS "pc.created_on",
      pc.review AS "pc.review"
    FROM post p
    LEFT JOIN post_comment pc ON p.id = pc.post_id
    WHERE p.title LIKE :titlePattern
    ORDER BY p.created_on
  ) p_pc
) p_pc_r
WHERE p_pc_r.rank <= :rank

Однако мы не хотим возвращать список значений скалярного столбца. Мы хотим вернуть объекты JPA из этого запроса, поэтому нам нужно настроить атрибут entities аннотации @SqlResultSetMapping, например так:

@NamedNativeQuery(
    name = "PostWithCommentByRank",
    query = """
        SELECT *
        FROM (
          SELECT
            *,
            DENSE_RANK() OVER (
            ORDER BY
              "p.created_on",
              "p.id"
            ) rank
          FROM (
            SELECT
              p.id AS "p.id", p.created_on AS "p.created_on",
              p.title AS "p.title", pc.post_id AS "pc.post_id",
              pc.id as "pc.id", pc.created_on AS "pc.created_on",
              pc.review AS "pc.review"
            FROM post p
            LEFT JOIN post_comment pc ON p.id = pc.post_id
            WHERE p.title LIKE :titlePattern
            ORDER BY p.created_on
          ) p_pc
        ) p_pc_r
        WHERE p_pc_r.rank <= :rank
        """,
    resultSetMapping = "PostWithCommentByRankMapping"
)
@SqlResultSetMapping(
    name = "PostWithCommentByRankMapping",
    entities = {
        @EntityResult(
            entityClass = Post.class,
            fields = {
                @FieldResult(name = "id", column = "p.id"),
                @FieldResult(name = "createdOn", column = "p.created_on"),
                @FieldResult(name = "title", column = "p.title"),
            }
        ),
        @EntityResult(
            entityClass = PostComment.class,
            fields = {
                @FieldResult(name = "id", column = "pc.id"),
                @FieldResult(name = "createdOn", column = "pc.created_on"),
                @FieldResult(name = "review", column = "pc.review"),
                @FieldResult(name = "post", column = "pc.post_id"),
            }
        )
    }
)

Имея SqlResultSetMapping, мы можем получить объекты Post и PostComment следующим образом:

List<Object[]> postAndCommentList = entityManager
    .createNamedQuery("PostWithCommentByRank")
    .setParameter("titlePattern", "High-Performance Java Persistence %")
    .setParameter("rank", POST_RESULT_COUNT)
    .getResultList();

И мы можем проверить правильность выборки объектов:

assertEquals(
    POST_RESULT_COUNT * COMMENT_COUNT, 
    postAndCommentList.size()
);

for (int i = 0; i < COMMENT_COUNT; i++) {
    Post post = (Post) postAndCommentList.get(i)[0];
    PostComment comment = (PostComment) postAndCommentList.get(i)[1];

    assertTrue(entityManager.contains(post));
    assertTrue(entityManager.contains(comment));

    assertEquals(
        "High-Performance Java Persistence - Chapter 1",
        post.getTitle()
    );

    assertEquals(
        String.format(
            "Comment nr. %d - A must read!",
            i + 1
        ),
        comment.getReview()
    );
}

@EntityResult также полезен при извлечении сущностей JPA через хранимые процедуры SQL. Прочтите эту статью, чтобы подробнее.

JPA SqlResultSetMapping — ConstructorResult

Предположим, мы хотим выполнить запрос агрегации, который подсчитывает количество post_coment записей для каждого post и возвращает post title для целей отчетности. Мы можем использовать следующий SQL-запрос для достижения этой цели:

SELECT
  p.id AS "p.id",
  p.title AS "p.title",
  COUNT(pc.*) AS "comment_count"
FROM post_comment pc
LEFT JOIN post p ON p.id = pc.post_id
GROUP BY p.id, p.title
ORDER BY p.id

Мы также хотим инкапсулировать заголовок сообщения и количество комментариев в следующем DTO:

public class PostTitleWithCommentCount {

    private final String postTitle;
    
    private final int commentCount;

    public PostTitleWithCommentCount(
            String postTitle,
            int commentCount) {
        this.postTitle = postTitle;
        this.commentCount = commentCount;
    }

    public String getPostTitle() {
        return postTitle;
    }

    public int getCommentCount() {
        return commentCount;
    }
}

Чтобы сопоставить набор результатов приведенного выше SQL-запроса с PostTitleWithCommentCount DTO, мы можем использовать атрибут classes аннотации @SqlResultSetMapping, например:

@NamedNativeQuery(
    name = "PostTitleWithCommentCount",
    query = """
        SELECT
          p.id AS "p.id",
          p.title AS "p.title",
          COUNT(pc.*) AS "comment_count"
        FROM post_comment pc
        LEFT JOIN post p ON p.id = pc.post_id
        GROUP BY p.id, p.title
        ORDER BY p.id
        """,
    resultSetMapping = "PostTitleWithCommentCountMapping"
)
@SqlResultSetMapping(
    name = "PostTitleWithCommentCountMapping",
    classes = {
        @ConstructorResult(
            columns = {
                @ColumnResult(name = "p.title"),
                @ColumnResult(name = "comment_count", type = int.class)
            },
            targetClass = PostTitleWithCommentCount.class
        )
    }
)

Аннотация ConstructorResult позволяет указать Hibernate, какой класс DTO использовать, а также какой конструктор вызывать при создании объектов DTO.

Обратите внимание, что мы использовали атрибут type аннотации @ColumnResult, чтобы указать, что comment_count следует преобразовать в Java int. Это необходимо, поскольку некоторые драйверы JDBC используют либо Long, либо BigInteger для результатов функции агрегирования SQL.

Вот как вы можете вызвать собственный запрос с именем PostTitleWithCommentCount с помощью JPA:

List<PostTitleWithCommentCount> postTitleAndCommentCountList = entityManager
    .createNamedQuery("PostTitleWithCommentCount")
    .setMaxResults(POST_RESULT_COUNT)
    .getResultList();

И мы видим, что возвращенные PostTitleWithCommentCount DTO были получены правильно:

assertEquals(POST_RESULT_COUNT, postTitleAndCommentCountList.size());

for (int i = 0; i < POST_RESULT_COUNT; i++) {
    PostTitleWithCommentCount postTitleWithCommentCount = 
        postTitleAndCommentCountList.get(i);

    assertEquals(
        String.format(
            "High-Performance Java Persistence - Chapter %d",
            i + 1
        ),
        postTitleWithCommentCount.getPostTitle()
    );

    assertEquals(COMMENT_COUNT, postTitleWithCommentCount.getCommentCount());
}

Дополнительные сведения о том, как лучше всего получать проекции DTO с помощью JPA и Hibernate, см. на странице этой статьи.

JPA SqlResultSetMapping — ColumnResult

В предыдущем примере показано, как мы можем сопоставить набор результатов агрегации SQL с DTO. Но что, если мы хотим вернуть сущность JPA, для которой мы подсчитываем комментарии?

Для достижения этой цели мы можем использовать атрибут entities для определения объекта Post, который мы извлекаем, и атрибут classes аннотации @SqlResultSetMapping для сопоставления скалярного значения, которое в нашем случае представляет собой количество связанных записей post_comment:

@NamedNativeQuery(
    name = "PostWithCommentCount",
    query = """
        SELECT
          p.id AS "p.id",
          p.title AS "p.title",
          p.created_on AS "p.created_on",
          COUNT(pc.*) AS "comment_count"
        FROM post_comment pc
        LEFT JOIN post p ON p.id = pc.post_id
        GROUP BY p.id, p.title
        ORDER BY p.id
        """,
    resultSetMapping = "PostWithCommentCountMapping"
)
@SqlResultSetMapping(
    name = "PostWithCommentCountMapping",
    entities = @EntityResult(
        entityClass = Post.class,
        fields = {
            @FieldResult(name = "id", column = "p.id"),
            @FieldResult(name = "createdOn", column = "p.created_on"),
            @FieldResult(name = "title", column = "p.title"),
        }
    ),
    columns = @ColumnResult(
        name = "comment_count",
        type = int.class
    )
)

При выполнении нативного запроса с именем PostWithCommentCount:

List<Object[]> postWithCommentCountList = entityManager
    .createNamedQuery("PostWithCommentCount")
    .setMaxResults(POST_RESULT_COUNT)
    .getResultList();

мы получим как сущность Post, так и скалярное значение столбца commentCount:

assertEquals(POST_RESULT_COUNT, postWithCommentCountList.size());

for (int i = 0; i < POST_RESULT_COUNT; i++) {
    Post post = (Post) postWithCommentCountList.get(i)[0];
    int commentCount = (int) postWithCommentCountList.get(i)[1];

    assertTrue(entityManager.contains(post));

    assertEquals(i + 1, post.getId().intValue());
    assertEquals(
        String.format(
            "High-Performance Java Persistence - Chapter %d",
            i + 1
        ),
        post.getTitle()
    );

    assertEquals(COMMENT_COUNT, commentCount);
}
person Vlad Mihalcea    schedule 24.03.2020

Недавно я собрал быстрое доказательство концепции, которое неплохо сработало для меня и другого разработчика.

Конечная точка загрузки Spring, которая использует собственный запрос и использует SqlResultSetMapping, вот фрагменты кода.

import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.metrics.annotation.Timed;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

@RestController
@RequestMapping(value = "/rest/yum-query", produces = APPLICATION_JSON_VALUE)
@Api(tags = {"notification"})
@Timed(extraTags = {"controller", "YumController"})
public class YumController {

    private final YumService yumService;

    @Autowired
    public YumController(YumService yumService) {
        this.yumService = yumService;
    }

    @GetMapping(produces = APPLICATION_JSON_VALUE)
    public List<ApprovedApplicationsDTO> findApprovedApplications() {
        return yumService.findApprovedApplications();
    }

}

Добавить мою услугу

import au.edu.qld.qcaa.sate.serviceaara.domain.repository.YumRepositoryCustom;
import au.edu.qld.qcaa.sate.serviceaara.dto.yum.ApprovedApplicationsDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class YumService {

    private YumRepositoryCustom yumRepositoryCustom;

    @Autowired
    public YumService(YumRepositoryCustom yumRepositoryCustom) {
        this.yumRepositoryCustom = yumRepositoryCustom;
    }

    public List<ApprovedApplicationsDTO> findApprovedApplications() {
        return yumRepositoryCustom.findApprovedApplicationsNativeQuery();
    }

}

Настройте новый класс результатов запроса для собственного запроса, чтобы сопоставить его.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.SqlResultSetMapping;
import java.math.BigInteger;

@MappedSuperclass
@SqlResultSetMapping(name = "ApprovedApplicationQueryResultBean",
        classes = {
                @ConstructorResult(
                        targetClass = ApprovedApplicationQueryResultBean.class,
                        columns = {
                                @ColumnResult(name = "application_id", type = BigInteger.class),
                                @ColumnResult(name = "application_type", type = String.class),
                                @ColumnResult(name = "assessment_identifier", type = String.class),
                                @ColumnResult(name = "aara_code", type = String.class),
                                @ColumnResult(name = "aara_code_child", type = String.class),
                                @ColumnResult(name = "completion_year", type = Integer.class),
                                @ColumnResult(name = "reason", type = String.class),
                                @ColumnResult(name = "code", type = String.class),
                                @ColumnResult(name = "long_description", type = String.class),
                                @ColumnResult(name = "identified_condition", type = String.class),
                                @ColumnResult(name = "other", type = String.class),
                                @ColumnResult(name = "decision_code", type = String.class),
                        }
                )
        })
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ApprovedApplicationQueryResultBean {

    @Id
    private BigInteger applicationId;

    private String applicationType;
    private String assessmentIdentifier;
    private String aaraCode;
    private String aaraCodeChild;
    private Integer completionYear;
    private String reason;
    private String code;
    private String longDescription;
    private String identifiedCondition;
    private String other;
    private String decisionCode;

}

Создание нового интерфейса репозитория и класса реализации для вызова запроса из

import au.edu.qld.qcaa.sate.serviceaara.dto.yum.ApprovedApplicationsDTO;

import java.util.List;

public interface YumRepositoryCustom {

    List<ApprovedApplicationsDTO> findApprovedApplicationsNativeQuery();

}

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
@Slf4j
public class YumRepositoryCustomImpl implements YumRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    @Override
    public List<ApprovedApplicationsDTO> findApprovedApplicationsNativeQuery() {
        StringBuilder q = new StringBuilder();
        q.append("select distinct a4.application_id, a4.application_type, a3.assessment_identifier, ");
        q.append("a3.aara_code, ad.aara_code_child, a3.completion_year, c.reason, rd.code, rd.long_description, ic.identified_condition, ic.other, ad2.decision_code ");
        q.append("from category c ");
        q.append("left join application a4 on a4.application_id = c.application_id ");
        q.append("left join arrangement a3 on a3.application_id = a4.application_id ");
        q.append("left join arrangement_detail ad on a3.arrangement_id  = ad.arrangement_id ");
        q.append("left join identified_condition ic on ic.category_id = c.category_id ");
        q.append("left join reference_data rd on rd.code = c.reason ");
        q.append("left join arrangement_decision ad2 on ad2.arrangement_id  = a3.arrangement_id ");
        q.append("left JOIN (SELECT max(ad3.arrangement_decision_id) as theid ");
        q.append("FROM arrangement_decision ad3 ");
        q.append("GROUP BY ad3.arrangement_id) b on ad2.arrangement_decision_id = b.theid ");

        q.append("where a4.application_type = ?1 and a3.completion_year = ?2 ");

        q.append("and a4.is_active = true and a3.is_active = true and ic.is_active = true and c.is_active = true ");
        q.append("order by 1 ");

        Query query = em.createNativeQuery(q.toString(), "ApprovedApplicationQueryResultBean");
        query.setParameter(1, ApplicationConstants.APPLICATION_TYPE_AARA);
        query.setParameter(2, Calendar.getInstance().get(Calendar.YEAR));

        List<ApprovedApplicationQueryResultBean> entityResults = query.getResultList();
        return entityResults.stream().map(entity -> {
            return mapToDTO(entity);
        }).collect(Collectors.toList());
    }

    private ApprovedApplicationsDTO mapToDTO(ApprovedApplicationQueryResultBean entity) {
        return ApprovedApplicationsDTO.builder()
                .applicationId(entity.getApplicationId())
                .applicationType(entity.getApplicationType())
                .aaraCode(entity.getAaraCode())
                .aaraCodeChild(entity.getAaraCodeChild())
                .completionYear(entity.getCompletionYear())
                .reason(entity.getReason())
                .code(entity.getCode())
                .longDescription(entity.getLongDescription())
                .identifiedCondition(entity.getIdentifiedCondition())
                .other(entity.getOther())
                .decisionCode(entity.getDecisionCode())
                .build();
    }

}

Добавить мой ответ конечной точки DTO

import lombok.Builder;
import lombok.Data;

import java.math.BigInteger;

@Data
@Builder
public class ApprovedApplicationsDTO {
    private BigInteger applicationId;
    private String applicationType;
    private String assessmentIdentifier;
    private String aaraCode;
    private String aaraCodeChild;
    private Integer completionYear;
    private String reason;
    private String code;
    private String longDescription;
    private String identifiedCondition;
    private String other;
    private String decisionCode;

}
Then hit my endpoint via postman or curl or which ever tool you wish to use:
http://localhost:10050/rest/yum-query
person ZiggyStardust    schedule 21.10.2020

Это решение не зависит от реализации JPA. Как только вы соберете результат собственного запроса как

List<Object[]> = em.createNativeQueryBar(QUERY, "foo").getResultList();

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

Class Person { String name; Int age; }

где элемент списка [0] — имя, а элемент [1] — возраст.

Вы можете преобразовать Object[] в Person, используя ObjectMapper. Вам нужно добавить аннотацию @JsonFormat к классу.

@JsonFormat(shape = JsonFormat.Shape.ARRAY) 
Class Person { String name; Int age; }

и преобразовать Object[] в Person как

ObjectMapper mapper = new ObjectMapper();
JSONArray jsonArray = new JSONArray(Arrays.asList(attributes)).toString();
Person person = mapper.readValue(jsonArray, Person.class);
person dinesh salve    schedule 17.09.2019

@MappedSuperclass
@SqlResultSetMapping(name="foo",
classes = {
    @ConstructorResult(
            targetClass = Bar.class,
            columns = {
                @ColumnResult(name = "barId", type = Long.class),
                @ColumnResult(name = "barName", type = String.class),
                @ColumnResult(name = "barTotal", type = Long.class)
            })
})

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

Query query = em.createNativeQueryBar(QUERY);
... set some parameters ...
List<Object[]> result = query.getResultList();
List<Bar> list = result.stream().map(
        objects -> {
            Bar bar = new Bar();
            bar.setBarId(Long.parseLong(objects[0].toString()));
            bar.setBarName(objects[1].toString());
            bar.setBarTotal(Long.parseLong(objects[2].toString()));
            ...
            
            return bar;
        }
).collect(Collectors.toList());
person Eboo    schedule 15.09.2020