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

How to do the CURD operations on Spring Boot Restful API using Sping Data Jpa OneToMany nested model?

我试图制作一个 crud 应用程序,它有两个类型为 Muscle 和 Exercise 的模型对象。基本上一个肌肉对象可以有一个锻炼对象列表。我想为这两个模型对象实现 CRUD 操作。对于 Muscle 对象,它非常简单,但对于 put/Update 操作的 Exercise 对象,我收到以下错误 "JSON parse error: Unresolved forward references for: ; nested exception is com.fasterxml.jackson.databind.deser.UnresolvedForwardReference" 。更进一步,如果我尝试删除一项锻炼,肌肉和锻炼的所有数据都会以某种方式被删除。

这是我的肌肉class

    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 + "]";
//  }



}

这是肌肉控制器

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);
    }

}

这是运动控制器Class

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> {

}

这是肌肉库

包 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"

在你的练习中你有

@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

解决方案是,在 Parent/Muscle 模型上添加 orphanRemoval= true

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

移除子/运动模型中的级联= CascadeType.ALL

@ManyToOne
private Muscle muscle;

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

    @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);
}