CASCADE
에는 여러 가지의 Type이 있다. CASCADE
설정을 통해 객체가 영속화될 때 연관 관계에 있는 객체도 함께 영속화할 수 있고, 객체가 삭제될 때 연관 관계에 있는 객체를 함께 삭제할 수 있다.
오늘은 CASCADE가 지원하는 영속성 전이 기능에 집중해 PERSIST
와 REMOVE
타입에 대해 알아보자.
지하철의 노선과 역을 나타내는 Line
, Station
도메인 객체가 있다.
Line
과 Station
은 일대다 양방향 매핑 관계이다.
@Entity
@Table(name = "line")
public class Line {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@OneToMany(mappedBy = "line")
List<Station> stations = new ArrayList<>();
...
public void addStation(Station station) {
station.setLine(this);
stations.add(station);
}
}
@Entity
@Table(name = "station")
public class Station {
@Id // (3)
@GeneratedValue(strategy = GenerationType.IDENTITY) // (4)
private Long id;
@Column(name = "name", nullable = false) // (5)
private String name;
@ManyToOne
private Line line;
...
public void setLine(final Line line) {
this.line = line;
final List<Station> stations = line.getStations();
if (!stations.contains(this)) {
stations.add(this);
}
}
}
Line
에 Station
을 추가한 후, Line
객체를 저장하는 로직이다. 일반적으로 Line
을 저장할 때 연관된 Station
객체들도 함께 영속화되기를 바랄 것이다.
@Test
void save() {
final Line line = new Line("2호선");
line.addStation(new Station("선릉역"));
lineRepository.save(line);
}
쿼리 실행 결과를 보면 2호선
은 insert쿼리가 날아갔지만, 선릉역
은 쿼리가 날아가지 않은 것을 볼 수 있다.
Hibernate:
insert
into
line
(id, name)
values
(null, ?)
2023-07-08 21:31:29.041 TRACE 14908 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [
stationRepository
의 save메서드를 사용해 선릉역
을 영속화시켰다.
@Test
void save() {
final Line line = new Line("2호선");
Station station = new Station("선릉역");
line.addStation(station);
lines.save(line);
stations.save(station);
entityManager.flush();
}
쿼리 결과를 보면 2호선
과 선릉역
이 전부 영속화된 것을 알 수 있다.
Hibernate:
insert
into
line
(id, name)
values
(null, ?)
2023-07-08 22:29:20.541 TRACE 15520 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [2호선]
Hibernate:
insert
into
station
(id, line_id, name)
values
(null, ?, ?)
2023-07-08 22:29:20.565 TRACE 15520 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
2023-07-08 22:29:20.566 TRACE 15520 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [선릉역]
@Test
void save() {
final Line line = new Line("2호선");
Station station1 = new Station("선릉역");
Station station2 = new Station("잠실역");
line.addStation(station1);
line.addStation(station2);
lines.save(line);
stations.save(station1);
stations.save(station2)
entityManager.flush();
}
연관된 모든 Station
객체들이 영속화 되는 것을 관리해주어야한다.
물론 saveAll 같은 메서드를 추가해 한 번에 Station
객체들을 영속화할 수 있겠지만, Line
과 연관된 객체가 Station
외에도 더 추가된다면?
어떤 객체를 영속화하고자 할 때 그와 연관된 모든 객체들의 영속화 여부도 유념해야한다는 것은 번거로운 일이다.
CascadeType.PERSIST
를 사용하면 객체를 영속화할 때 연관 객체들도 같이 영속화를 해준다.
@Entity
@Table(name = "line")
public class Line {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@OneToMany(mappedBy = "line", cascade = CascadeType.PERSIST)
List<Station> stations = new ArrayList<>();
...
}
선릉역
을 영속화하는 코드를 작성하지 않아도, 2호선
이 영속화될 때, 선릉역
도 함께 영속화되는 것을 볼 수 있다.
@Test
void save() {
final Line line = new Line("2호선");
line.addStation(new Station("선릉역"));
lines.save(line);
}
Hibernate:
insert
into
line
(id, name)
values
(null, ?)
2023-07-08 21:37:25.149 TRACE 16356 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [2호선]
Hibernate:
insert
into
station
(id, line_id, name)
values
(null, ?, ?)
2023-07-08 21:37:25.171 TRACE 16356 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
2023-07-08 21:37:25.171 TRACE 16356 --- [ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [선릉역]
어떤 객체와 연관된 모든 객체들을 같이 삭제하고 싶을 때는 CascadeType.REMOVE
를 사용하면 된다.
@Entity
@Table(name = "line")
public class Line {
...
@OneToMany(mappedBy = "line", cascade = {CascadeType.REMOVE, CascadeType.PERSIST}) // mappedBy 외래키 관리자가 없으면 새 테이블 line_stations이 생김
List<Station> stations = new ArrayList<>();
}
@Test
void delete() {
final Line line = new Line("2호선");
Station station = new Station("선릉역");
line.addStation(station);
lines.save(line);
lines.delete(line);
lines.flush();
}
@Test
void delete() {
final Line line = new Line("2호선");
Station station = new Station("선릉역");
line.addStation(station);
lines.save(line);
lines.delete(line);
lines.flush();
}