0

Context

I'm trying to create an API with SpringBoot, Hateoas, store data into H2 database and manage it with JPA.

I have 2 entities : Mission and User.

Several users are assigned to a mission. And users can be assigned to different missions.

I use @ManyToMany annotation to create an associative table.

Objectives

My goal is to create some requests as :

  • (GET request) IP/missions/123456/users : Get all users assigned to mission 123456
  • (PUT request) IP/missions/456789 : Assigned users to mission 456789

Code

Entities

@Entity
public class Mission {

    @Id
    private String missionid;
    private String missionname;
    private Date start;
    private Date end;
    @ManyToMany(mappedBy = "missions")
    @JoinTable(name = "missions_users",
        joinColumns = @JoinColumn(name = "missionid"),
        inverseJoinColumns = @JoinColumn(name = "userid")
    )
    private Set<User> users = new HashSet<>();
    private int status;

    public Mission() {
    }

    public Mission(String name, Date start, Date end) {
        this.namemission = name;
        this.start = start;
        this.end = end;
        this.status = 0;
    }


}

@Entity
public class User {

    @Id
    private String userid;
    private String nameuser;
    @ManyToMany
    private Set<Mission> missions = new HashSet<>();

    public User() {
    }

    public User(String name) {
        this.nameuser = name;
    }
}

Resources

@RepositoryRestResource(collectionResourceRel = "mission")
public interface MissionResource extends JpaRepository<Mission, String> {
}

@RepositoryRestResource(collectionResourceRel = "user")
public interface UserResource extends JpaRepository<User, String> {
}

RestController

@RestController
@RequestMapping(value = "/missions", produces = MediaType.APPLICATION_JSON_VALUE)
@ExposesResourceFor(Mission.class)
public class MissionRepresentation {

    private final MissionResource missionResource;
    private final UserResource userResource;

    public MissionRepresentation(MissionResource missionResource, UserResource userResource) {
        this.missionResource = missionResource;
        this.userResource = userResource;
    }

    // mapping
}

SQL script to fill database

INSERT INTO mission (missionid, namemission, start, end, status) VALUES ('de7d9052-4961-4b4f-938a-3cd12cbe1f82', 'mission 1', '2019-02-11', '2019-02-13', 0)
INSERT INTO mission (missionid, namemission, start, end, status) VALUES ('425e7701-02c6-4de3-9333-a2459eece1c8', 'mission 2', '2019-02-10', '2019-02-15', 0)

INSERT INTO user (userid, nameuser) VALUES ('0dee5423-6bd9-4014-a690-0993b3cb5f3b', 'user 1')
INSERT INTO user (userid, nameuser) VALUES ('8e70ca41-8766-4188-bf37-3d9a0aae941d', 'user 2')
INSERT INTO user (userid, nameuser) VALUES ('75cb846a-3a7b-4116-8d8a-7778916dff8c', 'user 3')

INSERT INTO missions_users(missionid, userid) VALUES ('de7d9052-4961-4b4f-938a-3cd12cbe1f82', '0dee5423-6bd9-4014-a690-0993b3cb5f3b')
INSERT INTO missions_users(missionid, userid) VALUES ('de7d9052-4961-4b4f-938a-3cd12cbe1f82', '8e70ca41-8766-4188-bf37-3d9a0aae941d')
INSERT INTO missions_users(missionid, userid) VALUES ('425e7701-02c6-4de3-9333-a2459eece1c8', '8e70ca41-8766-4188-bf37-3d9a0aae941d')
INSERT INTO missions_users(missionid, userid) VALUES ('425e7701-02c6-4de3-9333-a2459eece1c8', '75cb846a-3a7b-4116-8d8a-7778916dff8c')

Results

Currently I try to do GET requests, but in the future I will have to do POST/PUT/... requests.

  • Result

Request : http://localhost:8082/mission/de7d9052-4961-4b4f-938a-3cd12cbe1f82

