CASCADE를 사용한 영속성 전이

hongo·2023년 7월 9일
0

개요

CASCADE에는 여러 가지의 Type이 있다. CASCADE 설정을 통해 객체가 영속화될 때 연관 관계에 있는 객체도 함께 영속화할 수 있고, 객체가 삭제될 때 연관 관계에 있는 객체를 함께 삭제할 수 있다.

오늘은 CASCADE가 지원하는 영속성 전이 기능에 집중해 PERSISTREMOVE 타입에 대해 알아보자.

지하철의 노선과 역을 나타내는 Line, Station 도메인 객체가 있다.

LineStation은 일대다 양방향 매핑 관계이다.

@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);
        }
    }
}

영속성 전이 - 저장

LineStation 을 추가한 후, Line 객체를 저장하는 로직이다. 일반적으로 Line 을 저장할 때 연관된 Station객체들도 함께 영속화되기를 바랄 것이다.

@Test
void save() {
    final Line line = new Line("2호선");
    line.addStation(new Station("선릉역"));
    lineRepository.save(line);
}

`2호선`에 `선릉역`을 추가한 후, `JpaRepository`를 상속한 `lineRepository`의 save메서드를 사용해 `2호선`을 영속화했다. 이 때 `2호선`에 등록되어있는 `선릉역`은 함께 영속화가 될까?

쿼리 실행 결과를 보면 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] - [

Station을 영속화한 후에 Line을 영속화한다면?

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] - [선릉역]

연관된 Station이 많아진다면?

@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

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

어떤 객체와 연관된 모든 객체들을 같이 삭제하고 싶을 때는 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();
}

0개의 댓글