콘텐츠로 이동

2022 08 16

2022-08-16

Spring Security & Transaction

  • 참고: https://velog.io/@jakeseo_me/JPA-%EC%82%AC%EC%9A%A9%ED%95%A0-%EB%95%8C-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD-%EC%A0%95%EB%A6%AC
  • 참고: https://ppomelo.tistory.com/147
  • 문제 상황
    • Security 단에서 Optional<User>를 findKlaytnAddress()로 트랜잭션을 열고 가져오게 되는데...
    • 로그를 보아하니 가져는 오는데, 변경 감지를 하고 얘를 트랜잭션 끝나는 시점에 flush를 해주면했는데... 안한다?
    • 어허... 엔티티 가져와서 넣어두면 변경 감지 해줘야하는거 국룰 아니야?
    • @Transactional도 붙여뒀는데 서비스 단에서... Controller로 @AuthenticationPrincipal 받아서 했는데 말이지? 엔티티 아닌가 이거?
      2022-08-10 20:24:58.324[TRACE] : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByKlaytnAddress]: This method is not transactional.
      Hibernate: 
          select
              user0_.id as id1_5_,
              user0_.is_active as is_activ2_5_,
              user0_.klaytn_address as klaytn_a3_5_,
              user0_.nickname as nickname4_5_,
              user0_.phone_number as phone_nu5_5_,
              user0_.privacy_agreement as privacy_6_5_ 
          from
              user user0_ 
          where
              user0_.klaytn_address=?
      2022-08-10 20:24:58.342[TRACE] : binding parameter [1] as [VARCHAR] - [0x9f3c619b6f534d123c8fc35607d73eee0161da7b]
      2022-08-10 20:24:58.418[TRACE] : Getting transaction for [com.backend.connectable.user.service.UserService.modifyUserByUserDetails]
      2022-08-10 20:24:58.419[TRACE] : Completing transaction for [com.backend.connectable.user.service.UserService.modifyUserByUserDetails]
      
  • 문제점
    • Spring Security Context에 JPA엔티티를 들고 다니지 말 것! => 준영속 상태로 이어짐
      • JPA 엔티티를 SecurityContext 내부에 있는 .contextHolder()에 들고 다님
      • .contextHolder()에 있는 JPA 엔티티는 아무리 수정해도, 디비나 영속성 컨텍스트에 반영이 되지 않음!
      • 영속성 컨텍스트에 있는 JPA 엔티티를 변화시켜도 SecurityContext.contextHolder()에 있는 JPA 엔티티는 영속상태가 아니기에 변화가 적용되지 않음!
      • 따라서 그냥 ID와 같은 문자열 정보만 저장하는게 좋아보임
  • 준영속 상태
    • 준영속 상태란?
      • 영속 상태의 엔티티가 영속성 컨텍스트에서 "분리" 된 것
      • 영속성 컨텍스트에 저장되었다가 분리된 상태로, 현재는 영속 상태가 아닌 상태
      • 영속성 컨텍스트가 제공하는 기능을 사용하지 못 함 => 1차 캐시, 변경 감지 등
    • 준영속 상태로 만드는 방법
      • em.detach(entity) : 특정 엔티티 준영속으로 전환
      • em.clear() : 영속성 컨텍스트 초기화
      • em.close() : 영속성 컨텍스트 종료
    • 준영속 엔티티를 수정하는 2가지 방법
      • 변경 감지 기능 사용
        • 다시 엔티티 조회하고, 데이터 수정
      • 병합 사용
        • em.merge(entity)
    • 준영속 상태는 한번 영속화되었다가 트랜잭션 끝나서 영속성 컨텍스트가 더는 관리하지 않는 엔티티
  • 왜 Spring Security를 거치면 준영속 상태가 되는거야?
    • 필터를 지나 mvc로 넘어가면 엔티티 매니저가 닫혀버리나?

엔티티의 생명주기

  • 해결 방법 at 서비스단
    1. 엔티티 한번 더 불러오기 (준영속 => 영속)
    2. Entity 강제 초기화 (필요한 정보 UserDetails에서 다 끌고오기)
    3. FetchType.EAGER
  • 그런데 말입니다
    • Controller에서 준영속 상태인 entity가 지연로딩 불가한 상황
    • 영속성 컨텍스트 Controller 살아있도록 하면 되는거 아닌가?
    • 그런데 놀랍게도 OSIV는 true임...!
  • OSIV를 알아보기

    • Spring Boot에서 자동으로 Bean 설정해주는 Configuration
      • OpenEntityManagerInViewIinterceptor를 인터셉터로 등록하여 OSIV 등록 함
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnWebApplication(type = Type.SERVLET)
        @ConditionalOnClass(WebMvcConfigurer.class)
        @ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class })
        @ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class)
        @ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true)
        protected static class JpaWebConfiguration {
            ...
        
            @Bean
            public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
                ...
                return new OpenEntityManagerInViewInterceptor();
            }
        
            @Bean
            public WebMvcConfigurer openEntityManagerInViewInterceptorConfigurer(OpenEntityManagerInViewInterceptor interceptor) {
                return new WebMvcConfigurer() {
                    @Override
                    public void addInterceptors(InterceptorRegistry registry) {
                        registry.addWebRequestInterceptor(interceptor);
                    }
                };
            }
        }
        
    • Spring에서 웹 요청 처리과정에서는 Filter를 제외한 대부분의 영역에서 @Transactional없어도 EntityManager의 관리를 받게 된다
      • 왜냐하면 OSIV는 디폴트 true이기 때문!
  • Spring Security는 어떤 방식으로 유저 정보 가져오는데?
    • Spring Security는 Filter 기반으로 동작
    • DelegatingFilterProxy에서 사용자 요청 가로채 보안이 적용되게끔 함
      • SecurityFilter가 존재해 일종의 FilterChain 이루어 동작
    • CustomUserDetailsService => UserDetailsService를 구현
  • 자 여기서!
    • Spring Security에서 Filter단에서 User 정보 가져옴
    • OSIV는 Interceptor 단에서 부터 적용됨
    • Filter가 Interceptor 보다 먼저 실행되기에, 영속성 컨텍스트는 CustomUserDetailsService에서만 유지됨
      • 이러니까 준영속 상태로 엔티티가 Spring MVC에 도착하는 것!
  • 해결 방법
    1. OpenEntityManagerInterceptor의 우선 순위 높이기
      • 이걸 Interceptor가 아니라 Filter로 교체하고, Bean으로 등록해서 뚝딱해결
      • 근데 과연 OSIV를 Filter단까지 올리는게 좋은 방법일까?

Transaction ReadOnly

  • 언제 트랜잭션 쓰지?
    • InnoDB 엔진에서는 모든 statement가 transaction
      • 따라서 locking/snapshot 등을 포함할 수 있음
  • 어플리케이션 vs DB
    • persistence layer를 다루려면 많은 추상화된 layer를 포함해야 함
    • 각 레이어는 제각기 다른 책임이 있음
    • DAO => Transcational abstraction => JPA abstraction => ORM Framework => JDBC Driver => RDBMS
  • 트랜잭션 관리
    • autocommit 속성을 꺼두고 JDBC 드라이버가 트랜잭션 시작함
    • BEGIN TRANSACTION statment와 동일
      • commit/rollback 둘중하나는 해야해
    • 주의할 점
      • Connection pool을 사용하는 경우, connection 열고 재사용함
      • 이런 이유로, 몇몇 statement는 pending changes를 버리는데 사용될 수 있음
    • autocommit
      • 트랜잭션이 하나의 쿼리만 실행시킨다면, autocommit=true 가 round-trips를 예방
      • 트랜잭션에 여러개의 쿼리가 있다면, 명시적으로 read-only 트랜잭션을 써야함
        • 이렇게 해야 write<->read-only 왔다갔다 하는 round trip을 피할 수 있고,
  • 영속성 컨텍스트
    • 여기서 관리되면 1차 캐시부터 변경 감지까지 얻을 수 있는 헤택이 많음
    • 변경 감지를 위해 스냅샷 인스턴스를 보관, 더 많은 메모리를 사용함.
  • 읽기 전용으로 엔티티 조회하기
    1. 스칼라 타입으로 조회하기
      • 스칼라 타입으로 필요한 필드만을 조회하는 방식
      • 엔티티 객체가 아니니, 영속성 컨텍스트가 결과를 관리하지 않음
    2. 읽기 전용 쿼리 힌트 사용
      • 하이버네이트 전용 힌트 org.hibernate.readOnly 사용시, 엔티티를 읽기 전용으로 조회 가능
    3. 읽기 전용 트랜잭션 사용
      • @Transactional(readOnly=true)
      • 스프링 프레임워크가 하이버네이트 세션 플러시 모드를 MANUAL로 설정함
      • 이렇게 하면, 강제 플러쉬 호출하지 않는 한 플러시 안 일어남
      • 트랜잭션 커밋하더라도 영속성 컨텍스트는 플러쉬되지 않아 엔티티 등록/수정/삭제 동작 x
      • 읽기 전용으로, 영속성 컨텍스트는 변경 감지를 위한 스냅샷 보관하지 않으므로 성능 향상