Spring Data JPA mapeia o resultado da consulta nativa para POJO de não entidade


97

Eu tenho um método de repositório Spring Data com uma consulta nativa

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
    GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

e gostaria de mapear o resultado para POJO de não entidade GroupDetails.

É possível e, em caso afirmativo, poderia dar um exemplo?

Respostas:


66

Assumindo GroupDetails como na resposta de orid, você tentou JPA 2.1 @ConstructorResult ?

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID"),
                @ColumnResult(name="USER_ID")
            }
        )
    }
)

@NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

e use o seguinte na interface do repositório:

GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

De acordo com a documentação do Spring Data JPA , o spring tentará primeiro encontrar a consulta nomeada que corresponda ao nome do seu método - então, usando @NamedNativeQuery, @SqlResultSetMappinge @ConstructorResultvocê deve ser capaz de atingir esse comportamento


15
Para que os dados de primavera possam corresponder a NamedNativeQuery, o nome da classe da entidade de domínio seguido por um ponto precisa ser prefixado ao nome de NamedNativeQuery. Portanto, o nome deve ser (assumindo que a entidade de domínio é Grupo) 'Group.getGroupDetails'.
— Grant Lay

@GrantLay você pode dar uma olhada nesta pergunta: stackoverflow.com/q/44871757/7491770 Eu tenho exatamente esse tipo de problema.
— Ram

Como vou retornar uma lista de tais objetos?
— Nikhil Sahu

1
Para fazê-lo funcionar, deve ser GroupDetailsmarcado com @Entity? Se possível, você pode dizer em qual classe a anotação @NamedNativeQuerydeve ser aplicada?
— Manu,

3
@SqlResultSetMappinge as @NamedNativeQueryanotações devem estar presentes na entidade usada em seu repositório Spring Data (por exemplo, porque public interface CustomRepository extends CrudRepository<CustomEntity, Long>é a CustomEntityclasse)
— Tomasz W

116

Acho que a maneira mais fácil de fazer isso é usar a chamada projeção. Ele pode mapear os resultados da consulta para interfaces. Usar SqlResultSetMappingé inconveniente e torna seu código feio :).

Um exemplo direto do código-fonte JPA da Spring Data:

public interface UserRepository extends JpaRepository<User, Integer> {

   @Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
   NameOnly findByNativeQuery(Integer id);

   public static interface NameOnly {

     String getFirstname();

     String getLastname();

  }
}

Você também pode usar este método para obter uma lista de projeções.

Verifique esta entrada de documentos JPA de dados de primavera para obter mais informações sobre as projeções.

Nota 1:

Lembre-se de ter sua Userentidade definida como normal - os campos da interface projetada devem coincidir com os campos desta entidade. Caso contrário, o mapeamento de campo pode ser interrompido ( getFirstname()pode retornar o valor do sobrenome etc.).

Nota 2:

Se você usar a SELECT table.column ...notação, sempre defina aliases que correspondam aos nomes da entidade. Por exemplo, este código não funcionará corretamente (a projeção retornará nulos para cada getter):

@Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

Mas isso funciona bem:

@Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

No caso de consultas mais complexas, prefiro usar o JdbcTemplaterepositório personalizado.


É uma solução mais limpa. Eu verifiquei, mas o desempenho é muito pior do que usar SqlResultSetMapping (é mais lento cerca de 30-40% :()
— kidnan1991

funciona bem! tornar a interface pública se quiser usá-la em outro lugar
— tibi

Não funciona se você deseja extrair o campo do tipo XML (clob). Alguma sugestão?
— Ashish,

@Ashish Prefiro usar JdbcTemplate( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ). Você pode usar o getClobmétodo on a resultSetfim de buscar clob InputStream. Para um exemplo: rs.getClob("xml_column").getCharacterStream().
— Michał Stochmal

E se eu usar SELECT * na consulta e a consulta for nativa?
— Salman Kazmi

17

Acho que a abordagem de Michal é melhor. Porém, há mais uma maneira de obter o resultado da consulta nativa.

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

Agora, você pode converter esta matriz de string 2D na entidade desejada.


2
simples e elegante
— john

como você faz a conversão de um array 2D para uma classe?
— Jorge Jiménez

10

Você pode escrever sua consulta nativa ou não nativa da maneira que desejar e pode agrupar os resultados da consulta JPQL com instâncias de classes de resultados personalizadas. Crie um DTO com os mesmos nomes das colunas retornadas na consulta e crie um construtor de todos os argumentos com a mesma sequência e nomes retornados pela consulta. Em seguida, use a seguinte maneira para consultar o banco de dados.

@Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")

Criar DTO:

package example;

public class CountryAndCapital {
    public String countryName;
    public String capitalName;

    public CountryAndCapital(String countryName, String capitalName) {
        this.countryName = countryName;
        this.capitalName = capitalName;
    }
}

correção: os mesmos nomes não são obrigatórios ... apenas a mesma sequência de parâmetros no construtor e o conjunto de resultados retornado.
— Waqas Memon de

Isso funciona apenas se Country for sua classe de entidade java. Isso não acontecerá se Country não for sua classe de entidade java.
— Yeshwant KAKAD

1
Você diz que isso também deve funcionar com consultas nativas? Você poderia dar um exemplo disso?
— Richard Tingle

OP pede uma consulta nativa, mas o exemplo dado é um não nativo
— CLS

0

Use o método padrão na interface e obtenha o EntityManager para ter a oportunidade de definir o ResultTransformer, então você pode retornar o POJO puro, assim:

final String sql = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = ? WHERE g.group_id = ?";
default GroupDetails getGroupDetails(Integer userId, Integer groupId) {
    return BaseRepository.getInstance().uniqueResult(sql, GroupDetails.class, userId, groupId);
}

E o BaseRepository.java é assim:

@PersistenceContext
public EntityManager em;

public <T> T uniqueResult(String sql, Class<T> dto, Object... params) {
    Session session = em.unwrap(Session.class);
    NativeQuery q = session.createSQLQuery(sql);
    if(params!=null){
        for(int i=0,len=params.length;i<len;i++){
            Object param=params[i];
            q.setParameter(i+1, param);
        }
    }
    q.setResultTransformer(Transformers.aliasToBean(dto));
    return (T) q.uniqueResult();
}

Esta solução não afeta nenhum outro método no arquivo de interface do repositório.


-2

Você pode fazer algo como

@NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,

    query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName, 
               cat.issueCategory, idc.issueDescriptor, idc.description) 
            from Department dep 
            inner join dep.issues iss 
            inner join iss.category cat 
            inner join cat.issueDescriptor idc 
            where idc.id in(?1)")

E deve haver um construtor como

public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
            String description) {
        super();
        this.id = id;
        this.department = department;
        this.issueName = issueName;
        this.issueCategory = issueCategory;
        this.issueDescriptor = issueDescriptor;
        this.description = description;
    }

15
A questão é sobre consultas nativas, não consultas escritas em HQL.
— DBK

-5

No meu computador, eu consigo que esse código funcione. É um pouco diferente da resposta de Daimon.

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID",type=Integer.class),
                @ColumnResult(name="USER_ID",type=Integer.class)
            }
        )
    }
)

@NamedNativeQuery(name="User.getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.