스프링 시큐리티 로그인 흐름 / JPA / 영속성 컨텍스트/ 연관관계/ Spring Data JPA(항해일지 22일차)

김형준·2022년 5월 30일
0

TIL&WIL

목록 보기
22/45

1. 학습 일지


1) Spring Security 로그인 과정

  • 인증 토큰과 세션을 생성하는 과정을 대략적으로 이해만 했었는데, 내부적인 자세한 흐름은 오늘 다시 공부하며 알게됐다.
  • 6번 과정까지가 내가 알고 있던 부분이고, 7~10 과정의 흐름은 추상적으로만 알고 있었는데 위 아키텍쳐를 보고 명확해졌다.
  • Authentication Manager는 인증 토큰과 세션을 생성하고, 세션에 인증토큰을 담아 내부 메모리의 세션 저장소(SecurityContextHolder)에 저장한다.
  • 이 때 인증 토큰은 UsernamePasswordAuthenticationToken으로 Authentication을 implements한 AbstractAuthenticationToken을 상속하고있다.
  • 세션 저장소에는 키-밸류 형태로, sessionId와 value가 저장되는데 (여기에서 value는 User 객체) 이 sessionId를 클라이언트에게 보낼 Response에 담아 보낸다.
  • 세션이 만료되면 이러한 세션 정보는 삭제된다. (스프링의 기능) 또한 커스터마이징 하여 로그아웃 버튼 클릭 시 메서드를 재정의 할 수도 있다.
  • 추가적으로 JSESSIONID가 탈취당했을 때의 경우도 방어할 수 있는 코드도 공부해봐야겠다.

2) JPA 이해

  • ORM: Object-Relational Mapping이란?

    • Object: "객체"지향 언어 (자바, 파이썬)
    • Relational: "관계형" 데이터베이스 (H2, MySQL)
    • Object와 Relational을 매핑해주는(서로 알아들을 수 있도록 번역해주는) 녀석이다.
    • ORM 이 없는 환경에서는 백엔드 개발자가 비즈니스 로직 개발보다 SQL 작성에 더 많은 노력을 들여야 하기에 탄생했다.
  • JPA: Java Persistence API란?

    • 자바 ORM 기술에 대한 표준 명세 ( 자바 진영의 ORM 명세서)
  • Hibernate란??

    • JPA 는 표준 명세이고, Hibernate는 이를 실제 구현한 프레임워크 중 사실상 표준이다.
    • 스프링 부트에서 기본적으로 "하이버네이트"를 사용 중이다.
    • ** 사실상 표준 (de facto, 디팩토): 보통 기업간 치열한 경쟁을 통해 시장에서 결정되는 비 공식적 표준이다

3) JPA 영속성 컨텍스트 이해

  • 영속성 컨텍스트란? 엔티티(Entity)를 저장하고 관리하는 저장소의 개념

    • JPA
      • 객체 <-> ORM <-> DB
      • 객체 <-> 영속성 컨텍스트 매니저(entity context manager) <-> DB
    • 영속성 컨텍스트 매니저는 객체와 DB의 소통을 효율적으로 관리한다.
  • 코드로 공부하기

    • h2 DB mem 방식이 아닌 file 방식으로 만들어서 저장하기
//application.properties
spring.h2.console.enabled=true
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:./myselectdb;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
  • h2 DB 우측 상단부에서 설정해주고, 우클릭- Database Tools - Manage shown schemas - All 선택해야 테이블 정보 볼 수 있음
  • Service에서 DB의 변경 상황을 보기 위해 breakpoint를 설정할 때는 우클릭-suspend-Thread - makeDefault로 설정해준다.

JPA 영속성 컨텍스트 1차 캐시 이해

  • Entity 저장 시
    • .save() 할 경우 1차 캐시에 (@Id, Entity)의 key-value 형태로 저장되고, SQL insert문을 실행한다.
    • 단 저장되는 Entity 객체는 실제 생성해서 넣어주는 객체와는 다른 객체로, 새로 만들어진 것이다.
        Users beforeSavedUser = new Users("user1", "정국", "불족발");
        // 회원 "user1" 객체를 영속화
        Users savedUser = userRepository.save(beforeSavedUser);

        // beforeSavedUser: 영속화되기 전 상태의 자바 일반객체
        // savedUser:영속성 컨텍스트 1차 캐시에 저장된 객체
        assert(beforeSavedUser != savedUser);  // 같은 객체일 경우 에러가 발생한다. assert는 괄호 안의 내용이 참이어야함을 강력히 주장하는 메서드
  • Entity 조회 시

    • 1차 캐시에 조회하는 Id가 존재하는 경우: 1차 캐시에 저장되어있는 entity(객체)를 가져온다.

    • 실제로 디버깅하며 테스트 해봐도 insert 이후 조회 시 select문이 실행이 안되는 것을 알 수 있음.

    • 1차 캐시에 조회하는 Id가 없는 경우: select 쿼리문을 실행하여 DB에서 해당 객체를 가져온다.

  • 1차 캐시 사용의 장점

    • DB 조회 횟수를 줄인다.
    • 1차 캐시를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)
    • 만약 1차 캐시를 사용할 수 없다면 객체를 부를 때 마다 다른 주소의 객체를 부르게된다. (객체 동일성 x)
    • 1차 캐시는 항상 활성화되지 않고 스프링 내부에서만 동작한다.
  • Entity 삭제 시

  • 캐시 뿐만 아니라 DB에 저장된 객체도 삭제한다.

  • Entity 업데이트 실패 (Setter)

    • 1차 캐시의 객체를 불러와서 정보를 변경해도(Setter 사용), 변경사항이 DB에 반영되지 않는다.
    • 즉 자바 영역에서만 값이 변경된거고 DB에는 아무런 변화가 일어나지 않는다.
    public Users updateUserFail() {
        // 회원 "user1" 객체 추가
        Users user = new Users("user1", "뷔", "콜라");
        // 회원 "user1" 객체를 영속화
        Users savedUser = userRepository.save(user);

        // 회원의 nickname 변경
        savedUser.setNickname("얼굴천재");
        // 회원의 favoriteFood 변경
        savedUser.setFavoriteFood("버거킹");

        // 회원 "user1" 을 조회
        Users foundUser = userRepository.findById("user1").orElse(null);
        // 중요!) foundUser 는 DB 값이 아닌 1차 캐시에서 가져오는 값
        assert(foundUser == savedUser);
        assert(foundUser.getUsername().equals(savedUser.getUsername()));
        assert(foundUser.getNickname().equals(savedUser.getNickname()));
        assert(foundUser.getFavoriteFood().equals(savedUser.getFavoriteFood()));

        return foundUser;
    }
  • Entity 업데이트 방법
    • 1) userRepository.save()
    • JPA는 save()할 때, DB에 해당 객체가 없다면 insert 쿼리를 실행하며, 있다면 update 쿼리를 실행한다.
    • 2) @Transactional을 추가
      • Setter까지만 해줘도 @Transactional이 알아서 업데이트 해준다.
      • @Transactional이 붙은 메서드 내부의 SQL 쿼리들은 모두 하나의 작업 단위로 엮이며, 모아두었다가 함수가 끝나는 시점에 한번에 처리된다.
      • 이를 "Dirty check" 라고 한다.
      • 추가 학습 키워드: 쓰기 지연 SQL 저장소, flush, commit 등
      • 아래의 경우 save()를 해도 바로 DB에 반영이 되지 않는 것을 알 수 있다.
    @Transactional
    public Users updateUser2() {
        // 테스트 회원 "user1" 생성
        // 회원 "user1" 객체 추가
        Users user = new Users("user1", "진", "꽃등심");
        // 회원 "user1" 객체를 영속화
        Users savedUser = userRepository.save(user);

        // 회원의 nickname 변경
        savedUser.setNickname("월드와이드핸섬 진");
        // 회원의 favoriteFood 변경
        savedUser.setFavoriteFood("까르보나라");

        return savedUser;
    }

