web-dev-qa-db-fra.com

Spring Data JPA mappe le résultat de la requête native à un POJO non-entité

J'ai une méthode de référentiel Spring Data avec une requête native

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

et j'aimerais mapper le résultat à POJO non-entité GroupDetails.

Est-ce possible et si oui, pourriez-vous donner un exemple?

62
alexanoid

En supposant que GroupDetails soit dans la réponse de orid, avez-vous essayé 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")

et utiliser ce qui suit dans l'interface du référentiel:

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

Selon Spring Data JPA documentation , spring essaiera d’abord de trouver la requête nommée correspondant au nom de votre méthode - utilisez donc @NamedNativeQuery, @SqlResultSetMapping et @ConstructorResult vous devriez pouvoir obtenir ce comportement

53
Daimon

Je pense que la meilleure façon de le faire est d'utiliser ce qu'on appelle la projection. Il peut mapper les résultats de la requête aux interfaces. Utiliser SqlResultSetMapping est inconvenant et rend votre code laid :).

Un exemple tiré du code source JPA des données de printemps:

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

  }
}

Vous pouvez également utiliser cette méthode pour obtenir une liste de projections.

Consultez cette entrée de documentation JPA de données de printemps pour plus d'informations sur les projections.

Remarque 1:

N'oubliez pas de définir votre entité User comme étant normale. Les champs de l'interface projetée doivent correspondre aux champs de cette entité. Sinon, le mappage des champs pourrait être interrompu (getFirstname() pourrait renvoyer la valeur du nom de famille, etc.).

Note 2:

Si vous utilisez la notation SELECT table.column ..., Définissez toujours des alias correspondant aux noms d'entité. Par exemple, ce code ne fonctionnera pas correctement (la projection renverra des valeurs NULL pour chaque getter):

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

Mais cela fonctionne 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);

Dans le cas de requêtes plus complexes, je préférerais utiliser JdbcTemplate avec un référentiel personnalisé.

62
Michał Stochmal

Je pense que l'approche de Michal est meilleure. Cependant, il existe un autre moyen d'obtenir le résultat de la requête native.

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

Maintenant, vous pouvez convertir ce tableau de chaînes 2D en votre entité souhaitée.

5
Ashish

Vous pouvez écrire votre requête native ou non native comme vous le souhaitez et encapsuler les résultats de la requête JPQL avec des instances de classes de résultats personnalisées. Créez un DTO avec les mêmes noms de colonnes renvoyés dans une requête et créez un constructeur tout argument avec la même séquence et les mêmes noms que ceux renvoyés par la requête. Ensuite, utilisez la méthode suivante pour interroger la base de données.

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

Créer DTO:

package example;

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

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

Vous pouvez faire quelque chose comme

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

Et il doit y avoir un constructeur comme

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;
    }
1
Chandan Gawri