1

Here is an example code demonstrating the issue:

The Meal Entity:

@Entity
public class Meal {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  @OneToMany(mappedBy = "meal", cascade = CascadeType.PERSIST)
  private Collection<Food> foods;

  public Meal() {
    foods = new HashSet<>();
  }

  public Collection<Food> getFoods() {
    return foods;
  }

  public void addFood(Food food) {
    foods.add(food);
    // without this the `meal_id` column is null
    food.setMeal(this);
  }
}

The Food Entity:

@Entity
public class Food {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  @ManyToOne
  private Meal meal;

  public long getId() {
    return id;
  }

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

  public Meal getMeal() {
    return meal;
  }

  public void setMeal(Meal meal) {
    this.meal = meal;
  }
}

And here is the code that creates and saves the entities:

Meal meal = new Meal();

for (int i = 0; i < 10; i++) {
  meal.addFood(new Food());
}

mealRepository.save(meal);

Both the Meal and the Food entities are persisted thanks to the CascadeType.PERSIST, but the meal_id column stays null if I don't explicitly set the meal field to the Meal entity.

This is not the behavior I'd expect, and I'm wondering why isn't this automatically done for me.

szelpe
  • 121
  • 5
  • A lot of things aren't automatically done for you, so you just had the wrong expectation. Why would it be done automatically since `foods.add(food);` isn't done automatically? The programmer is ultimately responsible for the working of the program, so reading the documentation is essential in tricky cases like bi-directional mappings. – Kayaman May 29 '20 at 19:34
  • I’m voting to close this question because it works as designed. – Kayaman May 29 '20 at 20:21
  • 1
    @Kayaman "works as designed" is not really an answer when the question is why. I understand you don't know the answer, perhaps others will. – szelpe May 29 '20 at 20:54
  • 2
    @Kayaman I suggest you to look into DX (Developer Experience). Great software tools for developers work as expected most of the time. A developer at one point made a design decision and I'm curios what lead to that decision, and that's my question is all about. – szelpe May 31 '20 at 18:41
  • I've got over two decades of "DX" with Java, so don't patronize me. You can send an email to Gavin King, the original author of Hibernate, and ask if it was him alone that made a decision, or whether it even was a decision as you assume. These kinds of questions are bad (and usually closed) because most design decisions aren't known to SO users, although you may find them from the projects' internal mailing lists or bugtrackers. – Kayaman May 31 '20 at 19:33
  • 1
    In fact you can find plenty of explanation for design decisions on SO, e.g. https://stackoverflow.com/questions/22435833/why-is-string-chars-a-stream-of-ints-in-java-8 I usually enjoy reading these. – szelpe Jun 01 '20 at 07:30
  • In some lucky cases yes, if you manage to get an authoritative answer from someone like Stuart Marks or Brian Goetz. However that's not the general case, and the good questions tend to be old. I enjoy reading experts' answers too. I just wish there were more of them. – Kayaman Jun 01 '20 at 13:29

1 Answers1

4
@Entity
public class Meal {


  @OneToMany(mappedBy = "meal", cascade = CascadeType.PERSIST)
  private Collection<Food> foods;

} 

The mappedBy here means that the value of meal field in Food is used to provide the value for the corresponding database column that link between Food and Meal (i.e. meal_id column in Food table). So if you do not set it, it will always remain NULL.

On the other hand, without mappedBy means that the value of the foods field in Meal is used to provide the value for meal_id column. The reason of manually syncing them is just to have a consistent OOP model to work with.

CascadeType.PERSIST is nothing to do with setting the value of the entity. It will never update the value of the entities for you. What it helps is to automatically calling some entityManager methods on the related entities. Without it, you have to call the following to save all Food and Meal:

entityManager.persist(meal);
entityManager.persist(food1);
entityManager.persist(food2);
......
entityManager.persist(foodN);

With it , you just need to call

entityManager.persist(meal);

and the rest of entityManager.persist(foodN) will be 'cascaded' to call automatically.

So in term of spring-data-jpa , instead of calling

mealRepository.save(meal);
foodRepository.persist(food1);
foodRepository.persist(food2);
......
foodRepository.persist(foodN);

You just need to call

mealRepository.save(meal);
Morteza
  • 642
  • 7
  • 17
Ken Chan
  • 84,777
  • 26
  • 143
  • 172