1
votes

Impossible de localiser l'erreur de constructeur appropriée pour l'objet de liste imbriquée dans kotlin et JpaRepository

Je suis confronté à l'erreur suivante lorsque JPA tente de mapper le résultat d'une requête vers la méthode de référentiel de résultats DTO:

data class User(
    val username: String,
    val password: String,
    val roles: List<Role>
)

data class Role(val roleName: String, val description: String)

J'utilise spring-boot-starter -data-jpa et plug-in org.jetbrains.kotlin.plugin.jpa dans mon projet Kotlin. J'ai un référentiel défini comme ceci:

@Entity
@Table(name = "user")
internal data class DbUser(
    @Id @Column val username: String,
    @Column val password: String,
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_role",
        joinColumns = [JoinColumn(name = "username", referencedColumnName = "username")],
        inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")]
    ) val roles: List<DbRole>
)

@Entity
@Table(name = "role")
internal data class DbRole(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long,
    @Column val roleName: String,
    @Column val description: String
)

Notez que le type utilisé par JpaRepository (DbUser) est différent de celui retuné par la méthode findUserByUsername (User) et aussi sur l'erreur au-dessus de la classe User est correctement trouvée par JPA ( ... class [com.example.dto.User] ... ) mais pas Role. Il attend un DbRole dans le DTO de destination, ce qui ne va pas.

DbUser est une classe annotée @Entity et fait référence à une autre classe annotée @Entity appelée DbRole. Les deux sont définis ci-dessous:

@Repository
internal interface JdbcUserRepository : UserRepository, JpaRepository<DbUser, String> {

    override fun findUserByUsername(username: String): User?
}

Et ci-dessous sont les classes où JPA doit mapper les résultats vers:

org.hibernate.hql.internal.ast.QuerySyntaxException: 
Unable to locate appropriate constructor on class [com.example.dto.User]. Expected arguments are: java.lang.String, java.lang.String, com.example.repository.DbRole

Quelqu'un sait-il comment résoudre ce problème et que JPA trouve et mappe correctement la liste d'entités imbriquées de DbRole à la liste DTO imbriquée de Role?


0 commentaires

3 Réponses :


1
votes

Le problème est que la requête de base de données ne peut renvoyer que des résultats simples. Le fournisseur de persistance peut le convertir en entités avec des listes d'entités imbriquées. Quant à dto , vous devez résoudre le problème vous-même.

Vous pouvez donc obtenir un résultat clair en utilisant User dto avec le constructeur comme ci-dessous

public Optional<User> findUserByUsername(username) {
    List<User> users = findUserByUsername(username);

    if(users.isEmpty()) {
        return Optional.empty();
    }

    User user = users.get(0);
    if(users.size() > 1) {
         users.subList(1, users.size()).forEach(u -> user.getRoles().addAll(u.getRoles()));
    }

    return Optional.of(user);
}


0 commentaires

0
votes

Pourquoi votre JdbcUserRepository renvoie-t-il un objet User pour findUserByUsername et non un DbUser ? La solution doit être évidente:

override fun findUserByUsername(username: String): DbUser?

Et vous faites ensuite le mappage manuellement.

Vous devriez penser que les gens savent pourquoi ils ont introduit plusieurs représentations du même objet au lieu d'utiliser une seule représentation aussi longtemps que possible ...


2 commentaires

"Pourquoi votre JdbcUserRepository renvoie-t-il un objet User pour findUserByUsername et non un DbUser?" Pour séparer les préoccupations entre le niveau des cas d'utilisation et le niveau des données / référentiel en suivant les principes d'architecture propre. Les cas d'utilisation n'ont aucune dépendance par rapport aux niveaux les plus externes sinon ce serait une violation de flux entrant, dans ce cas, les objets qui définissent la base de données référencée par les cas d'utilisation.


Ce que vous écrivez n'a presque aucun sens. Le mappage entre la base de données et le modèle est effectué indépendamment de ce que vous pouvez appeler. Mais au lieu d'avoir une simple relation 1: 1 entre votre base de données et vos entités et de mapper manuellement les informations entre vos entités et votre modèle, vous voulez que le mappage soit fait "par magie" par JPA et donc compliquer votre algorithme de mappage. Quelle est l'utilité du DbUser si vous pouvez "simplement" utiliser l'Utilisateur pour toutes les opérations de lecture? Et pourquoi votre base de données a-t-elle besoin de connaître l'utilisateur et le DbUser et pas seulement l'un d'entre eux?



0
votes

J'ai dû faire une étape supplémentaire de diffusion comme si cast (ds.demoId as java.lang.String) vérifiez ceci:

@Query( value = "select new com.api.models.DsResultStatus("+
            "cast(ds.demoId as java.lang.String),cast(ds.comp as java.lang.String),cast(ds.dc as java.lang.String),cast(be.buildUrl as java.lang.String)" +
            ",cast(be.username as java.lang.String),cast(tr.title as java.lang.String),cast(tr.result as java.lang.String))  \n" +
            "from DsEntity ds \n" +
            "inner join BtEntity be  ON ds.id = be.demoAssGuardingEntity\n" +
            "inner join  TrEntity tr ON be.id = tr.buildEntity\n" +
            "where ds.demoId in (?1) " +
            "and tr.title in ('\"Amazon\"','\"Google\"','\"FB\"') and tr.result = '\"failed\"'")

List<DemoAssGuardingBuildResultStatus> getAssIdsWithFaliures(@Param("demoIds") Set<String> demoIds);


0 commentaires