Save an object and a nested object with only one Repository save request

Viewed 2313

I'm trying to save an object and a nested object with only one JpaRepository request. So I'm using the save method of parent object's repository. There are my entities:

Parent

@Entity
@Data
@Table(name = "race")
@NoArgsConstructor
public class Race {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected int id;

private String name;

@OneToMany(cascade = CascadeType.ALL, mappedBy = "race")
private List<Stage> stages;

// some others attributes

}

Nested

@Data
@Entity
@Table(name = "stage")
@NoArgsConstructor 
public class Stage {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected int id;

String traceName;

@ManyToOne
@JoinColumn(name = "race_id", nullable = false, foreignKey = @ForeignKey(name = "stage_fk_race"))
private Race race;

//some other attributes

}

I'm using swagger to try my saving. This is my query's body :

{
  "name": "Trail du Clair de Lune",
  "stages": [
    {
      "traceName": "trail_8km.gpx",
    }
  ]
}

My controller :

@RestController
@RequestMapping(value = "/race", produces = MediaType.APPLICATION_JSON_VALUE)
public class RaceController {

private final RaceService raceService;

@Autowired
public RaceController(RaceService raceService) {
    this.raceService = raceService;
}

@PostMapping(value = "save")
public Race save(@RequestBody Race race) {
    System.out.println(race.toString());
    return raceService.save(race);
}

@GetMapping(path = "/{id}")
public Race getOne(@PathVariable Integer id) {
    Optional<Race> byId = raceService.findById(id);

    if(byId.isPresent()) {
        return byId.get();
    } else return null;
    }
}

My service :

@Service
public class RaceServiceImpl implements RaceService {

private RaceRepository raceRepository;

@Autowired
public RaceServiceImpl(RaceRepository raceRepository) {
    this.raceRepository = raceRepository;
}

@Override
public Race save(Race race) {
    return raceRepository.save(race);
}

@Override
public Optional<Race> findById(Integer id) {
    return raceRepository.findById(id);
    }
    
}

My repository :

    @Repository
public interface RaceRepository extends JpaRepository<Race, Integer> {

}

And I get this error :

"message": "org.hibernate.exception.ConstraintViolationException: could not execute statement"

I think the nested object stage is saved first. So the parent id doesn't exist yet and it can't set race_id attribute.

Is there a way to save both race and stage in the same query ?

Thanks in advance, Thomas

1 Answers
  • In JPA/Hibernate, you have to explicity set the bi-directioanl relatonship. When race object is created by jackson, it will have list of stage objects. But stage objects will not have race object.
     @PostMapping(value = "save")
     public Race save(@RequestBody Race race) {
        System.out.println(race.toString());
        race.getStages().forEach(stage -> stage.setRace(race));
        return raceService.save(race);
     }
  • Mark it as Optional=false
    @ManyToOne(optional = false)
    @JoinColumn(name = "race_id".....)
    private Race race;

Upate

The second error you are getting is not because of hibernate.

  • Either you are trying to print or debug the saved race object and toString object generated by Lombok goes into infinite recursion. To avoid that, do not make the stages part of the toString and equals method.

  • Or infinite recursion is happening, when Jackson is translating the saved race to json. There are many ways to tell Jackson to avoid it but simplest you can do is

    @PostMapping(value = "save")
     public Race save(@RequestBody Race race) {
        System.out.println(race.toString());

        race.getStages().forEach(stage -> stage.setRace(race));
        Race saved = raceService.save(race);

        saved.getStages().forEach(stage -> stage.setRace(null));
        return saved;
     }
Related