Spring Data JPA asigna el resultado de la consulta nativa a POJO que no es una entidad

92

Tengo un método de repositorio de Spring Data con una 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);

y me gustaría mapear el resultado a POJO sin entidad GroupDetails.

¿Es posible? De ser así, ¿podría darnos un ejemplo?

alexanoide
fuente

Respuestas:

65

Suponiendo GroupDetails como en la respuesta de orid, ¿ha probado 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")

y use lo siguiente en la interfaz del repositorio:

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

De acuerdo con la primavera de datos JPA documentación , primavera en primer lugar tratar de encontrar consulta con nombre que coincidan con su nombre de método - por lo que mediante el uso @NamedNativeQuery, @SqlResultSetMappingy @ConstructorResultque debe ser capaz de lograr que el comportamiento

Daimon
fuente
15
Para que los datos de primavera puedan coincidir con NamedNativeQuery, el nombre de clase de la entidad de dominio seguido de un punto debe ir precedido del nombre de NamedNativeQuery. Entonces, el nombre debería ser (asumiendo que la entidad de dominio es Grupo) 'Group.getGroupDetails'.
Grant Lay
@GrantLay, ¿puedes echar un vistazo a esta pregunta? Stackoverflow.com/q/44871757/7491770 Tengo exactamente este tipo de problema.
ram
¿Cómo devolveré una lista de dichos objetos?
Nikhil Sahu
1
Para que funcione, debe GroupDetailsmarcar con @Entity? Si es posible, ¿podría indicar en qué clase @NamedNativeQueryse debe aplicar la anotación ?
Manu
3
@SqlResultSetMappingy las @NamedNativeQueryanotaciones deben estar presentes en la entidad utilizada en su repositorio de Spring Data (por ejemplo, porque public interface CustomRepository extends CrudRepository<CustomEntity, Long>es la CustomEntityclase)
Tomasz W
112

Creo que la forma más sencilla de hacerlo es utilizar la llamada proyección. Puede asignar resultados de consultas a interfaces. Usar SqlResultSetMappinges incómodo y hace que su código sea feo :).

Un ejemplo directamente del código fuente JPA de 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();

  }
}

También puede utilizar este método para obtener una lista de proyecciones.

Consulte esta entrada de documentos JPA de datos de primavera para obtener más información sobre las proyecciones.

Nota 1:

Recuerde tener su Userentidad definida como normal: los campos de la interfaz proyectada deben coincidir con los campos de esta entidad. De lo contrario, la asignación de campos podría estar rota ( getFirstname()podría devolver el valor del apellido, etc.).

Nota 2:

Si usa SELECT table.column ...notación, defina siempre los alias que coincidan con los nombres de la entidad. Por ejemplo, este código no funcionará correctamente (la proyección devolverá nulos para cada captador):

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

Pero esto funciona bien:

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

En el caso de consultas más complejas, prefiero usar JdbcTemplateun repositorio personalizado.

Michał Stochmal
fuente
Es una solución más limpia. Lo había comprobado, pero el rendimiento es mucho peor que el uso de SqlResultSetMapping (es más lento alrededor del 30-40% :()
kidnan1991
funciona muy bien! hacer pública la interfaz si desea utilizarlo en otro lugar
tibi
No funciona si desea extraer el campo de tipo XML (clob). ¿Cualquier sugerencia?
Ashish
@Ashish Prefiero usar JdbcTemplate( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) en su lugar. Puede utilizar el getClobmétodo resultSetpara obtener clob InputStream. Para un ejemplo: rs.getClob("xml_column").getCharacterStream().
Michał Stochmal
¿Qué pasa si utilizo SELECT * en la consulta y la consulta es nativa?
Salman Kazmi
17

Creo que el enfoque de Michal es mejor. Pero hay una forma más de obtener el resultado de la 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);

Ahora, puede convertir esta matriz de cadenas 2D en su entidad deseada.

Ashish
fuente
2
simple y elegante
juan
9

Puede escribir su consulta nativa o no nativa de la forma que desee, y puede ajustar los resultados de la consulta JPQL con instancias de clases de resultados personalizadas. Cree un DTO con los mismos nombres de columnas devueltos en la consulta y cree un constructor de todos los argumentos con la misma secuencia y nombres que devuelve la consulta. Luego use la siguiente forma para consultar la base de datos.

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

Crear DTO:

package example;

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

    public CountryAndCapital(String countryName, String capitalName) {
        this.countryName = countryName;
        this.capitalName = capitalName;
    }
}
Waqas Memon
fuente
corrección: los mismos nombres no son obligatorios ... solo la misma secuencia de parámetros en el constructor y el conjunto de resultados devuelto.
Waqas Memon
Esto solo funciona si Country es su clase de entidad java. Esto no ocurrirá si Country no es su clase de entidad java.
Yeshwant KAKAD
1
¿Dice que esto también debería funcionar con consultas nativas? ¿Podría dar un ejemplo de eso?
Richard Tingle
OP solicita una consulta nativa, pero el ejemplo dado es no nativo
CLS
0

Puedes hacer 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)")

Y debe haber un constructor 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;
    }
Chandan Gawri
fuente
13
La pregunta es sobre consultas nativas, no sobre consultas escritas en HQL.
DBK
-5

En mi computadora, este código funciona, es un poco diferente de la respuesta 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")

jiangke
fuente