pageable - Spring Data JPA asigna el resultado de la consulta nativa a POJO sin entidad
requestmapping spring boot (6)
Tengo un método de repositorio 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 asignar el resultado a
GroupDetails
POJO no de entidad.
¿Es posible y, de ser así, podría dar un ejemplo?
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.
Creo que la forma más fácil de hacerlo es usar la llamada proyección.
Puede asignar resultados de consultas a interfaces.
Usar
SqlResultSetMapping
es
SqlResultSetMapping
y hace que su código sea feo :).
Un ejemplo 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 usar este método para obtener una lista de proyecciones.
Nota 1:
Recuerde tener su entidad de
User
definida como normal: los campos de la interfaz proyectada deben coincidir con los campos de esta entidad.
De lo contrario, la asignación de campo podría romperse (
getFirstname()
podría devolver el valor del apellido, etc.).
Nota 2:
Si usa
SELECT table.column ...
notación siempre defina alias que coincidan con los nombres de la entidad.
Por ejemplo, este código no funcionará correctamente (la proyección devolverá valores nulos para cada getter):
@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 caso de consultas más complejas, prefiero usar
JdbcTemplate
con un repositorio personalizado.
En mi computadora, me funciona este código. 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")
Puede escribir su consulta nativa o no nativa de la manera que desee, y puede ajustar los resultados de la consulta JPQL con instancias de clases de resultados personalizados. 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 devueltos por 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;
}
}
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;
}
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
documentation
Spring Data JPA, Spring primero intentará encontrar una consulta con nombre que coincida con el nombre de su método, por lo que al usar
@NamedNativeQuery
,
@SqlResultSetMapping
y
@ConstructorResult
debería poder lograr ese comportamiento