Why does Spring Data REST ignore cascade ops on an associated entity?

31 Views Asked by At

Background

I have an Car entity with a @ManyToOne relationship with a Make entity:

@Entity
public class Car {
 
  @Id
  Long id;

  @ManyToOne(cascade = CascadeType.ALL)
  Make make;
}

@Entity
public class Make {

  @Id
  Long id;
  String name;
}

Both Car and Make have repositories:

public interface CarRepository extends CrudRepository<Car, Long> {
}

public interface MakeRepository extends CrudRepository<Make, Long> {
}  

Problem

When a client issues an HTTP POST request to the Spring Data REST endpoint /api/cars with a payload of:

{
  "id":null, 
  "make": {
    "id": null,
    "name": "Toyota"
  }
}

A new car is created and persisted in the database but its Make is set as null in the DB.

What I expected

Since CascadeType.ALL is specified and the make of the car has a null id, I thought a new Make record would be persisted in the DB and that the new Car record would have its foreign key to Make (i.e., makeId) set to that new Make record.

Observations

  • If Make does not have its own repository (i.e., I delete the MakeRepository), the make will be persisted as expected and associated with the new car.
  • If I annotate the make field in Car with @RestResource(exported = false), the make will be persisted as expected and associated with the new car.
  • If I save Make first and then set that on the Car, the make will obviously be persisted and then associated with the new car.

Questions

Is there a way to save the make of a car without adding @RestResource to the make field or without a separate save? Where can I find Spring documentation describing this behavior?

1

There are 1 best solutions below

0
Sahab On

Why Make (i.e., makeId) is set as null in the DB :

  • There is no GeneratedValue in your Car and Make entity

How we can fix / solve :

  • Update your entity like below

    @Entity public class Car {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    Make make;
    

    }

    @Entity public class Make {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    
    String name;
    

    }

  • To save a new Car and a new Make with a single save ie carRepository.save(car) your payload will be like below

    {
      "id":null, 
      "make": {
        "id": null,
        "name": "Toyota"
      }
    }

  • To Update an old Car and an old Make with a single save ie carRepository.save(car) your payload will be like below

    {
      "id":2, 
      "make": {
        "id": 21,
        "name": "Toyota"
      }
    }