본문 바로가기

Study/개발일지

[백엔드온라인TIL] java 학습 42일차

jpa에서 영속성의 의미 

Persistence 사전적 의미는 지속되다라는 의미가 있다.
엔티티 매니저의 persist() 메서드가 있다. save라고 해도될텐데 말이다.
이유는 정의하기 나름일 수도 있지만, 나는 간단하게 flush, commit 단계가 존재하고,
영속성 컨텍스트에 영속화 하는 것이기 때문에 persist()라고 네이밍 했을 것이라고 했다.
save는 persist, flush, commit을 모두 포함하는 것이기 때문이다.

팀원 분의 생각은 조금 더 나아간 생각이었다.

1.1.1. 영속성 컨텍스트에서 영속이 뭔가요?

나는 영속성 컨텍스트에 객체를 영속 시킨다는 것이라고 했다.
객체를 관리하는 컨텍스트이다 라는 의미였다.

다음 질문이 있었다.

1.1.2. 영속이란 무엇인가요?

설명이 되지 않아 사전을 찾아보니 지속적인 것, 영원한 것과 같은 의미가 있었다.
누구나 대략 감으로 알고 있는 내용일 것이다.
여기서 왜 JPA는 persistence라는 영속이라는 개념을 사용하는지 본인의 생각을 전달해주셨다.

이유는?

1.1.3. JVM 외부에 객체를 영원히 관리하고 싶기 때문이다.

처음에는 무슨 의미지? 라는 생각이 들었다.
하지만 점점 공감이 되었다.
java라는 것은 JVM이라는 세상에서 존재하게 된다. 즉, JVM 밖에서는 지속될 수 없는 것이다.
JVM이 사라지면 java 객체도 사라지는 것이다. 그런데 JPA는 이를 JVM 밖에서도 유지하고 싶었던 것이다.
그래서 JVM에 있던 객체를 JVM 밖에서도 지속하고 싶다는 생각으로 영속화라는 개념이 나오게 된 것이었다.
그게 데이터 베이스가 될 수도, 텍스트 파일이 될 수도 있다. 중요한건 JVM 외부에서 객체를 유지하고 싶다는 것이다.

여기서 나는 의문이 들었다.

1.1.4. 그럼 굳이 JPA(ORM)이 아니고, MyBatis를 쓴다고 해도 이것도 영속이겠네요?

객체를 JVM 외부에서 유지를 하고싶다. 라는 것은 결국 데이터 베이스에 저장한다는 것인데
JPA를 사용하지 않고, MyBatis를 쓴다고 해도 객체를 JVM 외부인 데이터 베이스에 지속시킬 수 있는 것이다.
하지만, JPA에서만 영속화라는 persist라는 의미를 사용하는 이유는 나의 생각은 이렇다.
MyBatis는 객체 지향적 사고가 아닌 데이터 베이스 위주의 데이버 베이스 매퍼 객체일 뿐이다.
물론 JVM 외부인 데이터 베이스에서 객체를 유지하는 것은 맞지만 패러다임 자체가 다른 것이다.
객체를 외부에서 유지하고 싶다가 아닌 데이터 베이스에 맞춰 매핑을 하고 싶다라는 것이 핵심이다.
반면, JPA는 데이터 베이스가 메인이 아니다. 데이터베이스에 영향을 덜 받고 객체지향적으로 설계하기 위한 것이다.
즉, 객체를 외부에 지속하고 싶은 것이다.
정리를 하자면 MyBatis는 DB → 객체라면 JPA는 객체 → DB와 같은 개념이 되는 것이다.
그렇기 때문에 JPA는 영속이라는 의미를 사용하고, MyBatis에서는 그런 의미를 사용하지 않는 것이다.

이 내용은 hibernate 공식 문서에 있던 내용을 상의한 내용이고,
ORM을 왜 사용해야 하는지 → ORM 중 JPA는 왜 ORM을 추구 했는지 → JPA는 왜 영속이라는 의미를 사용하는지에 대한
생각의 흐름에서 나온 이야기이다. 정답이 아닐 수는 있지만, 기술을 사용함에 있어서 가장 중요한건
이 기술이 왜 탄생 했는지라고 생각한다. 그래야 그 배경에 맞게 잘 활용할 수 있기 때문이다.
그렇지 않으면, 엉뚱하게 사용하면서, 이 기술은 안좋다라는 터무니 없는 생각이 들기 때문이다.
이번 대화에서 JPA가 추구하는 철학?에 조금 더 다가간 것 같고, 이런 경험은 조금 씩 해당 기술을 올바르게 사용하는데 있어서
큰 도움이 될 것이라고 생각한다.

