본문 바로가기
spring/jpa

JPA - Cascade(영속성 전이)

by 쭈꾸마뇽 2021. 6. 10.

영속성 전이

JPA에서 처리하는 Entity의 상태에 따라 종속적인 객체들의 영속성도 함께 처리되는 것이며 총 5개의 옵션이 있다.

CascadeType

이번 게시글에서는 PERSIST와 REMOVE에 대해 테스트해보려 한다.  테스트에 사용할 Entity들은 Team과 Member이며 OneToMany로 양방향 매핑되어있다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    List<Member> members = new ArrayList<>();
    
    ...
}
@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
    
    ...
}

PERSIST

Persist옵션은 Entity가 persist될 때, 즉 save될 때 동작한다.  먼저 Casecade 옵션 없이 테스트해보겠다.

@SpringBootTest
@Transactional
class CascadeTestApplicationTests {

    @Autowired
    EntityManager em;

    @Test
    public void testEntity() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        teamA.getMembers().addAll(List.of(member1, member2));
        teamB.getMembers().addAll(List.of(member3, member4));
        em.persist(teamA);
        em.persist(teamB);
        //초기화
        em.flush();
        em.clear();
        //확인

        System.out.println("===========================================================================================================");
        List<Team> teams = em.createQuery("select t from Team t", Team.class).getResultList();
        teams.forEach(System.out::println);
        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
        members.forEach(System.out::println);
        System.out.println("===========================================================================================================");
    }
}

두개의 Team에 각각 2명의 Member를 매핑해 주었다.  그리고 persist는 Team에만 해준다.  이렇게 된 경우 Member들은 저장되지 않는다.

그래서 Member는 조회되지 않았고 Team에 매핑한 Member들도 전혀 저장되지 않은 상태이다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST)
    List<Member> members = new ArrayList<>();
    
    ...
}

반대로 Team의 members에 Persist 옵션을 넣어서 테스트 해보자.  테스트코드는 동일하며 결과를 확인해보면 Member들이 저장된 것을 확인할 수 있다.

Persist 옵션으로 Team에 연관된 Member들도 연속성 전이를 일으키며 같이 저장된 상태이다.

REMOVE

Remove 옵션은 Entity가 삭제될 때 작동한다.

@SpringBootTest
@Transactional
class CascadeTestApplicationTests {

    @Autowired
    EntityManager em;

    @Test
    public void testEntity() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        teamA.getMembers().addAll(List.of(member1, member2));
        teamB.getMembers().addAll(List.of(member3, member4));
        em.persist(teamA);
        em.persist(teamB);
        //초기화
        em.flush();
        em.clear();
        //확인

        System.out.println("===========================================================================================================");
        List<Team> teams = em.createQuery("select t from Team t", Team.class).getResultList();
        teams.forEach(System.out::println);
        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
        members.forEach(System.out::println);
        System.out.println("===========================================================================================================");

        em.remove(teams.get(0));
        em.flush();
        em.clear();

        List<Team> teams2 = em.createQuery("select t from Team t", Team.class).getResultList();
        teams2.forEach(System.out::println);
        List<Member> members2 = em.createQuery("select m from Member m", Member.class).getResultList();
        members2.forEach(System.out::println);
        System.out.println("===========================================================================================================");
       }
}

위에서 사용한 테스트 코드에 가져온 팀중 첫번 째 팀을 삭제하고 다시 조회를 하였다.  현재는 cascade 옵션이 없는 상태기 때문에 이 코드를 실행하면 flush때 예외가 발생한다.

Team은 삭제되었지만 연관되있는 Member에 대한 처리가 없기 때문이다.  이 코드가 동작하게 만들려면 Team에 연관된 Member들도 같이 삭제해줘야 한다.

teams.get(0).getMembers().forEach(member -> em.remove(member));

반대로 Cascade 옵션을 REMOVE로 설정하고 테스트 해보자.  REMOVE 옵션을 준 경우 별도로 Team에 연관된 Member들을 명시적으로 삭제하지 않더라도 자동으로 삭제가 가능하다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team", cascade = CascadeType.REMOVE)
    List<Member> members = new ArrayList<>();
    
    ...
}


OrphanRemoval

Cascade 옵션에 REMOVE를 넣지 않아도 연관된 객체를 삭제할 수 있는 방법중에 하나다.  이 옵션의 경우 Team을 삭제하게 되면 Member에 연관된 Team이 없어짐으로 Member는 주인 없는 고아객체가 되버린다.  이 옵션을 활성화 하면 고아객체가 된 객체들을 자동으로 삭제해준다.  기본값은 false로 되어있으며 true로 바꿔서 활성화 할 수 있다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team", orphanRemoval = true)
    List<Member> members = new ArrayList<>();
}

마찬가지로 정상적으로 삭제되는 것을 확인할 수 있다.

댓글