n00bst3r
n00bst3r

Reputation: 647

Updating an OneToMany Relationship with Spring REST

My problem is that i've got a bidirectional oneToMany Relationship between an entity Task and an entity User(Author). This is realized in a spring boot project using spring rest and spring data jpa. If I create a user by a POST-Request it works fine. The same if I create a Task with no Author(User) instance. I can create it without trouble. After that I try to update the author of the task with an user-instance by executing PUT. The answer of the webservice ist 200 Okay. I've got also an entry in the database with the user id in the author column of the task-table. But when i execute a GET-Request for getting all user or tasks, nothing works anymore. Postman displays only stuff like Bad Array. And I've no idea why. If i delete the task in the database, everything works fine again. Has anyone an idea why? Here the classes:

@Entity
@Table(name="User")
public class User {
@Id
@GeneratedValue
@Column(name="USER_ID")
private Long id;

@Column(name="CONFIRMATION")
private boolean confirmed;

@Column(name = "EMAIL",nullable = false)
private String email;

@Column(name = "USERNAME",nullable = false)
@NotNull
private String userName;

@Column(name="PASSWORD",nullable = false)
@NotNull
private String password;

@Column(name="TIMESTAMP")
private Long creationTime;

@OneToMany(cascade=CascadeType.ALL, mappedBy="author",fetch=FetchType.EAGER)
private Set<Task>assignedTasks;

@OneToMany(cascade=CascadeType.ALL, mappedBy="assignee",fetch=FetchType.EAGER) 
private Set<Task>createdTasks;


public User() {
}

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}



public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public String getUserName() {
    return userName;
}

public void setUserName(String userName) {
    this.userName = userName;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public Long getCreationTime() {
    return creationTime;
}

public void setCreationTime(Long creationTime) {
    this.creationTime = creationTime;
}

public boolean isConfirmed() {
    return confirmed;
}

public void setConfirmed(boolean confirmed) {
    this.confirmed = confirmed;
}

public Set<Task> getAssignedTasks() {
    return assignedTasks;
}

public void setAssignedTasks(Set<Task> assignedTasks) {
    this.assignedTasks = assignedTasks;
}

public void addAssignedTasks(Task task){
    addAssignedTasks(task,true);
}

void addAssignedTasks(Task task, boolean set){
    if(task != null){
        getAssignedTasks().add(task);
        if(set){
            task.setAssignee(this);
        }
    }
}

public Set<Task> getCreatedTasks() {
    return createdTasks;
}

public void setCreatedTasks(Set<Task> createdTasks) {
    this.createdTasks = createdTasks;
}

public void addCreatedTask(Task task){
    addCreatedTask(task,true);
}

void addCreatedTask(Task task, boolean set){
    if(task != null){
        getCreatedTasks().add(task);
        if(set){
            task.setAuthor(this);
        }
    }
}

public void removeCreatedTask(Task task) {
    getCreatedTasks().remove(task);
    task.setAuthor(null);
}  

}

Then the Task Entity:

@Entity
@Table(name="Task")
public class Task {


@Id
@GeneratedValue
@Column(name="TASK_ID")
private Long id;

@Transient
private List<String> possibleTaskTypes = TaskType.getTaskTypesAsString();

@Transient 
private List<String>possibleTaskContainer = TaskContainer.getTaskContainerAsString();

@Column(name="TASK_Container")
private String taskContainer;

@Column(name="TASK_TYPE")
private String taskType;

@Column(name="HEAD_LINE")
private String headLine;

@Column(name="TASK_TEXT")
private String taskText;

@Transient
private List<String> possibleworkFlowStati = WorkFlowStatus.getWorkFlowsStatiAsString();

@Column(name="WORKFLOW_STATUS")
private String status;

@ManyToOne(cascade=CascadeType.ALL)
private User author;

@ManyToOne(cascade=CascadeType.ALL)
private User assignee;

@Column(name="COMMENTS")
@ElementCollection(targetClass=String.class)
private List<String>comments;

@ManyToOne(cascade=CascadeType.ALL)
private Sprint sprint;

@Column(name="TIMESTAMP")
private Long creationTime;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getHeadLine() {
    return headLine;
}

public void setHeadLine(String headLine) {
    this.headLine = headLine;
}

public String getTaskText() {
    return taskText;
}

public void setTaskText(String taskText) {
    this.taskText = taskText;
}

public User getAuthor() {
    return author;
}

public void setAuthor(User author) {
    setAuthor(author, true);
}

void setAuthor(User author, boolean add){
    this.author = author;
    if(author !=null&&add){
        author.addCreatedTask(this, false);
    }
}

public User getAssignee() {
    return assignee;
}

public void setAssignee(User assignee) {
    setAssignee(assignee,true);
}

void setAssignee(User assignee, boolean add){
    this.assignee = assignee;
    if(assignee != null && add){
        assignee.addAssignedTasks(this,false);
    }
}

public List<String> getComments() {
    return comments;
}

public void setComments(List<String> comments) {
    this.comments = comments;
}

public Sprint getSprint() {
    return sprint;
}

public void setSprint(Sprint sprint) {
    this.sprint = sprint;
}

public Long getCreationTime() {
    return creationTime;
}

public void setCreationTime(Long creationTime) {
    this.creationTime = creationTime;
}

public String getTaskType() {
    return taskType;
}

public void setTaskType(String taskType) {
    this.taskType = taskType;
}

public List<String> getPossibleTaskTypes() {
    return possibleTaskTypes;
}

public void setPossibleTaskTypes(List<String> possibleTaskTypes) {
    this.possibleTaskTypes = possibleTaskTypes;
}

public List<String> getPossibleworkFlowStati() {
    return possibleworkFlowStati;
}

public void setPossibleworkFlowStati(List<String> possibleworkFlowStati) {
    this.possibleworkFlowStati = possibleworkFlowStati;
}

public String getStatus() {
    return status;
}

public void setStatus(String status) {
    this.status = status;
}

public List<String> getPossibleTaskContainer() {
    return possibleTaskContainer;
}

public void setPossibleTaskContainer(List<String> possibleTaskContainer) {
    this.possibleTaskContainer = possibleTaskContainer;
}

public String getTaskContainer() {
    return taskContainer;
}

public void setTaskContainer(String taskContainer) {
    this.taskContainer = taskContainer;
}




}

Now the Task-Controller:

@RestController
@RequestMapping("/v1/")
@Api(value = "tasks", description = "V1 - Tasks API")
public class TaskController {

private final Logger logger = LoggerFactory.getLogger(TaskController.class);
private TaskRepository taskRepository;
private UserRepository userRepository;

@Autowired
public TaskController(TaskRepository taskRepository, UserRepository userRepository){
    this.taskRepository = taskRepository;
    this.userRepository = userRepository;

}

protected void verifyTaskById(Long id) throws ResourceNotFoundException{
    Task task = taskRepository.findOne(id);
    if(task == null){
        throw new ResourceNotFoundException("Task with ID:"+id+" not found."); 
    }
}
/**
 * <b>POST</b> v1/tasks/
 * @param task
 * @return
 */
@RequestMapping(value="tasks", method=RequestMethod.POST)
@ApiOperation(value = "Creates a new Task", notes="The newly created Task Id will be sent in the location response header", 
response = Void.class)
@ApiResponses(value = {@ApiResponse(code=201, message="Task Created Successfully", response=Void.class), @ApiResponse(code=500, message="Error creating Task", response=ErrorDetail.class) } )
public ResponseEntity<?> createTask(@Valid @RequestBody Task task){
    logger.info(task.toString());
    task.setCreationTime(new Date().getTime());

    task = taskRepository.save(task);
    HttpHeaders responseHeaders = new HttpHeaders();

    URI newTaskUri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(task.getId()).toUri();
    responseHeaders.setLocation(newTaskUri);

    return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}

/**
 * <b>GET</b> v1/tasks/
 * @return
 */
@RequestMapping(value = "tasks", method = RequestMethod.GET )
@ApiOperation(value = "Retrieves all the tasks", response=Task.class, responseContainer="List")
public ResponseEntity<Page<Task>> listAllTasks(Pageable pageable){
    logger.info("Receiving get-Tasks Request..");
    Page<Task> allTasks = taskRepository.findAll(pageable);
    return new ResponseEntity<>(allTasks,HttpStatus.OK);
}

/**
 * <b>GET</b> v1/tasks/{id}
 * @param id
 * @return
 */
@RequestMapping(value = "tasks/{id}", method = RequestMethod.GET )
@ApiOperation(value = "Retrieves given Task", response=Task.class)
@ApiResponses(value = {@ApiResponse(code=200, message="", response=Task.class),  @ApiResponse(code=404, message="Unable to find Task", response=ErrorDetail.class) } )
public ResponseEntity<?> getSingleTask(@PathVariable(value="id") Long id){
    logger.info("Receiving get Task-Request for ID: "+ id);
    verifyTaskById(id);
    Task task = taskRepository.findOne(id);
    return new ResponseEntity<>(task,HttpStatus.OK);
}

/**
 * <b>PUT</b> v1/tasks/{id}
 * @param task
 * @param id
 * @return
 */
@RequestMapping(value = "tasks/{id}", method = RequestMethod.PUT)
@ApiOperation(value = "Updates given Task", response=Void.class)
@ApiResponses(value = {@ApiResponse(code=200, message="", response=Void.class),  
        @ApiResponse(code=404, message="Unable to find Task", response=ErrorDetail.class) } )
public ResponseEntity<?> updateTask(@RequestBody Task task, @PathVariable(value="id") Long id) {
    logger.info("Receiving put Task Request for "+id);
    verifyTaskById(id);
    if(!isIdInBodyCorrect(task, id)){
        task.setId(id);
    }

    if(task.getAuthor() != null)userRepository.save(task.getAuthor());
    if(task.getAssignee()!=null)userRepository.save(task.getAssignee());
    Task t = taskRepository.save(task);

    return new ResponseEntity<>(HttpStatus.OK);
}

/**
 * <b>DELETE</b> v1/tasks/{id}
 * 
 * @param id
 * @return
 */
@RequestMapping(value = "tasks/{id}", method = RequestMethod.DELETE)
@ApiOperation(value = "Deletes given Task", response=Void.class)
@ApiResponses(value = {@ApiResponse(code=200, message="", response=Void.class),  
        @ApiResponse(code=404, message="Unable to find Task", response=ErrorDetail.class) } )
public ResponseEntity<?> deleteTask(@PathVariable (value="id") Long id) {
    verifyTaskById(id);
    taskRepository.delete(id);
    return new ResponseEntity<>(HttpStatus.OK);
}

protected boolean isIdInBodyCorrect(Task task, Long id){
    if(task.getId() != id){
        logger.warn("Id in Body wasn't defined or wrong. ");  //TODO Exception werfen. 
        return false;
    }else{
        return true;
    }
}

}

And last but not least the User Controller:

