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

Spring Boot Java Discussion :

Apprendre à développer les services REST avec Spring Boot et Spring RestTemplate


Sujet :

Spring Boot Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut Apprendre à développer les services REST avec Spring Boot et Spring RestTemplate
    Bonjour chers amis développeurs,

    J'ai rédigé ce tutoriel qui permet d'apprendre à développer les services REST avec Spring Boot et Spring RestTemplate. L'idée dans ce tutoriel consiste à développer les services REST client/serveur dans deux applications distinctes et surtout à montrer qu'avec Spring Boot, le développeur ne passe plus trop du temps de la configuration de son projet, et peut ainsi déployer très facilement son application dans un environnement de développement comme dans un environnement de production.

    Cet espace vous permet de donner votre point de vue et éventuellement une autre façon de répondre au même besoin. Votre participation est très attendue.

    Merci d'avance
    Bertrand Nguimgo



    Retrouvez les meilleurs cours et tutoriels pour apprendre Spring
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  2. #2
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    Bonjour

    J'ai parcouru ce tuto et je dois dire qu'il est très complet et très bien fait.

    Mais j'ai quand même quelques questions d'enquiquineur ^^

    - UserDTO : pourquoi créer un autre objet plutôt que d'utiliser directement l'objet User? (sachant qu'ils sont identiques, modulo le hashcode/equals)
    Le pattern DTO oblige à sans arrêt passer d'un type d'objet à un autre juste pour obtenir une "pseudo" isolation des couches DAO/Services qui sont quand même liées les unes aux autres.
    Accessoirement, ça se passe bien dans le cas présent parce qu'on ne gère pas de grappe d'objet.

    - L'injection Spring : afin de faciliter les tests unitaires, il pourrait être bon de faire de l'autowire par constructeur plutôt que par propriété
    Avec l'exemple de RoleServiceImpl
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    @Service(value = "roleService") // c'est optionnel de préciser le value s'il existe une seule implémentation de l'interface
    public class RoleServiceImpl implements RoleService {
     
        private RoleRepository roleRepository;
     
        @Autowired
        public RoleServiceImpl(RoleRepository roleRepository){
            this.roleRepository = roleRepository;
        }
     
        ...
    }
    Ce qui permettrait de créer des tests unitaires sous la forme suivante :
    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
     
    // TU pur : pas d'injection ou de dépendances autres que la classe testée
    public class RoleServiceImplTest {
     
        @Test
        public void should_get_all_roles_when_asking_for_all_roles(){
            // ARRANGE
            RoleRepository roleRepository = Mockito.mock(RoleRepository.class);
            RoleServiceImpl roleService = new  RoleServiceImpl (roleRepository );
     
            Mockito.when(roleRepository.findAll().thenReturn(RoleFixture.getRoles());// classe RoleFixture permettant de créer des rôles à créer
            // ACT
            Collection<Role> result = roleService.getAllRoles();
     
            // ASSERT        
            Assertions.assertThat(result).isNotNull() // Assertions vient de assertJ qui est un excellent framework pour la rédaction de tests unitaires
                    .hasSize(5)
                    ....
        }
     
        ...
    }
    - Pourquoi est-ce que le code "métier" de la sauvegarde de l'utilisateur se trouve dans le contrôleur ?
    Si les services ne servent que de "passe plat" pour les DAO, ils n'ont pas de valeur propre (à part des valeurs purement techniques comme la gestion des transaction et de l'encryptage des mots de passe)

    - Un détail : c'est mieux quand les logger sont `private static final` (il manque le final là)

    - Les requête POST/PUT sont en XML et le retour en JSON, ça fait bizarre non? C'est voulu pour montrer qu'on gère ce qu'on veut comme type de données?

    - Les tests d'intégration avec TestRestTemplate : c'est TOP !

    - Y'a un repo git de dispo avec le code source pour pouvoir faire des propositions d'amélioration?


    Je le répète, c'est du chipotage, et le guide en l'état est très bien.
    Je ne suis pas mort, j'ai du travail !

  3. #3
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Bonjour Monsieur eulbobo,

    Vous avez un regard très pointilleux, et je suis flatté de vous répondre. Merci pour vos remarques.

    Citation Envoyé par eulbobo Voir le message
    - UserDTO : pourquoi créer un autre objet plutôt que d'utiliser directement l'objet User?
    Cette démarche a pour objectif de faire comprendre qu'il est préférable de manipuler un DTO que de manipuler l'objet lui-même, même si dans le cas présent, les deux sont strictement identiques.

    Citation Envoyé par eulbobo Voir le message
    @Service(value = "roleService") // c'est optionnel de préciser le value s'il existe une seule implémentation de l'interface
    Tout à fait, mais de prudence, il faut le préciser pour faciliter la tâche à l'évolution future des différentes implémentations

    Citation Envoyé par eulbobo Voir le message
    - L'injection Spring : afin de faciliter les tests unitaires, il pourrait être bon de faire de l'autowire par constructeur plutôt que par propriété
    Avec l'exemple de RoleServiceImpl
    Très bonne remarque, super!!!!!, avantage donnée aux tests. Avec ça plus besoin d'injecter une instance de la classe à tester. Vous avez parfaitement raison. Merci encore

    Citation Envoyé par eulbobo Voir le message
    - Pourquoi est-ce que le code "métier" de la sauvegarde de l'utilisateur se trouve dans le contrôleur ?
    Ça n'est pas normal. A revoir. Merci pour la remarque

    Citation Envoyé par eulbobo Voir le message
    - Un détail : c'est mieux quand les logger sont `private static final` (il manque le final là)
    Erreur de frappe, vous avez un œil pointu, il faudra mettre à jour

    Citation Envoyé par eulbobo Voir le message
    - Les requête POST/PUT sont en XML et le retour en JSON, ça fait bizarre non? C'est voulu pour montrer qu'on gère ce qu'on veut comme type de données?
    C'est fait express. Car c'est un des avantages de Spring Boot. Il sait se débrouiller tout seul. Et j'ai signalé ce mécanisme quelque part dans le tutoriel

    J'ai tenu à automatiser les tests unitaires et les tests d'intégration comme vous l'avez si bien remarqué.

    Bravo pour toutes vos remarques, je prendrais en compte ces remarques pour une prochaine mise à jour.

    Cordialement
    Bertrand Nguimgo
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  4. #4
    Invité
    Invité(e)
    Par défaut
    Très bon tutoriel, très complet.
    Je vais pinailler aussi : bcrypt est une fonction de hachage, le mot de passe n'est donc pas "encrypté" (chiffré), mais haché.

  5. #5
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Citation Envoyé par John Bournet Voir le message
    Très bon tutoriel, très complet.
    Je vais pinailler aussi : bcrypt est une fonction de hachage, le mot de passe n'est donc pas "encrypté" (chiffré), mais haché.
    Remarque à prendre en compte.

    Merci !

    Bertrand
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  6. #6
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    Bonjour

    Je reviens à nouveau sur deux trucs :

    Citation Envoyé par parchemal Voir le message
    Cette démarche a pour objectif de faire comprendre qu'il est préférable de manipuler un DTO que de manipuler l'objet lui-même, même si dans le cas présent, les deux sont strictement identiques.
    J'ai souvent entendu cet argument du "c'est mieux"
    Je n'ai jamais entendu autre chose de constructif sur le POURQUOI ça serait "mieux" de "copier" les données d'un wrapper à un autre.
    Mais j'ai souvent vu des horreurs avec de la copie en boucle de données d'une grappe d'objets à une autre... Genre une grappe d'objets hibernates qui sont tous copiés un par un dans le DTO qui leur correspond -> duplication totale de code, risque de bug, complexité inutile, etc...

    Et je suis pragmatique, j'ai tendance à ne pas faire de code qui ne m'apporte rien.

    Tout à fait, mais de prudence, il faut le préciser pour faciliter la tâche à l'évolution future des différentes implémentations
    Sur les anciennes versions de Spring, je dis pas, depuis la version 4 je n'ai plus jamais eu le problème.
    Attention de ne pas prévoir des trucs qui ne servent pas au simple principe que peut-être un jour ça sera utile :p
    (cf ci dessus : pas de code qui ne me sert pas même si c'est pour de la configuration)

    C'est fait express. Car c'est un des avantages de Spring Boot. Il sait se débrouiller tout seul. Et j'ai signalé ce mécanisme quelque part dans le tutoriel
    Alors c'est une très bonne initiative ^^
    Je ne suis pas mort, j'ai du travail !

  7. #7
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Bonjour M eulbobo,

    Remarques à prendre en compte.

    Merci pour votre contribution

    Bertrand

    Citation Envoyé par eulbobo Voir le message
    Bonjour

    Je reviens à nouveau sur deux trucs :


    J'ai souvent entendu cet argument du "c'est mieux"
    Je n'ai jamais entendu autre chose de constructif sur le POURQUOI ça serait "mieux" de "copier" les données d'un wrapper à un autre.
    Mais j'ai souvent vu des horreurs avec de la copie en boucle de données d'une grappe d'objets à une autre... Genre une grappe d'objets hibernates qui sont tous copiés un par un dans le DTO qui leur correspond -> duplication totale de code, risque de bug, complexité inutile, etc...

    Et je suis pragmatique, j'ai tendance à ne pas faire de code qui ne m'apporte rien.


    Sur les anciennes versions de Spring, je dis pas, depuis la version 4 je n'ai plus jamais eu le problème.
    Attention de ne pas prévoir des trucs qui ne servent pas au simple principe que peut-être un jour ça sera utile :p
    (cf ci dessus : pas de code qui ne me sert pas même si c'est pour de la configuration)


    Alors c'est une très bonne initiative ^^
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  8. #8
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par eulbobo Voir le message

    J'ai souvent entendu cet argument du "c'est mieux"
    Je n'ai jamais entendu autre chose de constructif sur le POURQUOI ça serait "mieux" de "copier" les données d'un wrapper à un autre.
    Mais j'ai souvent vu des horreurs avec de la copie en boucle de données d'une grappe d'objets à une autre... Genre une grappe d'objets hibernates qui sont tous copiés un par un dans le DTO qui leur correspond -> duplication totale de code, risque de bug, complexité inutile, etc...
    A l'origine, le pattern DTO a pour philosophie de remonter un maximum d'information depuis un appel distant (forcément coûteux). L'utilisation qui en a été faite au cours du temps est légèrement différente : du mapping d'objet, sans aucune logique supplémentaire, comme c'est le cas dans cet article, un simple "passe plat" en somme. Dans les anciennes versions d'hibernate, cela était nécessaire, dans la mesure où les beans étaient enrichis par le framework artificiellement, pour gérer les états de persistance, et contenaient donc une quantité de données qu'il n'était pas souhaitable de transférer aux autres couches des applications, d'où l'utilisation du pattern DTO. Depuis cette époque, il semble que cette pratique a perduré, même lorsque ça n'a plus été nécessaire, peut être simplement par habitude ou par impression de bonne pratique (ça me rappelle une histoire avec des singes et une échelle).

  9. #9
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    J'avoue avoir commencé avec Hibernate 2... Mais déjà à l'époque, les gens qui balançaient ce pattern de tous les bouts ne savaient pas plus "pourquoi" ils le faisaient...
    On appelait ça le pattern VIEW à l'époque, parce qu'on mettait nos données dans des objets "vue" depuis nos beans hibernates... Objets View qui finissaient dans les objets Form de struts...

    Juste ils le faisaient... Et du coup, je le faisais aussi en rageant de toutes les erreurs que ça pouvait générer (c'était en 2006)


    12 ans plus tard, toujours aucune explication du mieux ^^


    Cargo Cult mon amour...
    Je ne suis pas mort, j'ai du travail !

  10. #10
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Citation Envoyé par eulbobo Voir le message
    - Y'a un repo git de dispo avec le code source pour pouvoir faire des propositions d'amélioration?
    Je vais voir comment mettre en place le repo git. As-tu déjà mis en place un repo git, si oui quelques indications ?

    Bertrand
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  11. #11
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    Citation Envoyé par parchemal Voir le message
    Je vais voir comment mettre en place le repo git. As-tu déjà mis en place un repo git, si oui quelques indications ?

    Bertrand
    Le plus simple :
    - Aller sur github : github.com
    - Se créer un compte
    - Créer un repository public (c'est gratuit tant que c'est public)
    - Suivre les instruction pour pousser du code sur le repo
    - Donner l'URL aux gens

    Hésite pas à demander si tu as besoin d'aide, mais franchement le plus dur est de comprendre la finesse et la puissance de git, pas de créer un repo ou de partager du code :p
    Je ne suis pas mort, j'ai du travail !

  12. #12
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Citation Envoyé par eulbobo Voir le message
    Hésite pas à demander si tu as besoin d'aide, mais franchement le plus dur est de comprendre la finesse et la puissance de git, pas de créer un repo ou de partager du code :p
    Merci pour les indications. C'est juste que je n'ai jamais eu à faire un dépôt sous git public. Sinon, j'utilise Git au quotidien.

    Si je le fais, je mettrais le lien à disposition.

    Merci!
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  13. #13
    Membre chevronné Avatar de jeffray03
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2008
    Messages
    1 501
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Allemagne

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 501
    Points : 2 120
    Points
    2 120
    Par défaut
    salut,
    j´ai essayé de creer un projet conformement au tutoriel,
    mais losrque j´appelle cet URI http://localhost:8080/user/users
    j´ai ceci comme erreur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Caused by: java.lang.IllegalStateException: getOutputStream() has already been called for this response
    Merci.

    Eric

  14. #14
    Futur Membre du Club
    Homme Profil pro
    Lille
    Inscrit en
    Avril 2019
    Messages
    4
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Lille

    Informations forums :
    Inscription : Avril 2019
    Messages : 4
    Points : 5
    Points
    5
    Par défaut demande d'aide suite à la lecture de votre tuto springboot
    Bonjour à tous, merci à @parchemal pour ce super tuto. Je suis tombé dessus en cherchant à faire un webservice rest et une application web cliente permettant de manipuler cette api.
    Je ne sais pas si c'est le bon lieu pour demander cela sinon je m'en excuse et déplacerais le post.
    N'ayant pas une grande expérience dans le domaine, j'espère trouver avec vous des indications pour arriver à mon but.
    Je dois réaliser un webservice qui permet de gérer des vidéos (ajout, modification, suppression d'une vidéo); une vidéo est caractérisé par un titre, un id qui est dans les faits l'identifiant youtube de cette vidéo et un ensemble de tags associé à cette vidéo. et un tag, c'est un id, et un nom.
    Autre chose: une vidéo peut donc être lié à plusieurs tags et inversement, un tag peut être lié à plusieurs vidéos.
    Voici donc mon approche pour le webservice : j'ai définis trois entité Video, Tag, et VideoTag qui est la table issue de l'association entre les deux entité Video et Tag


    -------------------------------------------------------------------------------package entity -------------------------------------------------------------------------------------
    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
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
     
    @Entity(name = "Video")
    @Table (name = "video")
    @NaturalIdCache
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public class Video implements Serializable{
    	private static final long serialVersionUID = 1L;
     
    	@Id
    	@GeneratedValue(
    	    strategy= GenerationType.AUTO,
    	    generator="increment"
    	)
    	@GenericGenerator(
    	    name = "increment",
    	    strategy = "increment"
    	)
    	@Column(name = "id", updatable = false, nullable = false)
    	private Long id;
     
    	@NaturalId
    	@NotBlank
    	@Column (name = "yid", unique = true)
    	private String youtubeId;
     
    	@NotBlank
    	private String title;
     
    	private String description;
     
    	private Long duration;
     
    	@OneToMany(
    			fetch = FetchType.EAGER, // LAZY me genere une erreur dans la fabrication du JSON
    			mappedBy = "video",
    			cascade = {
    					CascadeType.PERSIST,
    					CascadeType.MERGE
    			},
    			orphanRemoval = true
    			)
    	@JsonManagedReference
    	private Set<VideoTag> tags = new HashSet<>();
     
    	public Video() {}
     
    	public Video(@NotNull String youtubeId, @NotNull String title, String description, Long duration, Set<VideoTag> tags) {
    		super();
    		this.youtubeId = youtubeId;
    		this.title = title;
    		this.description = description;
    		this.duration = duration;
    		this.tags = tags;
    	}
     
    	public Long getId() {
    		return id;
    	}
     
    	public void setId(Long id) {
    		this.id = id;
    	}
     
    	public String getYoutubeId() {
    		return youtubeId;
    	}
     
    	public void setYoutubeId(String youtubeId) {
    		this.youtubeId = youtubeId;
    	}
     
    	public String getTitle() {
    		return title;
    	}
     
    	public void setTitle(String title) {
    		this.title = title;
    	}
     
    	public String getDescription() {
    		return description;
    	}
     
    	public void setDescription(String description) {
    		this.description = description;
    	}
     
    	public Long getDuration() {
    		return duration;
    	}
     
    	public void setDuration(Long duration) {
    		this.duration = duration;
    	}
     
    	public Set<VideoTag> getTags() {
    		return tags;
    	}
     
    	public void setTags(Set<VideoTag> tags) {
    		this.tags = tags;
    	}
     
    	public void addTag(Tag tag) {
    		VideoTag videoTag = new VideoTag(this, tag);
    		tags.add(videoTag);
    	}
     
    	public void removeTag(Tag tag) {
    		for (Iterator<VideoTag> iterator = tags.iterator();iterator.hasNext(); ) {
    			VideoTag videoTag = iterator.next();
     
    			if (videoTag.getVideo().equals(this) && videoTag.getTag().equals(tag)) {
    				iterator.remove();
    				videoTag.setVideo(null);
    				videoTag.setTag(null);
    			}
    		}
    	}
     
    	@Override
    	public boolean equals(Object o) {
    		if (this == o) return true;
     
    		if (o == null || getClass() != o.getClass())
    			return false;
     
    		Video video = (Video) o;
    		return Objects.equals(youtubeId, video.youtubeId);
    	}
     
    	@Override
    	public int hashCode() {
    		return Objects.hash(youtubeId);
    	}
    }
    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
     
    @Entity(name = "Tag")
    @Table(name = "tag")
    @NaturalIdCache
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public class Tag implements Serializable {
    	private static final long serialVersionUID = 1L;
     
    	@Id
    	@GeneratedValue(
    	    strategy= GenerationType.AUTO,
    	    generator="increment"
    	)
    	@GenericGenerator(
    	    name = "increment",
    	    strategy = "increment"
    	)
    	@Column(name = "id", updatable = false, nullable = false)
    	private Long id;
     
    	@NotBlank
    	@NaturalId
    	@Column( name = "name", unique = true, nullable = false)
    	private String name;
     
    	@OneToMany(
    			mappedBy = "tag", 
    			cascade = {
    					CascadeType.PERSIST,
    					CascadeType.MERGE
    			},
    			orphanRemoval = true)
    	@JsonIgnore
    	Set<VideoTag> videos = new HashSet<>();
     
    	public Tag() {}
     
    	public Tag(String name) {
    		this.name = name;
    	}
     
    	public Long getId() {
    		return id;
    	}
     
    	public void setId(Long id) {
    		this.id = id;
    	}
     
    	public String getName() {
    		return name;
    	}
     
    	public void setName(String name) {
    		this.name = name;
    	}
     
    	public Set<VideoTag> getVideos() {
    		return videos;
    	}
     
    	public void setVideos(Set<VideoTag> videos) {
    		this.videos = videos;
    	}
     
    	@Override
    	public boolean equals(Object o) {
    		if (this == o) return true;
    		if (o == null || getClass() != o.getClass()) return false;
    		Tag tag = (Tag) o;
    		return Objects.equals(name, tag.name);
    	}
     
    	@Override
    	public int hashCode() {
    		return Objects.hash(name);
    	}
    }
    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
    86
    87
    88
    89
    90
    91
    92
    93
    94
     
    @Entity(name = "VideoTag")
    @Table(name = "video_tag")
    public class VideoTag implements Serializable{
    	private static final long serialVersionUID = 1L;
    	private static final Boolean DEFAULT_VALUE_MAIN_TAG = false;
     
    	@EmbeddedId
    	@JsonIgnore
        private VideoTagId id;
     
    	@ManyToOne
    	@MapsId("videoId")
    	@JsonBackReference
        private Video video;
     
        @ManyToOne
        @MapsId("tagId")
        private Tag tag;
     
        @JsonIgnore
        @Column(name = "is_main_tag", nullable = false, columnDefinition = "BOOLEAN")
        private Boolean isMainTag;
     
        protected VideoTag() {}
     
        public VideoTag(Video video, Tag tag) {
        	this(video, tag, DEFAULT_VALUE_MAIN_TAG);
        }
     
        public VideoTag(Video video, Tag tag, Boolean isMainTag) {
            this.video = video;
            this.tag = tag;
            this.isMainTag = isMainTag;
     
            // creation de la clé primaire
            this.id = new VideoTagId(video.getId(), tag.getId());
     
            // mise à jour des relations pour assurer l'intégrité référentielle
            video.getTags().add(this);
    		tag.getVideos().add(this);
     
        }
     
        public VideoTagId getId() {
    		return id;
    	}
     
    	public void setId(VideoTagId id) {
    		this.id = id;
    	}
     
    	public Video getVideo() {
    		return video;
    	}
     
    	public void setVideo(Video video) {
    		this.video = video;
    	}
     
    	public Tag getTag() {
    		return tag;
    	}
     
    	public void setTag(Tag tag) {
    		this.tag = tag;
    	}
     
    	public Boolean getIsMainTag() {
    		return isMainTag;
    	}
     
    	public void setIsMainTag(Boolean isMainTag) {
    		this.isMainTag = isMainTag;
    	}
     
    	@Override
        public boolean equals(Object o) {
            if (this == o) return true;
     
            if (o == null || getClass() != o.getClass())
                return false;
     
            VideoTag that = (VideoTag) o;
            return Objects.equals(video, that.video) &&
                   Objects.equals(tag, that.tag);
        }
     
        @Override
        public int hashCode() {
            return Objects.hash(video, tag);
        }
     
    }
    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
     
    @Embeddable
    public class VideoTagId  implements Serializable {
    	private static final long serialVersionUID = 1L;
     
    	@Column(name = "vid")
    	private Long videoId;
     
    	@Column(name = "tid")
    	private Long tagId;
     
        protected VideoTagId() {}
     
        public VideoTagId(Long videoId, Long tagId) {
            this.videoId = videoId;
            this.tagId = tagId;
        }
     
        public Long getVideoId() {
    		return videoId;
    	}
     
    	public void setVideoId(Long videoId) {
    		this.videoId = videoId;
    	}
     
    	public Long getTagId() {
    		return tagId;
    	}
     
    	public void setTagId(Long tagId) {
    		this.tagId = tagId;
    	}
     
    	@Override
        public boolean equals(Object o) {
            if (this == o) return true;
     
            if (o == null || getClass() != o.getClass())
                return false;
     
            VideoTagId that = (VideoTagId) o;
            return Objects.equals(videoId, that.videoId) &&
                   Objects.equals(tagId, that.tagId);
        }
     
        @Override
        public int hashCode() {
            return Objects.hash(videoId, tagId);
        }
    }

    ------------------------------------------------------------------------package repository -----------------------------------------------------------------------------------------
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    @Repository
    public interface TagRepository extends JpaRepository<Tag, Long> {
    	Optional<Tag> findByName(String name);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    @Repository
    public interface VideoRepository extends JpaRepository<Video, Long>{
            @Query(value="SELECT v.* FROM video_tag vt JOIN tag t ON t.id = vt.tag_id JOIN video v ON v.id = vt.video_id WHERE t.name IN ('') UNION SELECT v.* FROM video_tag vt JOIN tag t ON t.id = vt.tag_id JOIN video v ON v.id = vt.video_id WHERE t.name IN (:mtags) OR t.name IN (:stags);", nativeQuery=true) // pouvoir recuperer les vidéos qui matchent les tags principaux se trouvant mtags et ceux qui matchent les tags sécondaires stags avec en premier, les videos matchant les tags contenus dans mtags
    	List<Video> findAllVideosByTags(@Param("mtags") List<String> mtags,@Param("stags")  List<String> stags);
    }

    -----------------------------------------------------------------------package controller --------------------------------------------------------------------------------
    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
     
    @RestController
    @RequestMapping(path = "/tags", produces = MediaType.APPLICATION_JSON_VALUE)
    public class TagController {
    	@Autowired ITagService tagService;
     
     
    	  @GetMapping
    	  ResponseEntity<Collection<Tag>> all() {
    	    return new ResponseEntity<>(tagService.findAll(), HttpStatus.OK);
    	  }
     
    	  @PostMapping
    	  ResponseEntity<Tag> addTag(@RequestBody Tag newTag) {
    	    return new ResponseEntity<>(tagService.save(newTag), HttpStatus.CREATED);
    	  }
     
     
    	  @GetMapping("/{id}")
    	  ResponseEntity<Tag> getOneTag(@PathVariable Long id) {
    	    return tagService.findById(id)
    	    		.map(tag -> {
    	    			return new ResponseEntity<>(tag, HttpStatus.OK);
    	    		})
    	    		.orElseThrow(() -> new ResourceNotFoundException("Tag", id));
    	  }
     
    	  @PutMapping("/{id}")
    	  ResponseEntity<Tag> replaceTag(@RequestBody Tag newTag, @PathVariable Long id) {
    		  return tagService.findById(id)
    				  .map(tag -> {
    					  tag.setName(newTag.getName());
    					  return new ResponseEntity<Tag>(tagService.save(tag), HttpStatus.OK);
    				  })
    				  .orElseGet(() -> {
    					  newTag.setId(id);
    					  return new ResponseEntity<Tag>(tagService.save(newTag), HttpStatus.CREATED);
    				  });
    	  }
     
    	  @DeleteMapping("/{id}")
    	  ResponseEntity<Void> deleteTag(@PathVariable Long id) {
    		  tagService.deleteById(id);
    		  return new ResponseEntity<Void>(HttpStatus.OK);
    	  }
    }
    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
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
     
    @RestController
    @RequestMapping(path = "/videos", produces = MediaType.APPLICATION_JSON_VALUE)
    public class VideoController {
    	@Autowired private IVideoService videoService;
     
    	@GetMapping
    	List<Video> all() {
    		return videoService.findAllVideos();
    	}
     
     
    	@GetMapping("/{id}")
    	Video one(@PathVariable Long id) {
    		Video video = videoService.findVideoById(id);
    		return video;
    	}
     
    	@GetMapping("/search")
    	ResponseEntity<Collection<Video>> findVideosByTagNames(@RequestParam("mt") List<String> mtags, @RequestParam("st") List<String> stags) {
    		if(mtags.isEmpty() ) {
    			mtags.add("");
    		}
    		if(stags.isEmpty()) {
    			stags.add("");
    		}
    		return new ResponseEntity<>(
    				videoService.findVideosByTags(mtags, stags).stream()
    				.distinct()
    				.collect(Collectors.toList()), HttpStatus.OK);
    	}
     
    	@GetMapping("{id}/tags")
    	ResponseEntity<Collection<Tag>> getTagsForIdVideo(@PathVariable Long id) {
    		 return new ResponseEntity<>(videoService.findTagsByIdVideo(id), HttpStatus.OK);
    	}
     
    	@PostMapping
    	Video newVideo(@RequestBody Video newVideo) {
    		return videoService.insertVideo(newVideo);
    	}
    //	@PostMapping
    //	ResponseEntity<Object> newVideo(@RequestBody Video newVideo) {
    //		Video savedVideo = videoService.insertVideo(newVideo);
    //		
    //		URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
    //				.buildAndExpand(savedVideo.getId()).toUri();
    //		return ResponseEntity.created(location).build();
    //	}
     
    	@PutMapping("/{id}/tags/aid")
    	Video addTagsByIdsToVideo(@PathVariable(name = "id") Long idVideo, @RequestParam (name = "ids") List<Long> tagIds) {
    		return videoService.addTagsByIdsToVideo(idVideo, tagIds);
    	}
     
    	@PutMapping("/{id}/tags/aname")
    	Video addTagsByNamesToVideo(@PathVariable(name = "id") Long idVideo, @RequestParam (name = "names") List<String> tagNames) {
    		return videoService.addTagsByNamesToVideo(idVideo, tagNames);
    	}
     
     
    	@PutMapping("/{id}/tags/did")
    	Video delTagsByIdsToVideo(@PathVariable(name = "id") Long idVideo, @RequestParam (name = "ids") List<Long> tagIds) {
    		return videoService.delTagsByIdsToVideo(idVideo, tagIds);
    	}
     
    	@PutMapping("/{id}/tags/dname")
    	Video delTagsByNamesToVideo(@PathVariable(name = "id") Long idVideo, @RequestParam (name = "names") List<String> tagNames) {
    		return videoService.delTagsByNamesToVideo(idVideo, tagNames);
    	}
     
    	@PutMapping("/{id}")
    	Video replaceVideo(@RequestBody Video newVideo, @PathVariable Long id) {
    		videoService.findVideoById(id);
    		return videoService.updateVideoById(newVideo, id);
    	}
    //	@PutMapping("/{id}")
    //	ResponseEntity<Object> replaceVideo(@RequestBody Video newVideo, @PathVariable Long id) {
    //		Optional<Video> video = videoService.findById(id);
    //		if(!video.isPresent()) {
    //			return ResponseEntity.notFound().build();
    //		}
    //		newVideo.setId(id);
    //		Video updatedVideo = videoService.save(newVideo);
    //		URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
    //				.buildAndExpand(updatedVideo.getId()).toUri();
    //		return ResponseEntity.created(location).build();
    //	}
     
    	@DeleteMapping("/{id}")
    	ResponseEntity<Object> deleteVideo(@PathVariable Long id) {
    		videoService.deleteVideoById(id);
    		return ResponseEntity.noContent().build();
    	}
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    @ControllerAdvice
    public class ErrorAdvice {
    	  @ResponseBody
    	  @ExceptionHandler(ResourceNotFoundException.class)
    	  @ResponseStatus(HttpStatus.NOT_FOUND)
    	  String videoNotFoundHandler(ResourceNotFoundException ex) {
    	    return ex.getMessage();
    	  }
    }


    ------------------------------------------------------------------------------package service-----------------------------------------------------------------------------------------------------
    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
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
     
    @Service
    public class VideoServiceImpl implements IVideoService {
    	@Autowired private VideoRepository videoRepository;
    	@Autowired private ITagService tagService;
     
    	@Override
    	public Video findVideoById(Long id) {
    		return videoRepository.findById(id)
    				.orElseThrow(() -> new ResourceNotFoundException("Video", id));
    	}
     
    	@Override
    	public List<Video> findAllVideos() {
    		return videoRepository.findAll();
    	}
     
    	@Override
    	public List<Video> findVideosByTags(List<String> mtags, List<String> stags) {
    		List<Video> videos = videoRepository.findAllVideosByTags(mtags, stags);
    		return videos;
    	}
     
    	@Override
    	public Video updateVideoById(Video newVideo, Long id) {
    		return videoRepository.findById(id)
    				.map(video -> {
    					video.setYoutubeId(newVideo.getYoutubeId());
    					video.setTitle(newVideo.getTitle());
    					video.setDescription(newVideo.getDescription());
    					video.setDuration(newVideo.getDuration());
    					return videoRepository.save(video);
    				})
    				.orElseGet(() -> {
    					newVideo.setId(id);
    					return videoRepository.save(newVideo);
    				});
    	}
     
    	@Override
    	public Video insertVideo(Video video) {
    		return videoRepository.save(video);
    	}
     
    	@Override
    	public void deleteVideoById(Long id) {
    		Video video = findVideoById(id);
    		videoRepository.delete(video);
    	}
     
    	@Transactional
    	@Override
    	public Video addTagsByIdsToVideo(Long idVideo, List<Long> idsTag) {
    		Video video = findVideoById(idVideo);
    		for(Long tagId : idsTag) {
    			Optional<Tag> tagOptional = tagService.findById(tagId);
    			if(tagOptional.isPresent()) {
    				video.addTag(tagOptional.get());
    			}
    		}
    		return save(video);
    	}
     
    	@Override
    	public Video delTagsByIdsToVideo(Long idVideo, List<Long> idsTag) {
    		Video video = findVideoById(idVideo);
    		for(Long tagId : idsTag) {
    			Optional<Tag> tagOptional = tagService.findById(tagId);
    			// si tag present on l'enleve de la video sinon on fait rien
    			if(tagOptional.isPresent()) {
    				video.removeTag(tagOptional.get());
    			}
    		}
    		return save(video);
    	}
     
    	@Override
    	public Video addTagsByNamesToVideo(Long idVideo, List<String> names) {
    		Video video = findVideoById(idVideo);
    		for(String tagName : names) {
    			Optional<Tag> tagOptional = tagService.findByName(tagName);
    			// si tag present on l'ajoute
    			Tag tag;
    			if(tagOptional.isPresent()) {
    				tag = tagOptional.get();
    			}else { // si non present on le cree et on l'ajoute
    				tag = tagService.save(new Tag(tagName));
    			}
    			video.addTag(tag);
    		}
    		return save(video);
    	}
     
    	@Override
    	public Video delTagsByNamesToVideo(Long idVideo, List<String> names) {
    		Video video = findVideoById(idVideo);
    		for(String tagName : names) {
    			Optional<Tag> tagOptional = tagService.findByName(tagName);
    			// si tag present on l'enleve de la video sinon on fait rien
    			if(tagOptional.isPresent()) {
    				video.removeTag(tagOptional.get());
    			}
    		}
    		return save(video);
    	}
     
    	@Override
    	public Video updateVideo(Video video) {
    		return videoRepository.save(video);
    	}
     
    	@Override
    	public Optional<Video> findById(Long id) {
    		return videoRepository.findById(id);
    	}
     
    	@Override
    	public Video save(Video newVideo) {
    		return videoRepository.save(newVideo);
    	}
     
    	@Override
    	public List<Tag> findTagsByIdVideo(Long idVideo) {
    		return findVideoById(idVideo).getTags().stream()
    				.map(VideoTag::getTag)
    				.collect(Collectors.toList());
    	}
     
    }
    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
     
    public interface IVideoService {
    	Video findVideoById(Long id);
    	List<Video> findAllVideos();
    	List<Video> findVideosByTags(List<String> mtags, List<String> stags);
    	Video updateVideo(Video video);
    	Video insertVideo(Video video);
    	Video updateVideoById(Video newVideo, Long id);
    	void deleteVideoById(Long id);
    	Video addTagsByIdsToVideo(Long idVideo, List<Long> idsTag);
    	Video delTagsByIdsToVideo(Long idVideo, List<Long> idsTag);
    	Video addTagsByNamesToVideo(Long idVideo, List<String> names);
    	Video delTagsByNamesToVideo(Long idVideo, List<String> names);
    	Optional<Video> findById(Long id);
    	Video save(Video newVideo);
    	List<Tag> findTagsByIdVideo(Long idVideo);
    }
    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
     
    public interface ITagService {
    	public Tag save(Tag tag);
    	public List<Tag> findAll();
    	public Optional<Tag> findById(Long id);
    	public List<Tag> saveAll(Iterable<Tag> tags);
    	public void deleteById(Long id);
    	public Tag getOne(Long id);
    	public void delete(Tag tag);
    	public void deleteAll(Iterable<Tag> tags);
    	public void deleteAll();
    	public boolean existsById(Long id);
    	public Long count();
    	public Optional<Tag> findByName(String name);
    }
    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
     
    @Service
    public class TagServiceImpl implements ITagService {
    	@Autowired private TagRepository tagRepository;
     
    	public Tag save(Tag tag) {
    		return tagRepository.save(tag);
    	}
     
    	public List<Tag> findAll() {
    		return tagRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
    	}
     
    	public Optional<Tag> findById(Long id) {
    		return tagRepository.findById(id);
    	}
     
    	public List<Tag> saveAll(Iterable<Tag> tags) {
    		return tagRepository.saveAll(tags);
    	}
     
    	public void deleteById(Long id) {
    		Tag tag = tagRepository.findById(id).get();
    		if(tag != null && !tag.getVideos().isEmpty()) {
    			tag.getVideos().clear();
    		}
    		tagRepository.delete(tag);
    	}
     
    	public Tag getOne(Long id) {
    		return tagRepository.getOne(id);
    	}
     
    	public void delete(Tag tag) {
    		tagRepository.delete(tag);
    	}
     
    	public void deleteAll(Iterable<Tag> tags) {
    		tagRepository.deleteAll(tags);
    	}
     
    	public void deleteAll() {
    		tagRepository.deleteAll();
    	}
     
    	public boolean existsById(Long id) {
    		return tagRepository.existsById(id);
    	}
     
    	public Long count() {
    		return tagRepository.count();
    	}
     
    	@Override
    	public Optional<Tag> findByName(String name) {
    		return tagRepository.findByName(name);
    	}
    }
    __________________________________________APP principal__________________________________________________________
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    @SpringBootApplication
    public class ProjetVideoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(ProjetVideoApplication.class, args);
    	}
    }
    Je souhaite obtenir une présentation comme celle-ci :
    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
     
    [
    	{
    		"id" : 1,
    		"youtubeId": "hgfdgdU_riE",
    		"title": "Video de test d'ajout",
    		"description": "Une video pour tester l'ajout avec POST",
    		"duration": 300,
    		"tags" : [
    			{
    				"id" : 1,
    				"name" : "tuto"
    			},
    			{
    				"id" : 2,
    				"name" : "rest"
    			}
    		]
    	},
    	{
    		"id" : 2,
    		"youtubeId": "hgfdgdU_riE",
    		"title": "Autre Video",
    		"description": "autre description",
    		"duration": 300,
    		"tags" : [
    			{
    				"id" : 1,
    				"name" : "tuto"
    			}
    		]
    	},
    	{
    		"id" : 3,
    		"youtubeId": "hgfdgdU_riE",
    		"title": "Autre Video",
    		"description": "autre description",
    		"duration": 300,
    		"tags" : []
    	}
    ]
    Ceci est la partie webservice
    j'ai pas encore commencé le client.
    Mais déjà j'aimerais pouvoir être sûr que le webservice fonctionne avec tous les besoins c'est-à-dire la recherche des vidéos par liste de tags principaux et secondaires.
    j'aimerais avoir votre avis et suggestions sachant que pour l'instant la suppression d'un tag en passant par le service tagservice entraine la suppression du lien dans VideoTag (jusque là ça va), mais aussi la vidéo correspondante. Pareil quand j'enleve un tag d'une vidéo je veux juste enlever le lien entre le tag et la vidéo mais ça ne fonctionne pas, le lien n'est pas supprimé dans videoTag.
    Merci pour vos retour.

  15. #15
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Citation Envoyé par uNouss Voir le message
    Pareil quand j'enleve un tag d'une vidéo je veux juste enlever le lien entre le tag et la vidéo mais ça ne fonctionne pas, le lien n'est pas supprimé dans videoTag
    Bonjour,
    Votre approche est bonne. Concernant votre question sur la suppression ou non des dépendances, il faut voir du côté de l'utilisation JPA Cascade Types (une petite recherche sur le web vous donne le résultat ci-dessous)

    The cascade types supported by the Java Persistence Architecture are as below:

    CascadeType.PERSIST : cascade type presist means that save() or persist() operations cascade to related entities.
    CascadeType.MERGE : cascade type merge means that related entities are merged when the owning entity is merged.
    CascadeType.REFRESH : cascade type refresh does the same thing for the refresh() operation.
    CascadeType.REMOVE : cascade type remove removes all related entities association with this setting when the owning entity is deleted.
    CascadeType.DETACH : cascade type detach detaches all related entities if a “manual detach” occurs.
    CascadeType.ALL : cascade type all is shorthand for all of the above cascade operations.

    Espérant que ça vous aide à avancer.
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  16. #16
    Futur Membre du Club
    Homme Profil pro
    Lille
    Inscrit en
    Avril 2019
    Messages
    4
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Lille

    Informations forums :
    Inscription : Avril 2019
    Messages : 4
    Points : 5
    Points
    5
    Par défaut
    Bonjour,
    Merci pour votre retour et je suis content de savoir que l'approche que j'ai eu est dans le bon sens.
    Citation Envoyé par parchemal Voir le message
    Concernant votre question sur la suppression ou non des dépendances, il faut voir du côté de l'utilisation JPA Cascade Types (une petite recherche sur le web vous donne le résultat ci-dessous)
    Je vais explorer cette piste et voir ce qu'il en revient mais j'ai aussi une erreur que j'avais pas remarqué. en faite il y a un retour dans Eclipse
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/api/v1].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/api/v1] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.leaderinfo.youtube.entity.Tag.videos, could not initialize proxy - no Session] with root cause
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.leaderinfo.youtube.entity.Tag.videos, could not initialize proxy - no Session
    quand j'essaie de faire un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    DELETE http://localhost:8080/api/v1/videos/{id}
    malgré le fait que le tag soit supprimé dans la base de donnée.

    Autre point j'aimerais à présent commencé la deuxième phase qui est de faire l'application web cliente qui via des formulaires et l'exploitation de l'api permettra de manipuler les vidéos.
    J'ai suivi votre tuto et j'ai un certain nombre de classe qui sont déprécié avec la version de spring que j'utilise et pareil je n'arrive pas à lancer en même temps le client et le serveur. J'imagine que pour ce dernier point je dois juste changer le port de tomcat pour avoir les deux lancé pour tester votre code par exemple.

  17. #17
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut
    Citation Envoyé par uNouss Voir le message
    J'imagine que pour ce dernier point je dois juste changer le port de tomcat pour avoir les deux lancé pour tester votre code par exemple.
    Bonjour,

    Il faut démarrer le serveur à part, et ensuite le client, car l'application est développée dans l'esprit de séparer complètement le back-end du front-end

    Courage
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  18. #18
    Futur Membre du Club
    Homme Profil pro
    Lille
    Inscrit en
    Avril 2019
    Messages
    4
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Lille

    Informations forums :
    Inscription : Avril 2019
    Messages : 4
    Points : 5
    Points
    5
    Par défaut
    Citation Envoyé par parchemal Voir le message
    Il faut démarrer le serveur à part, et ensuite le client, car l'application est développée dans l'esprit de séparer complètement le back-end du front-end
    Y a t'il un moyen de lancer les deux sur le même post ?

    En changeant le port j'arrive à lancer les deux mais j'obtiens cette erreur depuis le client.
    Nom : errorClient.png
Affichages : 594
Taille : 21,7 Ko

  19. #19
    Membre averti
    Avatar de parchemal
    Homme Profil pro
    Ingénieur Développeur Java
    Inscrit en
    Août 2009
    Messages
    144
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Ingénieur Développeur Java
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2009
    Messages : 144
    Points : 320
    Points
    320
    Par défaut Mise à jour du tutoriel
    Bonjour chers lecteurs,

    Une nouvelle version du tutoriel Apprendre à développer les services REST avec Spring Boot et Spring RestTemplate a été publiée.

    Principales nouveautés:

    • Prise en compte de la dernière version stable de spring boot-2.2.6.RELEASE.
    • Mise à jour des packages et méthodes du projet suite aux évolutions liées à spring boot.
    • Amélioration des tests unitaires et des tests d'intégration suite aux remarques des lecteurs.
    • Amélioration de la gestion des exceptions.
    • Retrait du code métier dans les contrôleurs pour les mettre dans la partie service.
    • Ajout du plugin cargo-maven2-plugin pour automatiser les tests d'intégration (démarrer et arrêter Tomcat9xx lors des tests d'intégration).
    • Ajout du profile pour déploiement automatique de l'application.
    • Changement de commandName par modelAttribute (nouvelle recommandation de Spring pour la classe FormTag) dans les formulaires.
    • Etc.


    Merci à tous ceux qui ont encore des suggestions d'amélioration.
    Nguimgo Bertrand
    Ingénieur Etudes et Développement JAVA/JEE

    - Guide d'implémentation des services REST avec Spring Boot et Spring RestTemplate
    - Je vous propose de lire le guide d'implémentation du design pattern MVP-GWT
    - N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  20. #20
    Futur Membre du Club Avatar de jeromevill
    Homme Profil pro
    Développeur JEE - VueJS
    Inscrit en
    Juin 2018
    Messages
    6
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur JEE - VueJS
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2018
    Messages : 6
    Points : 8
    Points
    8
    Par défaut La partie I-F-1. Automatisation des tests unitaires -> Ne fonctionne pas - RESOLU
    RESOLU

    Note importante : au cours du projet et de la réalisation du cours, les dépendances dans le pom.xml peuvent varier (surefire, failsafe..) , pour éviter l'erreur ci-dessous, il faut alors reprendre l'ensemble des dépendances du fichier pom.xml et vérifier qu'elle sont conformes à l'exemple donné à la fin du cours, partie
    I-H. Paramétrage complet

    Une fois le fichier conforme à l'exemple, l'ensembles des tests automatisés pour la partie TU fonctionnent.



    Bonjour, j'ai essayé de suivre l'ensemble des TU, ils fonctionnent, les TI fonctionnements mais il y a des erreurs dans le code que je vous ferais remonter. Pour la partie
    I-F-1. Automatisation des tests unitaires

    le lancement de la commande mvn clean install dans le repertoire du pom.xml donne :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    15:56:26.458 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
    15:56:26.470 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
    15:56:26.502 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
    15:56:26.515 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests], using SpringBootContextLoader
    15:56:26.520 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests]: class path resource [fr/jerome/springbootrestserverapi/SpringbootRestserverapiApplicationTests-context.xml] does not exist
    15:56:26.521 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests]: class path resource [fr/jerome/springbootrestserverapi/SpringbootRestserverapiApplicationTestsContext.groovy] does not exist
    15:56:26.521 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests]: no resource found for suffixes {-context.xml, Context.groovy}.
    15:56:26.523 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests]: SpringbootRestserverapiApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
    15:56:26.570 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests]
    15:56:26.649 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [/home/jerome/IdeaProjects/springboot-restserverapi/target/classes/fr/jerome/springbootrestserverapi/SpringbootRestserverapiApplication.class]
    15:56:26.650 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplication for test class fr.jerome.springbootrestserverapi.SpringbootRestserverapiApplicationTests
    15:56:26.750 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@2fba3fc4]
    Impossible de détecter les classes de Tests. Les TU se lancement manuellement, mais pas à la commande mvn clean install avec le plugin maven surefire.

    Seule la classe SpringbootRestserverapiApplicationTests est detectée.

    Nom : Capture du 2020-03-31 16-00-27.png
Affichages : 854
Taille : 26,5 Ko

Discussions similaires

  1. [Web Services] Tutoriel sur le développement des services REST avec Spring 3
    Par regis1512 dans le forum Spring
    Réponses: 0
    Dernier message: 11/02/2015, 12h34
  2. Premier développement de services web avec Spring-WS
    Par Arnaud_03 dans le forum Services Web
    Réponses: 5
    Dernier message: 02/12/2008, 16h06

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