지금까지 너무 기술적인 부분에만 치우쳐 있었나?? 라는 생각도 조금 들게 되는 대화였다.

 

JPA를 공부하다 보면 바로 이해하기 쉽지 않은 개념들을 몇 개 마주친다. 필자는 연관관계 매핑, 영속성 전이, 고아 객체 등이 특히 어려웠다. 이때 CascadeType.REMOVE와 orphanRemoval = true가 유독 헷갈렸는데, 직접 학습 테스트를 작성하며 이해했다.

이번 글에서는 영속성 전이(REMOVE)와 고아 객체를 학습 테스트를 통해 비교하여 살펴본다. 최종적으로 독자들이 둘의 차이를 이해하는 것을 목표로 한다.

 

엔티티 기본 세팅

Team과 Member 엔티티를 바탕으로 두 개념의 공통점과 차이점을 알아본다. Team은 @OneToMany, Member는 @ManyToOne으로 양방향 매핑을 했다.

// Team.java
@Entity
public class Team {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
    private List<Member> members = new ArrayList<>();

    public Team() {
    }
}

// Member.java
@Entity
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Team team;

    public Member() {
    }
}

 

학습 테스트를 조금 더 편하게 작성하기 위해 Team에 연관관계 편의 메소드 addMember()를 추가했다.

// Team.java
@Entity
public class Team {

    public void addMember(Member member) {
        members.add(member);
        member.setTeam(this);
    }
}

 

학습 테스트 기본 세팅

학습 테스트는 @DataJpaTest로 진행했다. 테스트에 필요한 TeamRepository와 MemberRepository를 각각 DI했다.

// JpaLearningTest.java
@DataJpaTest
public class JpaLearningTest {

    @Autowired
    private TeamRepository teamRepository;

    @Autowired
    private MemberRepository memberRepository;
}

 

CascadeType.REMOVE

CascadeType.REMOVE는 부모 엔티티가 삭제되면 자식 엔티티도 삭제된다. 즉, 부모가 자식의 삭제 생명 주기를 관리한다. 만약 CascadeType.PERSIST도 함께 사용하면, 부모가 자식의 전체 생명 주기를 관리하게 된다.

한편, 이 옵션의 경우에는 부모 엔티티가 자식 엔티티와의 관계를 제거해도 자식 엔티티는 삭제되지 않고 그대로 남아있다.

학습 테스트를 위해 Team 엔티티에 영속성 전이 옵션을 추가한다.

// Team.java
@Entity
public class Team {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(
        mappedBy = "team",
        fetch = FetchType.LAZY,
        cascade = CascadeType.ALL   // { CascadeType.PERSIST, CascadeType.REMOVE }와 동일하다.
    )
    private List<Member> members = new ArrayList<>();
}

 

먼저, 부모 엔티티를 삭제하는 경우를 살펴본다.

// JpaLearningTest.java
@DisplayName("CascadeType.REMOVE - 부모 엔티티(Team)을 삭제하는 경우")
@Test
void cascadeType_Remove_InCaseOfTeamRemoval() {
    // given
    Member member1 = new Member();
    Member member2 = new Member();

    Team team = new Team();

    team.addMember(member1);
    team.addMember(member2);

    teamRepository.save(team);

    // when
    teamRepository.delete(team);

    // then
    List<Team> teams = teamRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(teams).hasSize(0);
    assertThat(members).hasSize(0);
}

 

delete 쿼리가 총 3번 나가는 걸 확인할 수 있다. 즉, Team(부모)가 삭제될 때 Member(자식)도 영속성 전이 옵션으로 인해 함께 삭제된다.

// DML
Hibernate: 
    insert 
    into
        team
        (id, name) 
    values
        (null, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)

Hibernate: 
    delete 
    from
        member 
    where
        id=?
Hibernate: 
    delete 
    from
        member 
    where
        id=?
Hibernate: 
    delete 
    from
        team 
    where
        id=?

 

다음으로, 부모 엔티티에서 자식 엔티티를 제거하는 경우를 알아본다.

// JpaLearningTest.java
@DisplayName("CascadeType.REMOVE - 부모 엔티티(Team)에서 자식 엔티티(Member)를 제거하는 경우")
@Test
void cascadeType_Remove_InCaseOfMemberRemovalFromTeam() {
    // given
    Member member1 = new Member();
    Member member2 = new Member();

    Team team = new Team();

    team.addMember(member1);
    team.addMember(member2);

    teamRepository.save(team);

    // when
    team.getMembers().remove(0);

    // then
    List<Team> teams = teamRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(teams).hasSize(1);
    assertThat(members).hasSize(2);
}

 

delete 쿼리가 전혀 나가지 않는다. 영속성 전이 삭제 옵션은 부모와 자식의 관계가 끊어졌다 해서 자식을 삭제하지 않기 때문이다.

// DML
Hibernate: 
    insert 
    into
        team
        (id, name) 
    values
        (null, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)

 

orphanRemoval = true

orphanRemoval = true 또한 부모 엔티티가 삭제되면 자식 엔티티도 삭제된다. 따라서 CascadeType.PERSIST를 함께 사용하면, 이때도 부모가 자식의 전체 생명 주기를 관리하게 된다.

한편, 이 옵션의 경우에는 부모 엔티티가 자식 엔티티의 관계를 제거하면 자식은 고아로 취급되어 그대로 사라진다.

이번에는 학습 테스트를 위해 Team 엔티티에 고아 객체 옵션을 추가한다.

// Team.java
@Entity
public class Team {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(
        mappedBy = "team",
        fetch = FetchType.LAZY,
        cascade = CascadeType.PERSIST,
        orphanRemoval = true
    )
    private List<Member> members = new ArrayList<>();
}

 

이전과 동일하게 부모 엔티티를 삭제하는 경우를 살펴본다.

// JpaLearningTest.java
@DisplayName("orphanRemoval = true - 부모 엔티티(Team)을 삭제하는 경우")
@Test
void orphanRemoval_True_InCaseOfTeamRemoval() {
    // given
    Member member1 = new Member();
    Member member2 = new Member();

    Team team = new Team();

    team.addMember(member1);
    team.addMember(member2);

    teamRepository.save(team);

    // when
    teamRepository.delete(team);

    // then
    List<Team> teams = teamRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(teams).hasSize(0);
    assertThat(members).hasSize(0);
}

 

이때도 delete 쿼리가 총 3번 나가는 걸 확인할 수 있다. 즉, Team(부모)가 삭제될 때 Member(자식)도 고아 객체 옵션으로 인해 같이 삭제된다.

// DML
Hibernate: 
    insert 
    into
        team
        (id, name) 
    values
        (null, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)

Hibernate: 
    delete 
    from
        member 
    where
        id=?
Hibernate: 
    delete 
    from
        member 
    where
        id=?
Hibernate: 
    delete 
    from
        team 
    where
        id=?

 

학습 테스트로 부모 엔티티를 삭제할 때는 CascadeType.REMOVE와 orphanRemoval = true가 동일하게 동작하는 것을 이해했다.

그렇다면, 부모 엔티티에서 자식 엔티티를 제거할 때는 어떤 결과를 나타낼까?

// JpaLearningTest.java
@DisplayName("orphanRemoval = true - 부모 엔티티(Team)에서 자식 엔티티(Member)를 제거하는 경우")
@Test
void orphanRemoval_True_InCaseOfMemberRemovalFromTeam() {
    // given
    Member member1 = new Member();
    Member member2 = new Member();

    Team team = new Team();

    team.addMember(member1);
    team.addMember(member2);

    teamRepository.save(team);

    // when
    team.getMembers().remove(0);

    // then
    List<Team> teams = teamRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(teams).hasSize(1);
    assertThat(members).hasSize(1);
}

 

이전과는 다르게 delete 쿼리가 1번 나간다. 고아 객체 옵션은 부모와 자식의 관계가 끊어지면 자식을 고아로 취급하고 자식을 삭제하기 때문이다.

// DML
Hibernate: 
    insert 
    into
        team
        (id, name) 
    values
        (null, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)
Hibernate: 
    insert 
    into
        member
        (id, name, team_id) 
    values
        (null, ?, ?)

Hibernate: 
    select
        team0_.id as id1_1_,
        team0_.name as name2_1_ 
    from
        team team0_

Hibernate: 
    delete 
    from
        member 
    where
        id=?

 

비교 결과

  • 부모 엔티티 삭제
    • CascadeType.REMOVE와 orphanRemoval = true는 부모 엔티티를 삭제하면 자식 엔티티도 삭제한다.
  • 부모 엔티티에서 자식 엔티티 제거
    • CascadeType.REMOVE는 자식 엔티티가 그대로 남아있는 반면, orphanRemoval = true는 자식 엔티티를 제거한다.

 

주의점

두 케이스 모두 자식 엔티티에 딱 하나의 부모 엔티티가 연관되어 있는 경우에만 사용해야 한다.

