简体   繁体   中英

How to map the result set of a JPA NativeQuery to a POJO using SqlResultSetMapping

I am attempting to map the results of a Native query to a POJO using @SqlResultSetMapping with @ConstructorResult. Here is my code:

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

...

And then in my DAO:

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

I have read that this functionality is only supported in JPA 2.1, but that is what I am using. Here's my dependency:

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

I found a couple of resources, including this one: @ConstructorResult mapping in jpa 2.1 . But I am still not having any luck.

What am I missing? Why can't the SqlResultSetMapping be found?

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

@SqlResultSetMapping annotation should not be put on a POJO . Put it at (any) @Entity class. "Unknown SqlResultSetMapping [foo]" tells you, that JPA provider doesn't see any mapping under name 'foo'. Please see another answer of mine for the correct example

Short working example:

  • DTO POJO class

    @lombok.Getter @lombok.AllArgsConstructor public class StatementDto { private String authorName; private Date createTime; }
  • Repository bean:

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

I able to do it this way:

Session session = em().unwrap(Session.class);
SQLQuery q = session.createSQLQuery("YOUR SQL HERE");
q.setResultTransformer( Transformers.aliasToBean( MyNotMappedPojoClassHere.class) );
List<MyNotMappedPojoClassHere> postList = q.list();

The problem with adding @Entity to your DTO POJO is that it will create a table in your db that you don't need. Having to add @Id and a transient keyword to necessary fields is also a hassle. A simple solution is to move your @SqlResultSetMapping to an abstract class.

@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 {}

Don't forget to add @MappedSuperclass . This will ensure Hibernate auto-wires your mapping.

Update: this functionality is broken in Hibernate 5.4.30.Final, Jira ticket created HHH-14572

Update 2: It looks like this functionality is permanently broken after 5.4.30.Final. New solution, you will need to add an @Entity with a table name, then create two classes:

public class CustomSchemaFilterProvider implements SchemaFilterProvider {
// implement code, use CustomSchemaFilter below
}

then create a singleton custom filter

public class CustomSchemaFilter implements SchemaFilter {
// implement code, check for table names you don't want in your schema
}

in your JPA Config file set:

jpaProp.put(Environment.HBM2DDL_FILTER_PROVIDER, "com.your.package.CustomSchemaFilterProvider");
// OR
hibernate.hbm2ddl.schema_filter_provider=com.your.package.CustomSchemaFilterProvider

this will ensure that your new @Entity is ignored by hibernate during mapping

tested with hibernate 5.6.9.Final

Domain Model

Let's consider we have the following post and post_comment tables in our database:

JPA SqlResultSetMapping post 和 post_comment 表

JPA SqlResultSetMapping

The SqlResultSetMapping JPA annotation looks as follows:

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

    String name(); 

    EntityResult[] entities() default {};

    ConstructorResult[] classes() default {};

    ColumnResult[] columns() default {};
}

The SqlResultSetMapping annotation is repeatable and is applied at the entity class level. Apart from taking a unique name, which is used by Hibernate to register the mapping, there are three mapping options:

  • EntityResult
  • ConstructorResult
  • ColumnResult

Next, we are going to see how al these three mapping options work, as well as the use cases where you will need to use them.

JPA SqlResultSetMapping - EntityResult

The EntityResult option allows you to map the JDBC ResultSet columns to one or more JPA entities.

Let's assume we want to fetch the first 5 Post entities along with all their associated PostComment entities that match a given title pattern.

As I explained in this article , we can use the DENSE_RANK SQL Window Function to know how to filter the post and post_comment joined records, as illustrated by the following SQL 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

However, we don't want to return a list of scalar column values. We want to return JPA entities from this query, so we need to configure the entities attribute of the @SqlResultSetMapping annotation, like this:

@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"),
            }
        )
    }
)

With the SqlResultSetMapping in place, we can fetch the Post and PostComment entities like this:

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

And, we can validate that the entities are properly fetched:

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

The @EntityResult is also useful when fetching JPA entities via SQL stored procedures. Check out this article for more details.

JPA SqlResultSetMapping - ConstructorResult

Let's assume we want to execute an aggregation query that counts the number of post_coment records for each post and returns the post title for reporting purposes. We can use the following SQL query to achieve this goal:

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

We also want to encapsulate the post title and the comment count in the following 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;
    }
}

To map the result set of the above SQL query to the PostTitleWithCommentCount DTO, we can use the classes attribute of the @SqlResultSetMapping annotation, like this:

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

The ConstructorResult annotation allows us to instruct Hibernate what DTO class to use as well as which constructor to be called when instantiating the DTO objects.

Note that we used the type attribute of the @ColumnResult annotation to specify that the comment_count should be cast to a Java int . This is needed since some JDBC drivers use either Long or BigInteger for the SQL aggregation function results.

This is how you can call the PostTitleWithCommentCount named native query using JPA:

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

And, we can see that the returned PostTitleWithCommentCount DTOs have been fetched properly:

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

For more details about the best way to fetch DTO projections with JPA and Hibernate, check out this article .

JPA SqlResultSetMapping - ColumnResult

The previous example showed how we could map the SQL aggregation result set to a DTO. But, what if we want to return the JPA entity for which we are counting the comments?

To achieve this goal we can use the entities attribute to define the Post entity we are fetching, and the classes attribute of the @SqlResultSetMapping annotation to map the scalar value, which in our case is the number of associated post_comment records:

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

When executing the PostWithCommentCount named native query:

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

we will get both the Post entity and the commentCount scalar column value:

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

This is not really an entity as it does not match any database table. But the @Entity and the @Id annotations are compulsories for JPA to understand the mapping. If you don't really want to have @Entity / @Id in that class, you can remove the @SqlResultSetMapping annotation and put it in any other entity as far as JPA can scan it.

You should also make sure that your @ComponentScan content the corresponding package, if you are using a java based spring configuration, you should explicitly declare your entity in the persistence.xml/orm.xml under the META-INF directory.

This is the call

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());

I'm using Spring, JPA 2.1, Hibernate 5 and Oracle, i think this might not be possible with JPA lower version, find more http://www.thoughts-on-java.org/result-set-mapping-complex-mappings/

I have a slightly varied answer which is just derived from wildloop's answer.
Here is my answer:

Constants class: Constants.java

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

Result Mapping Class: 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;

}

Then... somewhere in my Repository Implementation code:

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

}


... then violah! I got some results :D!

Cheers,
Artanis Zeratul

QLRM could be a alternative: http://simasch.github.io/qlrm/

It is not related to a specific JPA implementation and also works with JDBC.

I recently put together a quick Proof of concept which worked quite well for myself and another developer.

A Spring boot endpoint which uses a Native Query and makes use of SqlResultSetMapping, here are the code snippets.

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

}

Add my Service

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

}

Setup a new Query Result Class for the native query to map to

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;

}

Creating a new Repository Interface and Implementation class to invoke the query from

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

}

Add my endpoint response 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

This solution is independent of JPA implementation. Once you collect result of native query as

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

and if you need to map each List element to a Person eg

Class Person { String name; Int age; }

where List element[0] is name and element [ 1] is age.

You can convert Object[] to Person using ObjectMapper . You need to add @JsonFormat annotation on the class.

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

and convert Object[] to Person as

ObjectMapper mapper = new ObjectMapper();
JSONArray jsonArray = new JSONArray(Arrays.asList(attributes)).toString();
Person person = mapper.readValue(jsonArray, Person.class);
@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)
            })
})

You can do it like this, but this is not very recommended because it is not very comfortable. You can do it like below.

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());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM