IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Hibernate Java Discussion :

Supprimer dans une association ManyToMany


Sujet :

Hibernate Java

  1. #1
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut Supprimer dans une association ManyToMany
    Coucou ! C'est encore moi !

    Suite à l'aide efficace, notamment de tchize, j'ai maintenant le bon affichage quand l'étudiant qui se connecte est inscrit à un stage.

    Bonjour Test Test.

    Vous êtes inscrit au stage Histoire, du 2011-05-03 au 2011-05-20, dans l'établissement EPLEA Capou de Montauban (82).
    Sous ce texte d'accueil, à la place du bouton "Voir les stages", j'affiche un bouton "Me désinscrire" qui, comme son nom l'indique, doit effacer l'inscription de l'étudiant au stage, laquelle est physiquement enregistrée dans une table associative de ma BDD.

    Rappel du morceau de MCD concerné :
    ThStageStg -(1,1)----Etre----1,1- TeSessionSsn -0,n----Inscrire----0,n- ThCandidatCnd -(1,1)----Etre----0,1- ThUtilisateurUti

    J'ai donc créé une méthode desinscrire() dans AccueilEtudiant.java :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        /**
         * desinscrire
         * Désinscrit l'étudiant de son stage
         * @return String retour à la page d'accueil de l'étudiant
         */
        public String desinscrire()
        {
            log.info("accueilEtudiant.desinscrire() ");
     
            // Instanciation du user en tant que candidat pour accéder à la session à laquelle est inscrit le candidat
            ThCandidatCnd candidat = (ThCandidatCnd) user;
            log.info("accueilEtudiant.desinscrire() - après instanciation candidat");
            stage.removeFromCandidat(candidat);
            log.info("accueilEtudiant.desinscrire() après remove");
            return "/AccueilEtudiant.xhtml";
        }
    => Celle-ci fait appel à la méthode removeFromCandidat de TeSessionSsn que j'ai écrite en suivant cet exemple du manuel de référence d'Hibernate, au chapitre 1.3.6.
    1.3.6. Travailler avec des liens bidirectionnels

    Premièrement, gardez à l'esprit qu'Hibernate n'affecte pas la sémantique normale de Java. Comment avons-nous créé un lien entre une Person et un Event dans l'exemple unidirectionnel ? Nous avons ajouté une instance de Event à la collection des références d'événement d'une instance de Person. Donc, évidemment, si vous voulons rendre ce lien bidirectionnel, nous devons faire la même chose de l'autre côté - ajouter une référence de Person à la collection d'un Event. Cette "configuration du lien des deux côtés" est absolument nécessaire et vous ne devriez jamais oublier de le faire.
    Beaucoup de développeurs programment de manière défensive et créent des méthodes de gestion de lien pour affecter correctement les deux côtés, par exemple dans Person :
    protected Set getEvents() {
    return events;
    }

    protected void setEvents(Set events) {
    this.events = events;
    }

    public void addToEvent(Event event) {
    this.getEvents().add(event);
    event.getParticipants().add(this);
    }

    public void removeFromEvent(Event event) {
    this.getEvents().remove(event);
    event.getParticipants().remove(this);
    }
    Voici la méthode que j'ai écrite dans TeSessionSsn dont hérite ThStageStg :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        /**
         * removeFromCandidat
         * Suppression d'une inscription d'un candidat à cette session
         * @param ThCandidatCnd candidat : le candidat dont il faut supprimer l'inscription à cette session
         */
        public void removeFromCandidat(ThCandidatCnd candidat)
        {
            this.getThCandidatCnds().remove(candidat);
            candidat.getTeSessionSsns().remove(this);
        }
    Sauf que j'ai un beau message d'erreur à l'exécution :
    16:49:23,638 INFO [AccueilEtudiant] accueilEtudiant.desinscrire()
    16:49:23,638 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() - après instanciation candidat
    16:49:23,639 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
    ...
    Cela veut-il dire qu'il faut que j'aille chercher la session de l'entityManager pour utiliser user et stage qui sont importés et dont pourtant je lis bien les propriétés dans la page AccueilEtudiant ?

    Pas après pas, j'y arriverai !
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  2. #2
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Deux jours plus tard... le boulet n'a pas avancé !

    Dans le message ci-dessus, j'instanciais user en tant que candidat et j'appelais la méthode removeFromCandidat de TeSessionSsn dont hérite ThStageStg.

    J'ai essayé d'instancier le stage en tant que session et d'appeler la méthode removeFromSession de ThCandidatCnd :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        public String desinscrire()
        {
            try
            {
                log.info("accueilEtudiant.desinscrire() ");
     
                // Instanciation du user en tant que candidat pour accéder à la session à laquelle est inscrit le candidat
                ThCandidatCnd candidat = (ThCandidatCnd) user;
                log.info("accueilEtudiant.desinscrire() - après instanciation candidat");
     
                TeSessionSsn ses = (TeSessionSsn) stage;
     
                candidat.removeFromSession(ses);
                //candidat.getTeSessionSsns().remove(ses);
                //ses.getThCandidatCnds().remove(candidat);
                log.info("accueilEtudiant.desinscrire() après remove");
                entityManager.flush();
                log.info("accueilEtudiant.desinscrire() après flush");
                return "OK";
            }
    => J'ai le même genre d'erreur qui doit se produire durant l'exécution de la méthode :
    11:34:07,844 INFO [AccueilEtudiant] accueilEtudiant.desinscrire()
    11:34:07,844 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() - après instanciation candidat
    11:34:07,845 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
    J'ai aussi essayé de supprimer directement à partir de l'instance de candidat dans la méthode desinscrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        public String desinscrire()
        {
            try
            {
                log.info("accueilEtudiant.desinscrire() ");
     
                // Instanciation du user en tant que candidat pour accéder à la session à laquelle est inscrit le candidat
                ThCandidatCnd candidat = (ThCandidatCnd) user;
                log.info("accueilEtudiant.desinscrire() - après instanciation candidat");
     
                TeSessionSsn ses = (TeSessionSsn) stage;
     
                //candidat.removeFromSession(ses);
                candidat.getTeSessionSsns().remove(ses);
                //ses.getThCandidatCnds().remove(candidat);
                log.info("accueilEtudiant.desinscrire() après remove");
                entityManager.flush();
                log.info("accueilEtudiant.desinscrire() après flush");
                return "OK";
            }
    => Cette fois je n'ai pas d'erreur mais la suppression ne se fait pas dans la BDD.
    11:42:43,301 INFO [AccueilEtudiant] accueilEtudiant.desinscrire()
    11:42:43,301 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() - après instanciation candidat
    11:42:43,301 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() après remove
    11:42:43,301 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() après flush
    J'ai pensé que c'était parce qu'il manque la suppression inverse, présente dans la méthode removeFromCandidat ou removeFromSession et j'ai ajouté la suppression inverse :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        public String desinscrire()
        {
            try
            {
                log.info("accueilEtudiant.desinscrire() ");
     
                // Instanciation du user en tant que candidat pour accéder à la session à laquelle est inscrit le candidat
                ThCandidatCnd candidat = (ThCandidatCnd) user;
                log.info("accueilEtudiant.desinscrire() - après instanciation candidat");
     
                TeSessionSsn ses = (TeSessionSsn) stage;
     
                //candidat.removeFromSession(ses);
                candidat.getTeSessionSsns().remove(ses);
                ses.getThCandidatCnds().remove(candidat);
                log.info("accueilEtudiant.desinscrire() après remove");
                entityManager.flush();
                log.info("accueilEtudiant.desinscrire() après flush");
                return "OK";
            }
    => Retour de l'erreur !
    11:48:01,944 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() - après instanciation candidat
    11:48:01,945 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
    Quelqu'un pour m'aider ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  3. #3
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    J'en ai eu marre ! Je suis passé par une nativeQuery et ça marche ! Et ça ne m'a pris que 5 mn à écrire et à tester !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
                Query q = entityManager.createNativeQuery(
                    "DELETE FROM tj_cnd_inscrire_ssn_cis " +
                    "WHERE cis_id_candidat = :idcandidat " +
                        "AND cis_id_session = :idsession ");
                q.setParameter("idcandidat", user.getPrsId());
                q.setParameter("idsession", stage.getSsnId());
     
                q.executeUpdate();
    Plus ça va, plus je dis que Hibernate est inutilement compliqué !

    N'empêche que si quelqu'un a une explication claire à me donner sur le pourquoi du comment que la précédente méthode engendrait une erreur, il est le bienvenu.
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  4. #4
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    l'explication claire est dans le message:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds no session or session was closed
    La session associée à ton objet n'est plus active (closed), il est donc devenu impossible de faire la moindre manipulation lazy. La session étant fermée, plus de pont avec la db, etc Tu as maintenant un simple POJO détaché de la DB.

    Maintenant la question à savoir c'est pourquoi tu as fermé ta session avant de finir le travail En hibernate, on travaille toujours en gros de la même manière

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    session = ...créer session....
    get,manipulations, appels à session.saveOrUpdate(), etc
    session.close();
    // a partir de là on ne fais plus rien avec les objets

  5. #5
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    Et si tu est obligé de travailler avec deux sessions différentes (exemple tu récupère user/stage dans une requete, tu veux les manipuler dans une autre requetes http), utiliser

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
                ThCandidatCnd candidat = (ThCandidatCnd) user;
    entitymanager.merge(candidat); // candidata maintenant associé à cet entitymanager
                log.info("accueilEtudiant.desinscrire() - après instanciation candidat");
     
                TeSessionSsn ses = (TeSessionSsn) stage;
    entitymanager.merge(stage)
                //candidat.removeFromSession(ses);
                candidat.getTeSessionSsns().remove(ses);

  6. #6
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    J'avais à peu près compris le sens du message et mes recherches à son sujet me faisait poser la même question :
    pourquoi tu as fermé ta session avant de finir le travail
    Sauf que je n'ai rien fait chef ! C'est Seam qui se débrouille tout seul avec ça !
    Il affiche la page avec les bonnes informations issues des objets user et stage importés depuis Authenticate.java / login.xhtml puis ferme tout.
    Je pensais qu'en instanciant les objets, comme tu m'as appris à le faire dans l'autre discussion, je récupérerais la maîtrise complète des propriétés et des méthodes de ces objets mais apparemment ce n'est pas comme ça que ça fonctionne dans le monde Seam.

    Il y a encore un paquet de trucs qui m'échappent dans cette usine à gaz !

    Bon maintenant faut que je trouve comment actualiser la page pour qu'il prenne en compte la nouvelle situation de l'étudiant !

    Au problème suivant donc...
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  7. #7
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    seam applique une recommandation de hibernate en mode web: une session / requete HTTP. Donc forcément, une fois le rendu fait, la session est cloturée. On va pas la garder ouvert, ce truc bouffe des ressources. Comme je l'ai montrer, faut réattacher tes objet si tu veux réutiliser des objets entre les requetes http. D'ailleurs, en général, il vaux mieux être prudent quand on stocke des objets hibernate dans la session, ca pose pas mal de problèmes, ne serait-ce que celui du comportement quand ces objets sont manipulés en parallèle par plusieurs requetes http simultanées (la session hibernate n'étant pas multi threadée)

  8. #8
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Je viens d'essayer ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
                ThCandidatCnd candidat = (ThCandidatCnd) user;
                entityManager.merge(candidat);
                TeSessionSsn ses = (TeSessionSsn) stage;
                entityManager.merge(ses);
                log.info("accueilEtudiant.desinscrire() - après instanciations");
     
     
                candidat.removeFromSession(ses);
    Erreur :
    14:35:56,998 DEBUG [Loader] result set contains (possibly empty) collection: [org.domain.stamas.entity.ThCandidatCnd.teSessionSsns#2]
    14:35:56,998 DEBUG [Loader] result row: EntityKey[org.domain.stamas.entity.TeSessionSsn#2]
    14:35:56,999 DEBUG [Loader] found row of collection: [org.domain.stamas.entity.ThCandidatCnd.teSessionSsns#2]
    14:35:56,999 DEBUG [AbstractBatcher] about to close ResultSet (open ResultSets: 1, globally: 1)
    14:35:56,999 DEBUG [AbstractBatcher] about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
    14:35:56,999 DEBUG [TwoPhaseLoad] resolving associations for [org.domain.stamas.entity.ThStageStg#2]
    14:35:56,999 DEBUG [TwoPhaseLoad] done materializing entity [org.domain.stamas.entity.ThStageStg#2]
    14:35:57,000 DEBUG [CollectionLoadContext] 1 collections were found in result set for role: org.domain.stamas.entity.ThCandidatCnd.teSessionSsns
    14:35:57,000 DEBUG [CollectionLoadContext] collection fully initialized: [org.domain.stamas.entity.ThCandidatCnd.teSessionSsns#2]
    14:35:57,000 DEBUG [CollectionLoadContext] 1 collections initialized for role: org.domain.stamas.entity.ThCandidatCnd.teSessionSsns
    14:35:57,000 DEBUG [StatefulPersistenceContext] initializing non-lazy collections
    14:35:57,000 DEBUG [Loader] done loading collection
    14:35:57,001 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() - après instanciations
    14:35:57,001 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.domain.stamas.entity.TeSessionSsn.thCandidatCnds, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
    ...
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  9. #9
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    j'ai fait mon fénaint sur le coup :/

    Tu dois récupérer la valeur qui sort de merge, c'est ta nouvelle entité, pas nécessairement la meme que la précédente:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
                ThCandidatCnd candidat = (ThCandidatCnd) user;
                candidat = entityManager.merge(candidat);
                TeSessionSsn ses = (TeSessionSsn) stage;
                ses = entityManager.merge(ses);
                log.info("accueilEtudiant.desinscrire() - après instanciations");
     
     
                candidat.removeFromSession(ses);
    il reste quand meme plus prudent de redemander les données à la db à chaque requete plutot que de prendre le risque de cacher les pojos en session

  10. #10
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Citation Envoyé par tchize_ Voir le message
    j'ai fait mon fénaint sur le coup :/

    Tu dois récupérer la valeur qui sort de merge, c'est ta nouvelle entité, pas nécessairement la meme que la précédente:
    Effectivement, ça fonctionne mieux avec ton nouveau code !

    il reste quand meme plus prudent de redemander les données à la db à chaque requete plutot que de prendre le risque de cacher les pojos en session
    Dans le cas présent, ça ne risque pas puisque c'est la donnée propre de l'utilisateur. À moins qu'il soit assez tordu pour ouvrir le site deux fois et demander à se désinscrire deux fois !

    Bon allez, un coup de mais c'est quand même vachement plus simple avec une nativeQuery !
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  11. #11
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    J'ai résolu un peu vite, il me reste une petite chose qui n'est pas purement Hibernate : Le réaffichage de la page avec la prise en compte du fait que l'étudiant n'est plus inscrit.

    Comme la méthode desinscrire() renvoie "OK" si l'opération s'est bien déroulée, j'ai pensé faire ce qui suit dans AccueilEtudiant.page.xml :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        <navigation from-action="#{accueilEtudiant.desinscrire}">
            <rule if-outcome="OK">
                <redirect/>    
            </rule>
        </navigation>
    Mais ça réaffiche la page avec l'inscription que l'étudiant vient de supprimer !

    C'est peut-être parce qu'au réaffichage de la page sont réinjectés les objets user et stage qui eux n'ont pas changé ?
    15:07:18,686 DEBUG [Component] trying to inject with hierarchical context search: Nom
    15:07:18,686 DEBUG [Component] trying to inject with hierarchical context search: Prenom
    15:07:18,686 DEBUG [Component] trying to inject with hierarchical context search: user
    15:07:18,686 DEBUG [Component] trying to inject with hierarchical context search: stage
    15:07:18,686 DEBUG [Component] trying to inject with hierarchical context search: statusMessages
    15:07:18,686 DEBUG [Component] trying to inject with hierarchical context search: entityManager
    Par contre je viens aussi de voir un truc qui m'inquiète :
    15:07:18,648 DEBUG [SQL]
    delete
    from
    stamas.tj_cnd_inscrire_ssn_cis
    where
    cis_id_candidat=?
    Hibernate:
    delete
    from
    stamas.tj_cnd_inscrire_ssn_cis
    where
    cis_id_candidat=?
    15:07:18,649 DEBUG [AbstractCollectionPersister] done deleting collection
    15:07:18,649 DEBUG [AbstractCollectionPersister] Deleting collection: [org.domain.stamas.entity.TeSessionSsn.thCandidatCnds#2]
    15:07:18,649 DEBUG [AbstractBatcher] Executing batch size: 1
    15:07:18,649 DEBUG [AbstractBatcher] about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
    15:07:18,649 DEBUG [AbstractBatcher] about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
    15:07:18,649 DEBUG [SQL]
    delete
    from
    stamas.tj_cnd_inscrire_ssn_cis
    where
    cis_id_session=?
    Hibernate:
    delete
    from
    stamas.tj_cnd_inscrire_ssn_cis
    where
    cis_id_session=?
    15:07:18,650 DEBUG [AbstractCollectionPersister] done deleting collection
    15:07:18,650 DEBUG [AbstractBatcher] Executing batch size: 1
    15:07:18,650 DEBUG [AbstractBatcher] about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
    15:07:18,650 INFO [AccueilEtudiant] accueilEtudiant.desinscrire() après flush
    15:07:18,650 DEBUG [SeamPhaseListener] committing transaction after phase: INVOKE_APPLICATION 5
    15:07:18,651 DEBUG [EntityTransaction] committing JPA resource-local transaction
    15:07:18,651 DEBUG [JDBCTransaction] commit
    Ne va t-il pas lancer les deux requêtes DELETE et donc suppimer toutes les inscriptions à la session de stage, pas seulement celle du candidat actif ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  12. #12
    Expert éminent sénior
    Avatar de tchize_
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2007
    Messages
    25 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : Belgique

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2007
    Messages : 25 481
    Points : 48 806
    Points
    48 806
    Par défaut
    c'est parceque les deux pojo que tu as gardé en session ne sont plus synchro à hibernate. Donc les changement que tu a fait (sur des nouveaux pojos sortis de merge) ne sont pas visible dans les pojos de session. Il faut les mettres à jour aussi (exempel typique des risque qu'on encourt à garder des objet hibernate en session: il faut les mettre à jour). Il est préférable de garder juste en session les Ids et de faire des méthode genre "getCurrentUser" ou "getCurrentStage" qui font le entitymanager.find ou entitymanager.getReference
    Garder les pojo dans la session pour éviter de surcharger le sgdb à chauqe requete http est une fausse bonne idée:
    -> tu va quand meme le charger avec tes merge
    -> tu va surcharger ton serveur d'objets encombrant dans les session (la session hibernate, même fermée, reste attachée à ces objets!)
    -> tu va te surcharger de travail pour synchroniser tout à la main

  13. #13
    Expert confirmé
    Homme Profil pro
    Inscrit en
    Septembre 2006
    Messages
    2 937
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 2 937
    Points : 4 358
    Points
    4 358
    Par défaut
    En fonction du type de container du @OneToMany/@ManyToMany (List<>, Set<>, …), de la manière dont la relation est implémentée (avec ou sans table intermédiaire) et parfois du SGBD cible, Hibernate peut choisir de supprimer tous les éléments pour ensuite remettre ceux qui restent.

    Il y a une page de la documentation qui parle de ces aspects liés à la sémantique "bag" ou "set".

    (dans certains cas, il peut être judicieux de spécifier "orphanRemoval=true" dans l'annotation @…ToMany)

  14. #14
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    Oui tchize c'est ce que j'ai compris. Et comme l'affichage adéquat dépend du fait que l'objet stage est vide ou pas, forcément il réaffiche les mêmes infos.

    J'ai ajouté une propriété booléenne dans Authenticator.java que j'exporte vers AccueilEtudiant et je la passe à true si l'étudiant est inscrit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        // Indique si l'utilisateur étudiant est inscrit à un stage
        @Out(value="InscritStage", required=false, scope=ScopeType.SESSION)
        private boolean inscritStage = false;
     
    ...
                            // Recherche d'une session de stage à laquelle est inscrit l'étudiant
                            log.info("authenticating - recherche session de stage");
                            Set<TeSessionSsn> sessions = candidat.getTeSessionSsns();
     
                            for (TeSessionSsn ses : sessions) // En réalité, un étudiant ne peut être inscrit qu'à un seul stage
                            {
                                // Instanciation de la session en tant que stage pour accéder aux propriétés du stage
                                setStage((ThStageStg) ses);
                                setInscritStage(true);
                            } // Fin for (TeSessionSsn ses : sessions)
    Dans AccueilEtudiant.java, je récupère la propriété et je la mets à false si l'étudiant s'est désinscrit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        // Indique si l'utilisateur est inscrit à un stage
        @In(value="InscritStage", required = false) boolean inscritStage;
    ...
                ThCandidatCnd candidat = (ThCandidatCnd) user;
                candidat = entityManager.merge(candidat);
                TeSessionSsn ses = (TeSessionSsn) stage;
                ses = entityManager.merge(ses);
     
                candidat.removeFromSession(ses);
                //candidat.getTeSessionSsns().remove(ses);
                //ses.getThCandidatCnds().remove(candidat);
                log.info("accueilEtudiant.desinscrire() après remove");
                entityManager.flush();
                log.info("accueilEtudiant.desinscrire() après flush");
     
                setInscritStage(false);
     
                return "OK";
    Et dans AccueilEtudiant.xhtml, je teste maintenant cette propriété pour le rendu de l'affichage :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
                <h:outputText 
                    value="Vous avez été reçu au concours écrit."
                    rendered="#{not accueilEtudiant.inscritStage}" />
                <br />
                <h:outputText 
                    value="Sur cet espace, vous pouvez vous inscrire à un stage Master dans un lycée agricole."
                    rendered="#{not accueilEtudiant.inscritStage}" />
                <h:outputText 
                    value="Vous êtes inscrit au stage #{accueilEtudiant.stage.ssnLibelle}, 
                        du #{accueilEtudiant.stage.ssnDateDebut} 
                        au #{accueilEtudiant.stage.ssnDateFin}, 
                        dans l'établissement #{accueilEtudiant.stage.getTeEtablissementEtb().etbNom} 
                        de #{accueilEtudiant.stage.getTeEtablissementEtb().getTrCommuneCmn().cmnNom} 
                        (#{accueilEtudiant.stage.getTeEtablissementEtb().getTrCommuneCmn().getTrDepartementDpt().dptNumero})." 
                    rendered="#{accueilEtudiant.inscritStage}" />
    Mais ça réaffiche les infos du stage après désinscription quand même. Il semble qu'il garde l'ancienne valeur de inscritStage.

    EDIT :
    Comme j'ai commencé ma réponse, tout en faisant des tests, avant que tchize ait complété son message, je mets la suite ci-dessous...
    Citation Envoyé par tchize_
    Il est préférable de garder juste en session les Ids et de faire des méthode genre "getCurrentUser" ou "getCurrentStage" qui font le entitymanager.find ou entitymanager.getReference
    Garder les pojo dans la session pour éviter de surcharger le sgdb à chauqe requete http est une fausse bonne idée:
    -> tu va quand meme le charger avec tes merge
    -> tu va surcharger ton serveur d'objets encombrant dans les session (la session hibernate, même fermée, reste attachée à ces objets!)
    -> tu va te surcharger de travail pour synchroniser tout à la main
    Je vais effectivement essayer d'aller dans cette voie car ce que j'ai écrit au dessus montre qu'effectivement, il y a problème !

    Citation Envoyé par JeitEmgie
    En fonction du type de container du @OneToMany/@ManyToMany (List<>, Set<>, …), de la manière dont la relation est implémentée (avec ou sans table intermédiaire) et parfois du SGBD cible, Hibernate peut choisir de supprimer tous les éléments pour ensuite remettre ceux qui restent.

    Il y a une page de la documentation qui parle de ces aspects liés à la sémantique "bag" ou "set".

    (dans certains cas, il peut être judicieux de spécifier "orphanRemoval=true" dans l'annotation @…ToMany)
    Euh... si cette dernière option fait ce que je crois qu'elle fait, je ne veux surtout pas qu'elle me supprime le candidat et/ou la session de stage sous prétexte qu'ils ne sont plus associés !
    Après désinscription du candidat, la session redevient libre pour un autre... ou pour lui s'il s'est trompé.

    La suite demain car là je sature avec cette histoire !
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

  15. #15
    Modérateur

    Avatar de CinePhil
    Homme Profil pro
    Ingénieur d'études en informatique
    Inscrit en
    Août 2006
    Messages
    16 799
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur d'études en informatique
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2006
    Messages : 16 799
    Points : 34 031
    Points
    34 031
    Billets dans le blog
    14
    Par défaut
    J'ai essayé mais...

    Dans Authenticator.java, j'ai supprimé les propriétés user et stage et je n'exporte cette fois que idUtilisateur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    @Name("authenticator")
    public class Authenticator
    {
        // ============================= Propriétés ==============================
     
        @Logger private Log log;
     
        @In Identity identity;
        @In Credentials credentials;
        @In EntityManager entityManager;
     
        // Page suivante après connexion ou insuccès de l'authentification
        private String pageSuivante = "/home.xhtml";
     
        // Identifiant de l'utilisateur connecté
        @Out(value = "IdUtilisateur", required = false, scope = ScopeType.SESSION)
        private Integer idUtilisateur;
     
        // ========================== Méthodes =======================
     
        /**
         * authenticate
         * Contrôle la connection de l'utilisateur et l'oriente sur sa page d'accueil en cas de succès
         * @return boolean TRUE si succès, FALSE si échec de l'authentification
         */
        public boolean authenticate()
        {
     
            try
            {
                log.info("authenticating {0}", credentials.getUsername());
     
                // Recherche de l'utilisateur qui se connecte
                Query query = entityManager.createQuery(
                        "FROM ThUtilisateurUti u " +
                        "WHERE u.utiLogin = :username " +
                            "AND u.utiMotPasse = :password");
     
                query.setParameter("username", credentials.getUsername());
     
                String passwd = MD5.generateMD5passwd(credentials.getPassword());
                query.setParameter("password", passwd);
     
                ThUtilisateurUti user = (ThUtilisateurUti) query.getSingleResult();
                setIdUtilisateur(user.getPrsId());
     
                            // Ajout du type d'utilisateur pour contrôle d'accès aux fonctions du site
                identity.addRole(user.getTeTypeUtilisateurTu().getTuLibelle());
     
                // Extraction du code du type d'utilisateur
                int typeUtilisateur = user.getTeTypeUtilisateurTu().getTuId();
     
                // Actions selon le type d'utilisateur
                switch (typeUtilisateur)
                {
                    // Administrateur
                    case 1 : 
                        setPageSuivante("/home.xhtml");
                        return true;
     
                    // Gestionnaire
                    case 2 : 
                        setPageSuivante("/accueuilGestionnaire.xhtml");
                        return true;
     
                    // Étudiant
                    case 3 : 
                        log.info("authenticating {0} - Authentifié en tant qu'étudiant", credentials.getUsername());
     
                        setPageSuivante("/accueilEtudiant.xhtml");
                        return true;
     
                } // Fin switch (typeUtilisateur)
     
                setPageSuivante("/login.xhtml");
                return false;
     
            } // Fin try
            catch (NoResultException ex)
            {
                setPageSuivante("/login.xhtml");
                return false;
            } // Fin catch
     
        } // Fin public boolean authenticate()
    Dans AccueilEtudiant.java, j'ai essayé de chercher l'étudiant connecté dans le constructeur pour accéder aussi à son éventuel stage :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    @Name("accueilEtudiant")
    public class AccueilEtudiant
    {
        // ========================== Propriétés ==========================
        @Logger private Log log;
        @In StatusMessages statusMessages;
        @In EntityManager entityManager;
     
        // Utilisateur étudiant connecté
        private ThEtudiantEtu etudiant;
     
        // Identifiant de l'utilisateur étudiant connecté
        @In(value = "IdUtilisateur", scope = ScopeType.SESSION)
        private Integer idUtilisateur;
     
        // ========================== Méthodes =============================
     
        /**
         * AccueilEtudiant
         * Constructeur pour chercher les infos de l'étudiant
         */
        public AccueilEtudiant()
        {
            // Récupération de l'étudiant
            log.info("authenticating - récupération de l'étudiant {0}", idUtilisateur);
            setEtudiant((ThEtudiantEtu) entityManager.find(ThEtudiantEtu.class, idUtilisateur));
     
            // Recherche d'une session de stage à laquelle est inscrit l'étudiant
            log.info("AccueilEtudiant - recherche session de stage");
            Set<TeSessionSsn> sessions = etudiant.getTeSessionSsns();
     
            for (TeSessionSsn ses : sessions) // En réalité, un étudiant ne peut être inscrit qu'à un seul stage
            {
                // Instanciation de la session en tant que stage pour accéder aux propriétés du stage
                setStage((ThStageStg) ses);
                setInscritStage(true);
            } // Fin for (TeSessionSsn ses : sessions)
        }
    Dans le log d'Eclipse je n'ai pas d'erreur mais dans le navigateur, j'ai cette belle erreur :
    org.jboss.seam.InstantiationException: Could not instantiate Seam component: accueilEtudiant
    at org.jboss.seam.Component.newInstance(Component.java:2144)
    ...
    Caused by: java.lang.NullPointerException
    at org.domain.stamas.session.AccueilEtudiant.<init>(AccueilEtudiant.java:69)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
    at java.lang.Class.newInstance0(Class.java:372)
    at java.lang.Class.newInstance(Class.java:325)
    at org.jboss.seam.Component.instantiateJavaBean(Component.java:1438)
    at org.jboss.seam.Component.instantiate(Component.java:1359)
    at org.jboss.seam.Component.newInstance(Component.java:2122)
    ... 71 more
    Apparemment, et comme je l'ai déjà dit dans une autre discussion, je ne peux pas lancer ce processus de recherche dans le constructeur. J'avais essayé aussi de lancer une méthode dans le constructeur, ça fonctionnait pour l'affectation d'une valeur à une propriété textuelle mais pas pour une recherche sur une entité.

    Quel est le processus à suivre pour chercher les paramètres de l'étudiant et vérifier s'il est inscrit à un stage afin d'afficher la page selon son statut inscrit ou pas à un stage ?
    Philippe Leménager. Ingénieur d'étude à l'École Nationale Supérieure de Formation de l'Enseignement Agricole. Autoentrepreneur.
    Mon ancien blog sur la conception des BDD, le langage SQL, le PHP... et mon nouveau blog sur les mêmes sujets.
    « Ce que l'on conçoit bien s'énonce clairement, et les mots pour le dire arrivent aisément ». (Nicolas Boileau)
    À la maison comme au bureau, j'utilise la suite Linux Mageïa !

Discussions similaires

  1. [C#] Supprimer dans une listView
    Par thomfort dans le forum Windows Forms
    Réponses: 13
    Dernier message: 03/10/2006, 10h55
  2. forme juridique pour des prestations dans une association
    Par guigui5931 dans le forum Association
    Réponses: 8
    Dernier message: 15/07/2006, 15h46
  3. Compter le nombre d'enregistrement supprimer dans une jsp
    Par DarkWark dans le forum Servlets/JSP
    Réponses: 2
    Dernier message: 26/05/2006, 11h36
  4. Réponses: 2
    Dernier message: 12/05/2006, 12h00
  5. Réponses: 4
    Dernier message: 04/04/2006, 16h09

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo