Adriano
Adriano

Reputation: 924

How to merge child entity in Spring Data JPA

I'm using Spring Data with CrudRepository. I'm trying to save Parent with Cascade to children and I'm giving Hibernate the possibility to merge children entities, but I'm getting error a different object with the same identifier value was already associated with the session. That is happening probably when he has persisted two parent entities which has other children entities (RecipeIngredients). I'm trying to override equals and hashcode to focus only on id and name, but it nothing change. Recipe object are the same but List<RecipeIgredients> are different. Any ideas on how to solve it?

Example:

This is my existed object:

{
  "id": 100,
  "name": "salat",
  "ingredients": [
    {
      "ingredient": {
        "id": 100,
        "name": "banana"
      },
      "count": 2
    },
    {
      "ingredient": {
        "id": 1,
        "name": "eggs"
      },
      "count": 1
    }
  ]
}

And I want to update it to one below (remove one ingredient):

{
  "id": 100,
  "name": "salat",
  "ingredients": [
    {
      "ingredient": {
        "id": 100,
        "name": "bannana"
      },
      "count": 2
    }
  ]
}

Parent:

@Entity
@Data
public class Recipe {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "recipe_generator")
@SequenceGenerator(name="recipe_generator", sequenceName = "recipe_seq")
@Column(name = "id", nullable = false)
private Long id;

@NaturalId
@Column
private String name;

@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RecipeIngredients> ingredients;

}

Children in the mid-table

@Entity
@Data
public class RecipeIngredients implements Serializable {

@EmbeddedId
private RecipeIngredientsId recipeIngredientsId;

@ManyToOne(fetch = FetchType.LAZY)
@MapsId("recipeId")
private Recipe recipe;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@MapsId("ingredientId")
private Ingredient ingredient;

@Column
private Integer count;

public RecipeIngredients(Recipe recipe, Ingredient ingredient) {
    this.recipe = recipe;
    this.ingredient = ingredient;
    this.recipeIngredientsId = new RecipeIngredientsId(recipe.getId(), ingredient.getId());
}

}

Children

@Entity
@Data
public class Ingredient {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ingredient_generator")
    @SequenceGenerator(name="ingredient_generator", sequenceName = "ingredient_seq")
    @Column(name = "id", updatable = false, nullable = true)
    private Long id;

    @NaturalId
    @Column(unique = true)
    private String name;

}

Upvotes: 3

Views: 1853

Answers (1)

y0bu
y0bu

Reputation: 85

I can give you example of one of my projects i will hope it will help:

the parent:

package com.yoav.todolist.models;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "accounts")
public class Account {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private int id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @OneToMany(
            mappedBy = "account",
            orphanRemoval = true,
            cascade = CascadeType.ALL
    )
    private List<Task> tasks = new ArrayList<>();

    public Account(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public Account() {
    }

    public void removeTask(Task task) {
        tasks.remove(task);
        task.setAccount(null);
    }

    public void addTask(Task task) {
        tasks.add(task);
        task.setAccount(this);
    }

    public List<Task> getTasks() {
        return tasks;
    }

    public void setTasks(List<Task> tasks) {
        this.tasks.forEach(i -> i.setAccount(null));
        this.tasks.clear();

        tasks.forEach(i -> {
            i.setAccount(this);
            addTask(i);
        });
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean equals(Object account) {
        return ((Account)account).getUsername().equals(this.username);
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

the child:

package com.yoav.todolist.models;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "tasks")
public class Task {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private int id;

    @Column(name = "task")
    private String task;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Account account;

    @Temporal(TemporalType.DATE)
    @Column(name = "date_of_creation_task")
    private Date date;

    public Task(String task) {
        this.date = new Date();
        this.task = task;
    }

    public Task() {
        this.date = new Date();
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public int getId() {
        return id;
    }

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

    public String getTask() {
        return task;
    }

    public void setTask(String task) {
        this.task = task;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    @Override
    public boolean equals(Object task) {
        return ((Task)task).getId() == this.id;
    }

    @Override
    public String toString() {
        return "Task{" +
                "id=" + id +
                ", task='" + task + '\'' +
                '}';
    }

    @Override
    public int hashCode() {
        return 31;
    }

}

in my case i just need to get the entity for example accountSerice.getById(1); and then in my case I just need to to do account.setTasks(tasks); // tasks is just list of the childs @see the setTasks() at the account(parent) entity

Upvotes: 1

Related Questions