如何使用Sping Data Jpa OneToMany嵌套模型在Spring Boot Restful API上执行CURD操作?

时间:2018-10-10 06:40:45

标签: java spring-mvc spring-boot spring-data-jpa spring-restcontroller

我正在尝试制作一个具有两个类型对象的肌肉应用程序,即Muscle和Exercise类型。基本上,一个肌肉对象可以包含一个运动对象列表。我想为两个模型对象实现CRUD操作。对于Muscle对象,它非常简单,但是对于put / Update操作的Exercise Object,我收到以下错误“ JSON解析错误:无法解析的前向引用:;嵌套的异常是com.fasterxml.jackson.databind.deser.UnresolvedForwardReference “。还有,如果我尝试删除一项运动,那么肌肉和运动的所有数据都会被删除。

这是我的肌肉课

    package com.fazla.exercise.model;

    import java.util.ArrayList;
    import java.util.List;

    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.OneToMany;

    import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    import com.fasterxml.jackson.annotation.ObjectIdGenerators;


    @Entity
    @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
    public class Muscle {

        @Id
    //  @Column(unique = true, nullable = false)
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;

        @Column
        private String name;

        @OneToMany(mappedBy="muscle", cascade= CascadeType.ALL, fetch = FetchType.EAGER)
    //  @JoinColumn(name="muscle_id")
    //  @Column(nullable = true)
        private List<Exercise> exercises = new ArrayList<>();

        public Muscle() {

        }

        public Muscle(String name, List<Exercise> exercises) {
            super();
            this.name = name;
            this.exercises = exercises;
        }

        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 List<Exercise> getExercises() {
            return exercises;
        }

        public void setExercises(List<Exercise> exercises) {
            this.exercises = exercises;
        }

    //  @Override
    //  public String toString() {
    //      return "Muscle [id=" + id + ", name=" + name + ", exercises=" + exercises + "]";
    //  }



}

这是我的锻炼对象

    package com.fazla.exercise.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
//import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@Entity
//@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
public class Exercise {
    @Id
    @Column(unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String name;

    @Column
    private String description;


    //As there will be many exercise under one muscle that is why manytoone
    //object references an unsaved transient instance - save the transient instance before flushing 
    //that is why need to add the cascading dependencies
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="muscle_id")
//  @JsonIgnore
//  @JoinTable(name="muscle")
    private Muscle muscle;

    public Exercise() {

    }

    public Exercise(String name, String description, Muscle muscle) {
        super();
        this.name = name;
        this.description = description;
        this.muscle = muscle;
    }

    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 String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Muscle getMuscle() {
        return muscle;
    }

    public void setMuscle(Muscle muscle) {
        this.muscle = muscle;
    }

//  @Override
//  public String toString() {
//      return "Exercise [id=" + id + ", name=" + name + ", description=" + description + ", muscle=" + muscle + "]";
//  }



}

这是MuscleController

package com.fazla.exercise.controller;

import java.util.List;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.fazla.exercise.model.Muscle;
import com.fazla.exercise.repository.MuscleRepository;

@RestController
public class MuscleController {

    private MuscleRepository muscleRepository;

    public MuscleController(MuscleRepository muscleRepository) {

        this.muscleRepository = muscleRepository;
    }

    @GetMapping("/muscle")
    List<Muscle> all(){
        return muscleRepository.findAll();
    }

    @PostMapping("/muscle")
    Muscle newMuscle(@RequestBody Muscle muscle) {
        return muscleRepository.save(muscle);
    }

    @GetMapping("/muscle/{id}")
    Muscle one(@PathVariable Long id) {
        return muscleRepository.findById(id)
                    .orElse(null);
    }

    @PutMapping("/muscle/{id}")
    Muscle updateMuscle(@RequestBody Muscle newMuscle, @PathVariable Long id) {
        return muscleRepository.findById(id)
                .map(muscle ->{
                    muscle.setName(newMuscle.getName());
                    muscle.setExercises(newMuscle.getExercises());
                    return muscleRepository.save(muscle);
                })
                .orElse(null);
    }

    @DeleteMapping("/muscle/{id}")
    void deleteMuscle(@PathVariable Long id){
        muscleRepository.deleteById(id);
    }

}

