javaspringhibernatespring-data-jpajpa-2.1

How to delete child records using Spring Data JPA


I am having a lot of issues trying to delete child records using JPA. Consider the mappings below. I am trying to delete all the state data in the jobs table. When it is removed it will cascade all deletes down the chain. Here is the chain:

Job -> State -> TaskState -> Step

When I try to remove state from job table it is set to NULL. However it is not cascaded down the chain. How would I go about achieving this?

Here is how I am deleting the data:

@Transactional
public void runJob(Long id) throws IOException, JAXBException {
    
        Job job = jobRepository.findOne(id);

        if(job.getState() != null){
            State stateObj = stateRepository.findOne(job.getState().getId());
            stateObj.setTasks(null);
            stateRepository.delete(stateObj);
        }

        job.setState(null);
        jobRepository.save(job);
}

Jobs:

@Entity
@Table(name = "jobs")
public class Job implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "description")
    private String description;
    @Temporal(TemporalType.TIMESTAMP)
    private Date date;
    @OneToOne
    private Image image;
    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    private State state;
    @OneToMany
    private List<Task> tasks;
    @Column(name = "status")
    @Enumerated(EnumType.STRING)
    private JobStatusEnum status;
    @ManyToMany
    private List<Device> devices;
    @OneToMany
    private List<JobRun> runs;
    @OneToMany
    private List<Container> containers;

    public Job() {
        this.image = new Image();
        this.status = JobStatusEnum.New;
        this.devices = new ArrayList<>();
        this.runs = new ArrayList<>();
        this.containers = new ArrayList<>();
    }

    public Job(String name, String description, Date date, Image image, State state, List<Task> tasks, JobStatusEnum status, List<Device> devices, List<JobRun> runs, List<Container> containers) {
        this.name = name;
        this.description = description;
        this.date = date;
        this.image = image;
        this.state = state;
        this.tasks = tasks;
        this.status = status;
        this.devices = devices;
        this.runs = runs;
        this.containers = containers;
    }

    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 Date getDate() {
        return date;
    }

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

    public Image getImage() {
        return image;
    }

    public void setImage(Image image) {
        this.image = image;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

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

    public void setTasks(List<Task> tasks) {
        this.tasks = tasks;
    }

    public JobStatusEnum getStatus() {
        return status;
    }

    public void setStatus(JobStatusEnum status) {
        this.status = status;
    }

    public List<Device> getDevices() {
        return devices;
    }

    public void setDevices(List<Device> devices) {
        this.devices = devices;
    }

    public List<JobRun> getRuns() {
        return runs;
    }

    public void setRuns(List<JobRun> runs) {
        this.runs = runs;
    }

    public List<Container> getContainers() {
        return containers;
    }

    public void setContainers(List<Container> containers) {
        this.containers = containers;
    }
}

State:

@Entity
@Table(name = "state")
public class State implements Serializable {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="name")
    private String name;
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<TaskState> tasks;

    public State() {
        tasks = new ArrayList<>();
    }

    public State(Long id, String name, List<TaskState> tasks) {
        this.id = id;
        this.name = name;
        this.tasks = tasks;
    }

    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<TaskState> getTasks() {
        return tasks;
    }

    public void setTasks(List<TaskState> tasks) {
        this.tasks = tasks;
    }
}

TaskState:

@Entity
@Table(name = "task_state")
public class TaskState implements Serializable {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "parent")
    private Long parent;
    @Column(name = "referenceid")
    private Long referenceId;
    @Column(name = "name")
    private String name;
    @Column(name = "status")
    private TaskStatusEnum status;
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Step> steps;
    @Column(name = "maxAttempts")
    private Integer maxAttempts;
    @ManyToOne
    @JsonIgnore
    private State state;

    public TaskState() {
        status = TaskStatusEnum.New;
        steps = new ArrayList<>();
        maxAttempts = 5;
    }

    public TaskState(Long id, Long parent, Long referenceId, String name, TaskStatusEnum status, List<Step> steps, Integer maxAttempts) {
        this();
        this.id = id;
        this.parent = parent;
        this.referenceId = referenceId;
        this.name = name;
        this.status = status;
        this.steps = steps;
        this.maxAttempts = maxAttempts;
    }

    public Long getId() {
        return id;
    }

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

    public Long getParent() {
        return parent;
    }

    public void setParent(Long parent) {
        this.parent = parent;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public TaskStatusEnum getStatus() {
        return status;
    }

    public void setStatus(TaskStatusEnum status) {
        this.status = status;
    }

    public List<Step> getSteps() {
        return steps;
    }

    public void setSteps(List<Step> steps) {
        this.steps = steps;
    }

    public Long getReferenceId() {
        return referenceId;
    }

    public void setReferenceId(Long referenceId) {
        this.referenceId = referenceId;
    }

    public Integer getMaxAttempts() {
        return maxAttempts;
    }

    public void setMaxAttempts(Integer maxAttempts) {
        this.maxAttempts = maxAttempts;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }
}

Step:

@Entity
@Table(name = "step")
public class Step implements Serializable {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "groupname")
    private String group;
    @Column(name = "route")
    private String route;
    @Column(name = "celerytask")
    private String celeryTask;
    @Column(name = "postbackqueuename")
    private String postBackQueueName;
    @Column(name = "postbackerrorqueuename")
    private String postBackErrorQueueName;
    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private TaskStatusEnum status;
    @ElementCollection
    private List<String> arguments;
    @Column(name="runningTaskId")
    private Long runningTaskId;
    @Column(name="result")
    private String result;
    @Column(name="attempt")
    private Integer attempt;
    @JsonIgnore
    @ManyToOne
    private TaskState task;

    public Step() {
        arguments = new ArrayList<>();
        status = TaskStatusEnum.New;
        attempt = 0;
    }

    public Step(String name, String group, String route, String celeryTask, String postBackQueueName, String postBackErrorQueueName, TaskStatusEnum status, List<String> arguments, Long runningTaskId) {
        this();
        this.name = name;
        this.group = group;
        this.route = route;
        this.celeryTask = celeryTask;
        this.postBackQueueName = postBackQueueName;
        this.postBackErrorQueueName = postBackErrorQueueName;
        this.status = status;
        this.arguments = arguments;
        this.runningTaskId = runningTaskId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public String getRoute() {
        return route;
    }

    public void setRoute(String route) {
        this.route = route;
    }

    public String getCeleryTask() {
        return celeryTask;
    }

    public void setCeleryTask(String celeryTask) {
        this.celeryTask = celeryTask;
    }

    public String getPostBackQueueName() {
        return postBackQueueName;
    }

    public void setPostBackQueueName(String postBackQueueName) {
        this.postBackQueueName = postBackQueueName;
    }

    public String getPostBackErrorQueueName() {
        return postBackErrorQueueName;
    }

    public void setPostBackErrorQueueName(String postBackErrorQueueName) {
        this.postBackErrorQueueName = postBackErrorQueueName;
    }

    public List<String> getArguments() {
        return arguments;
    }

    public void setArguments(List<String> arguments) {
        this.arguments = arguments;
    }

    public TaskStatusEnum getStatus() {
        return status;
    }

    public void setStatus(TaskStatusEnum status) {
        this.status = status;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public Integer getAttempt() {
        return attempt;
    }

    public void setAttempt(Integer attempt) {
        this.attempt = attempt;
    }

    public Long getId() {
        return id;
    }

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

    public Long getRunningTaskId() {
        return runningTaskId;
    }

    public void setRunningTaskId(Long runningTaskId) {
        this.runningTaskId = runningTaskId;
    }

    public TaskState getTask() {
        return task;
    }

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

Solution

  • It's not cascading because of stateObj.setTasks(null);.

    You're breaking the link between the State and its Tasks so when you're deleting the State, the delete doesn't cascade on anything.