예를 들어 Member(자식)을 Team(부모)도 알고 Parent(부모)도 알고 있다면, CascadeType.REMOVE 또는 orphanRemoval = true를 조심할 필요가 있다. 자식 엔티티를 삭제할 상황이 아닌데도 어느 한쪽의 부모 엔티티를 삭제했거나 부모 엔티티로부터 제거됐다고 자식이 삭제되는 불상사가 일어날 수 있기 때문이다.

그러므로 @OneToMany에서 활용할 때 주의를 기울이고, @ManyToMany에서는 활용을 지양하자.

 

 

 

1. 스프링 시큐리티 정의


  스프링 시큐리티란 스프링 기반의 어플리케이션의 인증과 권한을 담당하는 프레임워크이다.
  매 요청마다 올바른 권한을 가진 사용자가 인증 절차를 확인하였는지에 대한 검증을 해준다.

  Spring Security 는 Filter 기반으로 동작하며 XML 을 따로 작성하지 않고, JAVA Bean 으로 컨트롤 할 수 있다.

 

 

https://springsource.tistory.com/80

 

위의 그림과 같이 스프링 시큐리티는 필터 기반으로 사용자의 요청을 전처리 하거나, 서버의 응답을
후처리 하는 방식으로 이루어진다.

 

 

https://springsource.tistory.com/80

 

위와 같은 Filter Chain 을 통해 각 필터에서 사용자의  Request 에 대한 권한과 인증 절차가

이루어지며, Request, Response 는 의도에 따라 전처리, 후처리가 진행된다.

위의 Filter 들에 대한 설정은 JAVA Bean 의 형태로 각 프로젝트에 맞게 Custom 을 진행할 수 있다.

 

 

 

 

 

 

2. 자동으로 설정되는 10개의 Spring Security Filter


1. SecurityContextPersistenceFilter

   : SecurityContextRepository 에서 SecurityContext 를 로딩하거나 SecurityContextRepository 로

     SecurityContext 를 저장하는 역할을 한다. SecurityContext 란 사용자의 보호 및 인증된 세션을 의미한다.

 

2. LogoutFilter

   : 로그아웃 URL로의 요쳥을 감시하며 해당 사용자를 로그아웃 시킨다.

 

3. UsernamePasswordAuthenticationFilter

   : 아이디와 비밀번호를 사용하는 폼기반 인증 요청 URL 을 감시하며 사용자를 인증하는 역할을 한다.

 

4. DefaultLoginPageGeneratingFilter

   : 폼 또는 OpenId 기반 인증을 위한 로그인폼을 감시하고 이와 관련된 로그인 폼을 생성한다.

 

5. BasicAuthenticationFilter

   : HTTP 기본 인증 헤더를 감시하여 처리한다.

 

6. RequestCacheAwareFilter

   : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용된다.

 

7. SecurityContextHolderAwareRequestFilter

   : HttpServletRequestWrapper 를 상속한 SecurityContextHolderAwareRequestWrapper 클래스로

     HttpServletRequest 정보를 감싼다. SecurityContextHolderAwareRequestWrapper 클래스는 필터

     체인 상의 다음 필터들에게 부가정보를 제공한다.

 

8. AnonymousAuthenticationFilter

   : 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증토큰에 사용자가 익명 사용자로

     나타난다.

 

9. SessionManagementFilter

   : 이 필터는 인증된 사용자와 관련된 모든 세션을 추적한다.

 

10. ExceptionTranslationFilter

   : 이 필터는 보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달하는 역할을 한다.

 

11. FilterSecurityInterceptor

   : 이 필터는 AccessDecisionManager 로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게 해준다.

 

12. AbstractAuthenticationProcessingFilter

   : 웹 기반 인증요청에서 사용되는 컴포넌트로 POST 폼 데이터를 포함하는 요청을 처리한다.

     사용자 비밀번호를 다른 필터로 전달하기 위해서 Authentiaction 객체를 생성하고 일부 프로퍼티를 설정한다.

 

13. AuthenticationManager

  : 사용자 비밀번호를 인증하는 역할을 담당한다. 인증에 실패하면 예외를 던지기도 하고, 성공하면 Authentication

    객체의 모든 프로퍼티를 완성한다. 이렇게 Authentication 객체에 채워지는 값에는 권한 정보도 포함되어 있다.

 

14. AuthenticationProvider

  : AuthenticationManager 에게 비밀번호 인증기능을 제공하는 역할을 한다. 

    데이터베이스를 참조하여 비밀번호를 인증하기도 한다.

 

728x90