{
    "missionid": "de7d9052-4961-4b4f-938a-3cd12cbe1f82",
    "namemission": "mission 1",
    "users": [
        {},
        {}
    ],
    "status": 0,
    "start": "2019-02-10T23:00:00.000+0000",
    "end": "2019-02-12T23:00:00.000+0000"
}
  • Result excepted

Request : http://localhost:8082/mission/de7d9052-4961-4b4f-938a-3cd12cbe1f82

{
    "missionid": "de7d9052-4961-4b4f-938a-3cd12cbe1f82",
    "namemission": "mission 1",
    "users": [
        "user 1",
        "user 2"
    ],
    "status": 0,
    "start": "2019-02-10T23:00:00.000+0000",
    "end": "2019-02-12T23:00:00.000+0000"
}

Request : http://localhost:8082/mission/de7d9052-4961-4b4f-938a-3cd12cbe1f82/users

{
    "user 1",
    "user 2"
}

I don't know how to write my method to get only users names...

@GetMapping(value = "/{missionId}/users")
public ResponseEntity<?> getUsersByMission(@PathVariable("missionId") String missionId) {
    Mission mission = this.missionResource.findById(missionId).get();
    return new ResponseEntity<>(????, HttpStatus.OK);
}

A solution is to use a list of String instead of a list of User?

Conclusion

Someone can help me to have expected results please? Thanks.

EDIT 1: Result for @LppEdd answer

[
    {
        "userid": "0dee5423-6bd9-4014-a690-0993b3cb5f3b",
        "nameuser": "user 1",
        "missions": []
    },
    {
        "userid": "8e70ca41-8766-4188-bf37-3d9a0aae941d",
        "nameuser": "user 2",
        "missions": []
    }
]
Royce
  • 1,557
  • 5
  • 19
  • 44

1 Answers1

1

It should pretty easy actually. You were almost there.
JPA will handle the User objects creation for you. Just expose your Set<User> to the outer world.

public class Mission {
   ...

   public Set<User> getUsers() {
      return users;
   }

   public void setUsers(final Set<User> users) {
      this.users = users;
   }

   ...
}

Inside your @RestController, you can then get a hold on, and return the Set.

public ResponseEntity<List<String>> getUsersByMission(@PathVariable("missionId") String missionId) {
    final Optional<Mission> mission = this.missionResource.findById(missionId);

    if (mission.isEmpty()) {
       // Handle the "mission not found" case
    }

    final List<String> names = 
                  mission.get().getUsers()
                               .stream()
                               .map(user -> user.getNameUser())
                               .collect(Collectors.toList());

    return ResponseEntity.ok().body(names);
}

To handle bidirectional @ManyToMany associations, you need to specify the mappedBy attribute, which will point to the Mission#users field.

@ManyToMany(mappedBy = "users")
private Set<Mission> missions = new HashSet<>();
LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • I agree with you, but I will not have only name, I will have the full object (id and name) right? – Royce Feb 16 '19 at 10:34
  • @N.Lamblin yes, you'll get the entire User object. Do you need only the name? – LppEdd Feb 16 '19 at 10:34
  • I want to understand how works these tools so yes I try a lot of things. For example, get only names. Or define a chief of the mission and store it as String into Mission class : `private String chiefname;`. I hope I'm clear. – Royce Feb 16 '19 at 10:40
  • @N.Lamblin It's a broad topic. You can review it by reading the documentation at https://docs.spring.io/spring-data/jpa/docs/current/reference/html/ In the meantime, you can map the User object to its name by using a Stream. – LppEdd Feb 16 '19 at 10:42
  • Ok thanks a lot :). I updated my post with the result of your answer. Can you just tell me why `missions` is empty? Thanks. – Royce Feb 16 '19 at 10:44
  • @N.Lamblin I see the User object has a Set of "Tache" and not a Set of "Mission". That might be the cause. – LppEdd Feb 16 '19 at 10:46
  • Stupid mistake but it's not the reason. Thanks anyway. – Royce Feb 16 '19 at 10:48