这是ExerciseController类

package com.fazla.exercise.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.fazla.exercise.model.Exercise;
import com.fazla.exercise.model.Muscle;
import com.fazla.exercise.repository.ExerciseRepository;
import com.fazla.exercise.repository.MuscleRepository;

@RestController
public class ExerciseController {

    private ExerciseRepository repository;

    private MuscleRepository muscleRepository;

    @Autowired
    public ExerciseController(ExerciseRepository repository, MuscleRepository muscleRepository) {
        super();
        this.repository = repository;
        this.muscleRepository=muscleRepository;
    }

    @GetMapping("/exercise")
    public List<Exercise> getAll() {
        return repository.findAll();
    }

    @PostMapping("/exercise")
    public Exercise newExercise(@RequestBody Exercise newExercise, @RequestParam 
            Long muscleId) {

        Muscle muscle = muscleRepository.findById(muscleId).orElse(null);
        newExercise.setMuscle(muscle);

        return repository.save(newExercise);
    }

    @DeleteMapping("/exercise/{id}")
    public void deleteExercise(@PathVariable Long id) {
        repository.deleteById(id);
    }

    @GetMapping("/exercise/{id}")
    public Exercise one(@PathVariable Long id) {
        return repository.findById(id).orElse(null);
    }

    @PutMapping("/exercise/{id}")
    public Exercise updateExercise(@RequestBody Exercise newExercise, @PathVariable Long id) {
            return repository.findById(id)
                    .map(//map a function which maps 
                        e ->{
                        e.setName(newExercise.getName());
                        e.setDescription(newExercise.getDescription());
                        e.setMuscle(newExercise.getMuscle());
                        return repository.save(e);
                    })
                    .orElse(null);
    }
}

这是我的ExerciseRepository

包com.fazla.exercise.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.fazla.exercise.model.Exercise;

public interface ExerciseRepository extends JpaRepository<Exercise, Long> {

}

这是MuscleRepository

包com.fazla.exercise.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.fazla.exercise.model.Muscle;

public interface MuscleRepository extends JpaRepository<Muscle, Long>{

}

如果我尝试放置请求或更新行使对象,这就是错误

"timestamp": "2018-10-10T06:30:46.924+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Unresolved forward references for: ; nested exception is com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Unresolved forward references for: \n at [Source: (PushbackInputStream); line: 23, column: 1]Object id [1] (for `com.fazla.exercise.model.Muscle`) at [Source: (PushbackInputStream); line: 13, column: 28], Object id [1] (for `com.fazla.exercise.model.Muscle`) at [Source: (PushbackInputStream); line: 19, column: 28].",
"path": "/api/exercise/14"

2 个答案:

答案 0 :(得分:1)

解决方案是在“父/肌肉”模型上添加orphanRemoval = true

    @OneToMany(mappedBy="muscle", cascade= CascadeType.ALL, orphanRemoval= true)
    private List<Exercise> exercises = new ArrayList<>();

在子模型/运动模型中删除cascade = CascadeType.ALL

@ManyToOne
private Muscle muscle;

对于updateExercise,通过找到锻炼所属的肌肉和muscleRepository.findById(muscleId)并将其设置在新的锻炼对象中来更改“请求”。

    @PutMapping("/exercise/{id}")
public Exercise updateExercise(@RequestBody Exercise newExercise, @PathVariable Long id, @RequestParam Long muscleId) {
    Muscle muscle = muscleRepository.findById(muscleId).orElse(null);
        return repository.findById(id)
                .map(//map a function which maps 
                    e ->{
                    e.setName(newExercise.getName());
                    e.setDescription(newExercise.getDescription());
                    e.setMuscle(muscle);
                    return repository.save(e);
                })
                .orElse(null);
}

答案 1 :(得分:0)

锻炼后

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="muscle_id")
private Muscle muscle;

在Musce上,您拥有

@OneToMany(cascade= CascadeType.ALL)
private List<Exercise> exercises = new ArrayList<>();

Cascade.ALL传播对象上的所有操作,如果您删除锻炼,DELETE传播到所有引用的对象

因为您只想传播UPDATES替换

cascade = CascadeType.ALL

cascade = CascadeType.SAVE_UPDATE