@RestController
@RequestMapping("/v1/")
@Api(value = "users", description = "V1 - Users API")
public class UserController {

private final Logger logger = LoggerFactory.getLogger(UserController.class);

private UserRepository userRepository;

@Autowired
public UserController(UserRepository userRepository ){
    this.userRepository = userRepository;
}
/**
 * Checks wether the user exists. If not it throws a {@link ResourceNotFoundException}.
 * @param id
 * @throws ResourceNotFoundException
 */
protected void verifyUserById(Long id) throws ResourceNotFoundException{
    User user = userRepository.findOne(id);
    if(user==null){
        throw new ResourceNotFoundException("User with ID:"+id+" not found.");
    }
}
/**
 * <b>POST</b> v1/users/
 * @param user
 * @return
 */
@RequestMapping(value="users", method=RequestMethod.POST)
@ApiOperation(value = "Creates a new User", notes="The newly created user Id will be sent in the location response header", 
response = Void.class)
@ApiResponses(value = {@ApiResponse(code=201, message="User Created Successfully", response=Void.class), @ApiResponse(code=500, message="Error creating User", response=ErrorDetail.class) } )
public ResponseEntity<?> createUser(@Valid @RequestBody User user) {
    user.setCreationTime(new Date().getTime());
    user = userRepository.save(user);

    // Set the location header for the newly created resource
    HttpHeaders responseHeaders = new HttpHeaders();
    URI newUserUri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(user.getId()).toUri();
    responseHeaders.setLocation(newUserUri);

    return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}
/**
 * <b>GET</b> v1/users/
 * @return
 */
@RequestMapping(value = "users", method = RequestMethod.GET )
@ApiOperation(value = "Retrieves all the users", response=User.class, responseContainer="List")
public ResponseEntity<Page<User>> listAllUsers(Pageable pageable){
    Page<User> allUsers = userRepository.findAll(pageable);
    return new ResponseEntity<>(allUsers,HttpStatus.OK);
}

/**
 * <b>GET</b> v1/users/{id}
 * @param id
 * @return
 */
@RequestMapping(value = "users/{id}", method = RequestMethod.GET )
@ApiOperation(value = "Retrieves given User", response=User.class)
@ApiResponses(value = {@ApiResponse(code=200, message="", response=User.class),  @ApiResponse(code=404, message="Unable to find User", response=ErrorDetail.class) } )
public ResponseEntity<?> getSingleUser(@PathVariable(value="id") Long id){
    verifyUserById(id);
    User user = userRepository.findOne(id);
    return new ResponseEntity<>(user,HttpStatus.OK);
}
/**
 * <b>PUT</b> v1/users/{id}
 * @param user
 * @param id
 * @return
 */
@RequestMapping(value = "users/{id}", method = RequestMethod.PUT)
@ApiOperation(value = "Updates given User", response=Void.class)
@ApiResponses(value = {@ApiResponse(code=200, message="", response=Void.class),  
        @ApiResponse(code=404, message="Unable to find User", response=ErrorDetail.class) } )
public ResponseEntity<?> updateUser(@RequestBody User user, @PathVariable (value="id") Long id) {
    verifyUserById(id);
    // Save the entity
    if(!isIdInBodyCorrect(user,id)){
        user.setId(id);
    }
    User u = userRepository.findOne(id);
    userRepository.save(user);
    return new ResponseEntity<>(HttpStatus.OK);
}

/**
 * <b>DELETE</b> v1/users/{id}
 * @param id
 * @return
 */
@RequestMapping(value="users/{id}", method=RequestMethod.DELETE)
@ApiOperation(value = "Deletes given User", response=Void.class)
@ApiResponses(value = {@ApiResponse(code=200, message="", response=Void.class),  
        @ApiResponse(code=404, message="Unable to find User", response=ErrorDetail.class) } )
public ResponseEntity<?> deleteUser(@PathVariable (value="id") Long id) {
    verifyUserById(id);
    userRepository.delete(id);
    return new ResponseEntity<>(HttpStatus.OK);
}

protected boolean isIdInBodyCorrect(User user, Long id){
    if(user.getId() != id){
        logger.warn("Id in Body wasn't defined or wrong. ");  //TODO Exception werfen. 
        return false;
    }else{
        return true;
    }
}



}

Upvotes: 1

Views: 432

Answers (1)

Arif Acar
Arif Acar

Reputation: 1571

if you have a bidirectional relationship you have to use @JsonIdentityInfo annotation.

let’s see how to help with the serialization of entities with bidirectional relationship using @JsonIdentityInfo.

@Entity
@Table(name = "TABLENAME")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class ... {

Upvotes: 1

Related Questions