lv2. JPA Cascade
JPA의 관계 매핑과 Cascade(영속성 전이)에 대해 잘 알고 있다면 문제될 것이 없는 문제입니다.
JPA에서 객체 간의 관계를 설정할 때는 @OneToMany, @ManyToOne, @OneToOne, @ManyToMany 등의 어노테이션을 사용합니다.
@OneToMany: 하나의 Todo가 여러 개의 Manager를 가질 수 있음
@ManyToOne: 여러 개의 Manager가 하나의 Todo에 속함
// Manager.java (ManyToOne 관계)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "todo_id", nullable = false)
private Todo todo;
// Todo.java (OneToMany 관계)
@OneToMany(mappedBy = "todo", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Manager> managers = new ArrayList<>();
JPA에서 부모 엔티티가 저장, 삭제될 때 자식 엔티티에도 같은 동작이 전파되도록 하는 기능입니다.
즉, Todo를 저장할 때 Manager도 자동으로 저장되게 만들 수 있습니다.
orphanRemoval = true 설정을 추가하면 부모가 관계를 끊으면 자식 엔티티도 삭제됨
즉, Todo에서 managers 리스트에서 요소를 제거하면 자동으로 Manager 데이터도 삭제됨
public void addManager(Manager manager) {
managers.add(manager);
manager.setTodo(this);
}
이런식으로 하면 더 좋다는 말 (메서드 확인하면 됩니다)
여기서 궁금해해야합니다.
지금 Manager 엔티티를 Todo와 연관시키고 있는데, CascadeType.ALL을 설정하지 않으면 할 일을 저장할 때 Manager도 함께 저장되지 않습니다.
@OneToMany(mappedBy = "todo", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Manager> managers = new ArrayList<>();
CascadeType.ALL을 설정하면 부모(Todo)에 대한 모든 영속성 변화가 자식(Manager)에게 전파됩니다.
지금 보면 persist와 remove만 사용해도 가능하지만, 앞으로 todo를 더 수정하게 되거나, 수정할 계획이 있따면(가능성) 일괄적으로 ALL 설정하는 것이 제일 안전한 방법입니다. (나머지는 현재 요구 사항에서 필요 x)
쉽게 예시로 설명하면,
Todo todo = new Todo("지금은 Spring 공부중", "JPA 연관관계 학습", "맑음", user);
todoRepository.save(todo); // 자동으로 Manager도 함께 저장
todoRepository.delete(todo); // 자동으로 Manager도 함께 삭제
이렇게 todo 저장할 때 manager도 같이 저장되고, todo삭제시에 manager도 같이 자동으로 삭제돠ㅣㅂ니다.
둘은 서로 다른 역할을 하고, orphanRemoval = true만 설정한다고 해서 Manager가 자동 저장되지 않습니다. 따라서, 자동으로 할 일을 만든 사용자가 담당자로 등록 되도록 하려면 ALL 옵션이 반드시 필요합니다.
아래 표 참고하시면 됩니다.
코드를 정독하지 않고 봤을 때엔,
서비스에서 매니저를 건들지도 않은데 persist로 걸면 jpa랑 자기 리스트에 그 한마디로 양방향을 걸면 원래 꺼가 있고 그거에 대한 리스트에 양방향이 있고 양방향에 객체를 넣고 세이브를 하면 그거를 같이 저장안해도 알아서 jpa가 저장함. 이거를 생성자에서 해주고 있음.
new manage생성자에서 todo가 있고 유저가 있음. 일정에 대해서 관리자가 여러명일 수 있음.
여행을 가면 여행 일정에 대해서 관리자가 여려명일 수 있음. 이거를 자동으로.
생성자도 유저인데 생성자인 사람, 생성 한 사람도 관리할 수 있잖아.
그래서 생성자에서 비슷한 유저끼리 해주고, 해당유저를 매니저에다 추가해준거다.
끄러면 jpa에서 알아서 저장을 매니저에 해줌. 이게 양방향.
그래서 생성자때문에 cascade = CascadeType.PERSIST 하면 저장이 된다.
라는 생각의 전환(?)이 되었었습니다.
설명을 덧 붙여서 이해를 하자면,
콰드로 예시를 든다면?
@Entity
public class Task {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToOne
private User creator; // 생성한 유저
@OneToMany(mappedBy = "task", cascade = CascadeType.PERSIST)
private List<Assignee> assignees = new ArrayList<>();
//생성자에서 creator를 Assignee로 자동 추가
public Task(String title, User creator) {
this.title = title;
this.creator = creator;
this.addAssignee(new Assignee(this, creator)); //creator를 자동으로 Assignee로 등록
}
public void addAssignee(Assignee assignee) {
assignees.add(assignee);
assignee.setTask(this); // 양방향 관계 유지
}
}
Task가 생성될 때 생성한 유저(creator)가 자동으로 Assignee로 등록됨.
cascade = CascadeType.PERSIST 덕분에, Task 저장 시 Assignee도 자동 저장됨.
@Entity
public class Assignee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Task task;
@ManyToOne
private User user;
public Assignee(Task task, User user) {
this.task = task;
this.user = user;
}
public void setTask(Task task) {
this.task = task;
}
}
Assignee는 어떤 Task와 어떤 User가 연결되는지를 저장하는 엔티티.
public Task createTask(String title, User creator) {
Task task = new Task(title, creator); // 생성자에서 Assignee 자동 추가
return taskRepository.save(task); // Task 저장 시 Assignee도 자동 저장됨
}
List<Assignee>
를 감지하고, cascade = CascadeType.PERSIST 덕분에 Assignee도 자동 저장됨.