Bonjour à tous,

Tout est résumé dans le titre.
Je fabrique une API pour un forum de rencontres entre musiciens.

Mon projet Spring REST est basé sur celui ci:
https://spring.io/guides/tutorials/rest/

Au détail près que j'ai une base de données Postgres, et que tout est sur Docker (API + BDD) avec Docker Compose.

VOICI LA STRUCTURE DU PROJET;

Nom : Screenshot from 2022-01-17 18-30-14.png
Affichages : 560
Taille : 138,6 Ko



VOICI LE CODE POUR UNE ENTITE:

ENTITE:
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
 
package com.praline40.MeetZicker.Style;
 
import com.praline40.MeetZicker.Group.Group;
import com.praline40.MeetZicker.Musician.Musician;
 
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
 
@Entity
public class Style {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    private String name;
 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
 
    @ManyToMany(mappedBy = "styles")
    private List<Musician> musicians = new ArrayList<>();
 
    @ManyToMany(mappedBy = "styles")
    private List<Group> groups = new ArrayList<>();
 
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
 
    public List<Musician> getMusicians() {
        return musicians;
    }
    public void setMusicians(List<Musician> musicians) {
        this.musicians = musicians;
    }
 
    public List<Group> getGroups() {
        return groups;
    }
    public void setGroups(List<Group> groups) {
        this.groups = groups;
    }
}
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
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
 
package com.praline40.MeetZicker.Style;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.mediatype.problem.Problem;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
import java.util.stream.Collectors;
 
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
 
@RestController //Combination of @Controller and @ResponseBody. Beans returned are converted to/from JSON/XML.
public class StyleController {
    //@Autowired // Autowire the StyleRepository so that we can retrieve and save data to database.
    private final StyleRepository repository;
    private final StyleModelAssembler assembler; // Injecting StyleModelAssembler into the controller
 
    StyleController(StyleRepository repository, StyleModelAssembler assembler) {
        this.repository = repository;
        this.assembler = assembler;
    }
 
    @GetMapping("/styles/{id}")
    EntityModel<Style> one(@PathVariable Long id) {
 
        Style style = repository.findById(id) //
                .orElseThrow(() -> new StyleNotFoundException(id));
 
        return assembler.toModel(style);
    }
 
    @GetMapping("/allstyles")
    public String allStyles(){
        return "You wanna know all styles !!";
    }
 
    @GetMapping("/styles")
    CollectionModel<EntityModel<Style>> all() {
 
        List<EntityModel<Style>> styles = repository.findAll().stream()
                .map(assembler::toModel)
                .collect(Collectors.toList());
 
        return CollectionModel.of(styles,
                linkTo(methodOn(StyleController.class).all()).withSelfRel());
    }
 
    @PostMapping("/styles")
    ResponseEntity<?> newStyle(@RequestBody Style newStyle) {
 
        EntityModel<Style> entityModel = assembler.toModel(repository.save(newStyle));
 
        return ResponseEntity //
                .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) //
                .body(entityModel);
    }
 
    @PutMapping("/styles/{id}")
    ResponseEntity<?> replaceStyle(@RequestBody Style newStyle, @PathVariable Long id) {
 
        Style updatedStyle = repository.findById(id) //
                .map(style -> {
                    style.setName(newStyle.getName());
                    style.setMusicians(newStyle.getMusicians());
                    style.setGroups(newStyle.getGroups());
                    return repository.save(style);
                }) // s'il n'existe pas déjà on le créé
                .orElseGet(() -> {
                    newStyle.setId(id);
                    return repository.save(newStyle);
                });
 
        EntityModel<Style> entityModel = assembler.toModel(updatedStyle);
 
        return ResponseEntity //
                .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) //
                .body(entityModel);
    }
 
    @DeleteMapping("/styles/{id}")
    ResponseEntity<?> deleteStyle(@PathVariable Long id) {
 
        repository.deleteById(id);
 
        return ResponseEntity.noContent().build();
    }
}

ASSEMBLER:
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
 
package com.praline40.MeetZicker.Style;
 
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.RepresentationModelAssembler;
import org.springframework.stereotype.Component;
 
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
 
@Component
public class StyleModelAssembler implements RepresentationModelAssembler<Style, EntityModel<Style>> {
 
    @Override
    public EntityModel<Style> toModel(Style style) {
 
        return EntityModel.of(style, //
                linkTo(methodOn(StyleController.class).one(style.getId())).withSelfRel(),
                linkTo(methodOn(StyleController.class).all()).withRel("styles"));
    }
}
NOT FOUND EXCEPTIONS:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
 
package com.praline40.MeetZicker.Style;
 
class StyleNotFoundException extends RuntimeException {
 
    StyleNotFoundException(Long id) {
        super("Could not find style " + id);
    }
}
REPOSTITORY:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
 
package com.praline40.MeetZicker.Style;
 
import org.springframework.data.jpa.repository.JpaRepository;
 
interface StyleRepository extends JpaRepository<Style, Long> {
}
DOCKERFILE:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
 
FROM openjdk:11
ADD target/meetzicker.jar meetzicker.jar
ENTRYPOINT ["java", "-jar","meetzicker.jar"]
EXPOSE 8080
DOCKER-COMPOSE:
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
 
version: '2.1'
services:
  API:
    image: 'meetzicker.jar:latest'
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/${POSTGRES_DB}
      - SPRING_DATASOURCE_USERNAME=${POSTGRES_USER}
      - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
      - SPRING_JPA_HIBERNATE_DDL_AUTO=update
 
  db:
    image: postgres:${POSTGRES_VERSION}
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_DB=${POSTGRES_DB}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5



DANS POSTMAN:

D'abord un post qui fonctionne:
Nom : Screenshot from 2022-01-17 18-12-13.png
Affichages : 547
Taille : 46,2 Ko

Ensuite un get qui crashe:
GET http://localhost:8080/styles

Nom : Screenshot from 2022-01-17 18-13-49.png
Affichages : 537
Taille : 34,8 Ko


NB: tant que je ne fais pas de POST, le GET fonctionne (du moins pour les entités indépendantes)!

Nom : Screenshot from 2022-01-17 18-14-53.png
Affichages : 544
Taille : 31,3 Ko


Un post pour un nouvel instrument:

Nom : Screenshot from 2022-01-17 18-28-08.png
Affichages : 539
Taille : 54,7 Ko


Ensuite un get, pareil ça plante:
Nom : Screenshot from 2022-01-17 18-29-18.png
Affichages : 533
Taille : 39,4 Ko


POUR CE CONTROLLER SIMPLIFIE PAS DE SOUCIS:
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
 
package com.praline40.MeetZicker;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
import java.util.List;
import java.util.stream.Collectors;
 
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
 
@RestController //Combination of @Controller and @ResponseBody. Beans returned are converted to/from JSON/XML.
public class HomeController {
 
    @GetMapping("/")
    public String home(){
        return "Welcome to Meetzicker !!";
    }
}
Nom : Screenshot from 2022-01-17 18-33-32.png
Affichages : 531
Taille : 27,2 Ko


J'y connais pas grand chose, mais comme ça je dirais que cela vient d'un soucis entre mes relations entre entitées (relations base de données). Car pourquoi cela marcherait-il tant que la base de données est vide (avant POST) ?
J'avoue j'y suis allé de façon un peu bourrin j'ai fait directement une architecture relationnelle poussée.