4) JPA 연관관계

  • DB에도 연관관계(association)을 설정하듯, JPA에도 연관관계를 설정할 수 있다.

  • JPA 의 경우는 Enitity 클래스의 필드 위에 연관관계 어노테이션 (@) 을 설정해 주는 것만으로 연관관계가 형성된다.

  • 일대다(@OneToMany) / 다대일(ManyToOne) / 일대일(OneToOne) / 다대다(ManyToMany)

@Enitity
public class Order {
    @OneToMany
    private List<Food> foods;

		@OneToOne
		private Coupon coupon;
}

@Entity
public class Owner {
	@ManyToOne
	Restaurant restaurant;
}

@Entity
public class User {
	@ManyToMany
	List<Restaurant> likeRestaurants;
}

5) Spring Data JPA 이해

  • Spring Data JPA란?
    • JPA 를 편리하게 사용하기 위해, 스프링에서 JPA 를 Wrapping한 것
    • 스프링 개발자들이 JPA 를 사용할 때 필수적으로 생성해야 하나, 예상 가능하고 반복적인 코드들 → Spring Data JPA 가 대신 작성해준다.
    • Repostiory 인터페이스만 작성하면, 필요한 구현은 스프링이 대신 알아서 해준다!
    • Spring Data JPA) 기본 제공해 주는 기능
// 1. 상품 생성
Product product = new Product(...);
productRepository.save(product);

// 2. 상품 전체 조회
List<Product> products = productRepository.findAll();

// 3. 상품 전체 개수 조회
long count = productRepository.count();

// 4. 상품 삭제
productRepository.delete(product);
  • 타고 타고 들어가보면 구현부를 볼 수 있다.
	@Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity); // entityManager(영속성 컨텍스트 매니저)
			return entity;
		} else {
			return em.merge(entity);
		}
	}
  • ID 외의 필드에 대한 추가 기능은 interface 만 선언해 주면, 구현은 Spring Data JPA 가 대신해준다.
  • Spring data JPA 가 제공해주는 QueryMethod라고 한다.
  • 아주 고맙게도 ctrl + space를 치면 추천어들이 뜬다.
public interface ProductRepository extends JpaRepository<Product, Long> {
    // (1) 회원 ID 로 등록된 상품들 조회
    List<Product> findAllByUserId(Long userId);

    // (2) 상품명이 title 인 관심상품 1개 조회
		Product findByTitle(String title);

		// (3) 상품명에 word 가 포함된 모든 상품들 조회
		List<Product> findAllByTitleContaining(String word);

		// (4) 최저가가 fromPrice ~ toPrice 인 모든 상품들을 조회
    List<Product> findAllByLpriceBetween(int fromPrice, int toPrice);
}

2. 코멘트

  • 오늘은 팀과제를 수행하며 어제 구현했던 스프링 시큐리티의 로그인 흐름도를 그려봤다.
  • 구현하는 과정 상의 표면적 흐름은 알고있다고 생각했는데, 막상 백지 상태의 흐름도를 완성하려니 꽤나 힘들었다.
  • 아직은 익숙해지는 단계이니 너무 조급해는 말자, 정확하게 어떻게 작동하는지를 파악하는 데 초점을 두자.
  • 그 외에 JPA에 대해 공부 중인데, 영속성 컨텍스트의 1차 캐시 부분을 직접 디버깅 돌리며 확인해보니 그동안 의문이었던 @Transactional을 붙였던 이유와, 이미 생성한 객체를 save하면 update 쿼리를 실행할까 라는 의문이 모두 해결되었다.
  • 어림짐작했던 부분들을 강사님이 명확하게 말해주니 너무 좋았다.😁
  • 오늘도 고생 많았고, 내일도 힘내보자! 화이팅!!
profile
BackEnd Developer

0개의 댓글