스프링과 JPA 기반 웹 애플리케이션 개발 #27 Open EntityManager (또는 Session) In View 필터 및 설계 수정

스프링과 JPA 기반 웹 애플리케이션 개발 #27 Open EntityManager (또는 Session) In View 필터 및 설계 수정

해당 내용은 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발의 강의 내용을 바탕으로 작성된 내용입니다.

강의를 학습하며 요약한 내용을 출처를 표기하고 블로깅 또는 문서로 공개하는 것을 허용합니다 라는 원칙 하에 요약 내용을 공개합니다. 출처는 위에 언급되어있듯, 인프런, 스프링과 JPA 기반 웹 애플리케이션 개발입니다.

제가 학습한 소스코드는 https://github.com/n00nietzsche/jakestudy_webapp 에 지속적으로 업로드 됩니다. 매 커밋 메세지에 강의의 어디 부분까지 진행됐는지 기록해놓겠습니다.


OSIV란?

OSIV에 대한 추가 정리 (저작권 때문에 비공개)
OSIV 정리 링크 (매우 추천하는 자료)

Open EntityManager (혹은 Session) In View 필터

  • 해당 필터는 JPA EntityManager(영속성 컨텍스트)를 요청을 처리하는 전체 프로세스에 바인딩 시켜주는 필터이다.
    • 뷰를 렌더링할 때까지 영속성 컨텍스트를 유지하기 때문에 필요한 데이터를 렌더링하는 시점에 추가로 읽어올 수 있다. (지연 로딩, Lazy Loading)
    • 엔티티 객체 변경은 반드시 트랜잭션 내부에서 해야 한다.
      • 그래야 트랜잭션 종료 직전 또는 필요 시점에 변경사항을 DB에 반영할 수 있다.
  • 현재 일어난 버그
    • 컨트롤러에서 데이터를 변경했는데, DB에 반영되지 않았다.
      • 트랜잭션 범위 밖에서 일어난 일이기 때문이다.

@Transactional

사실 강의의 해당 부분에서 수정한 코드의 내용은 그다지 OSIV와는 관련 없는 내용이다. (왜냐하면 Lazy Loading 등의 문제는 전혀 만나지 않았으며, 단순히 @Transactional 영역이 아니어서 DB에 커밋만 안 된 것이기 때문이다.) 다만, OSIV 필터와 관련은 있는데, 내 생각엔 JPA에서 엔티티 객체의 사용법과 관련이 깊다. 그리고 무엇보다 @Transactional 애노테이션과 관련이 깊다.

@Transactional은 영속성 엔티티를 가져왔을 때, 엔티티의 생성이나 변경사항이 DB에 반영되기 위해 반드시 필요한 애노테이션이다. 사실 영속성 엔티티를 가져올 때는 일반적으로 xxxRepository.findXxx()와 같은 메소드를 통해 가져온다.

Spring Data JPA가 제공하는 SimpleJpaRepository에도 @Transactional이 붙어있다. 클래스 레벨에는 @Transactional(readOnly = true)가 붙어있고, 변화가 일어나는 메소드에는 @Transactional이 따로 붙어있다.

@Transactional이 끊기면?

.findXxx() 메소드를 통해 영속성 컨텍스트에서 가져온 엔티티는 @Transactional이 끊기는 순간 detached 된다.

그런데 이러한 개념에 대한 이해가 없으면, 나중에 매우 헷갈리는 버그를 만날 수 있다. 내 생각엔 그러한 버그가 탄생하기 쉬운 지점이 SecurityContextAccount 객체를 넣어놓고 사용하는 부분인데, 이 Account 객체는 SecurityContext에 넣어진 뒤에 해당 메소드가 끝나면 SecurityContext에 있는 Account 객체는 detached 된다.

위 소스코드에서 Account 객체는 UserAccount 라는 클래스의 생성자에 전달되고 .setAuthentication() 메소드를 통해 SecurityContext에 담겨진다.

개발자가 SecurityContext 내부에 있는 Account 객체의 영속 상태가 detached라는 이해가 없다면, SecurityContext 내부 Account 객체를 꺼내서 .setXxx()등의 메소드를 수행했을 때, 해당 내용이 데이터베이스에 반영되지 않을 거란 생각을 하기 어렵다.

또한, 해당 Account 객체에 대한 내용 변경이 있은 후에도 당연히 SecurityContext에 있는 Account 객체에도 내용 변경이 적용되었을거라 생각할 수 있다. 하지만, detached 상태의 Account 객체는 당연히 이전 상태를 유지하고 있다.

물론, 매번 변경이 발생할 때마다 SecurityContext 내부에 있는 Account 객체도 업데이트하면 상관없지만 깜빡하고 까먹기 쉽고 시스템의 복잡도가 증대되기 때문에 내 생각에는 id, email과 같이 한번 정하면 변하지 않는 요소만 SecurityContextprincipal에 넣어놓는 것이 좋다고 생각한다.

해당 id를 넣어놓고 해당 idAccount 객체 정보가 필요하다면, 매번 accountRepository.findById() 등과 같은 메소드를 통해 가져오는 것이 안정적인 설계라고 생각한다.

설계 변경

UserAccount

@Getter
public class UserAccount extends User {

    private final Long id;

    public UserAccount(Account account) {
        super(account.getNickname(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER")));
        this.id = account.getId();
    }
}

UserAccount에서는 오직 사용자의 id만 기록한다.

CurrentUserAccount 애노테이션

@Retention(RetentionPolicy.RUNTIME) // RUNTIME까지 유지되는 애노테이션
@Target(ElementType.PARAMETER) // 파라미터 타입으로 들어가는 애노테이션
// 인증정보가 없을 때는 스프링 시큐리티에서 자동으로 `anonymousUser` 라는 문자열을 principal 에 설정하는 특성을 이용했다.
@AuthenticationPrincipal
public @interface CurrentUserAccount {
}

@AuthenticationPrincipal뒤에 표현식을 없애주었다.

AccountController 소스 변경

@CurrentUserAccount 애노테이션이 쓰인 부분의 코드를 전부 변경해주었다. accountRepository.findById() 메소드를 통해 해당 Account 객체를 찾는 방식으로 전부 변경하였다.

좋은 웹페이지 즐겨찾기