SecurityContext에 저장, SecurityContextHolder를 통해 SecurityContext에 접근
SecurityContext 통해 Authentication에 접근 가능
Public interface Authentication extends Principal, Serializable{
// 현재 사용자의 권한 목록 가져옴
Collection<? extends GrantedAuthority> getAuthroties();
// credentials (비밀번호)를 가져옴
Object getCredentials();
Object getDetails();
// Principal 객체 가져옴
Object getPrincipal();
// 인증 여부
boolean isAuthenticated();
// 인증 여부 설정
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
UsernamePasswordAuthenticationToken
Authentication을 Implements한 AbstractAuthenticationToken의 하위 클래스
User의 ID가 Principal 역할, Password가 Credential의 역할
첫 번째 생성자는 인증 전의 객체 생성, 두번째는 인증이 완료된 객체 생성
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer{
}
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// 주로 사용자의 ID에 해당
private final Object principal;
// 주로 사용자의 Password에 해당
private Object credentials;
// 인증 완료 전의 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Ojbect credentials){
super(null);
this.principal = principal;
this.credentials = credentails;
setAuthenticated(false);
}
// 인증 완료 후 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extendes GrantedAuthority> authrorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // super 로 사용해야 됨
}
}
AuthenticationManager
인증에 대한 부분은 여기서 처리
실질적으로는 AuthenticationManager에 등록 된 AuthenticationProvider에 의해 처리
인증 성공하면 두번째 생성자를 이용해 객체 생성 >> SecurityContext에 저장
public interface AuthenticationManager {
Authentication authentication(Authentication authenticationi) throws AuthenticationException;
}
AuthenticationProvider
실제 인증에 대한 부분 처리
인증 전의 Authentication 객체를 받아 인증이 완료된 객체를 리턴하는 역할
아래와 같은 인터페이스를 구현하여 커스텀한 AuthenticationProvider 작성하고 AuthenticationManager에 등록하면 됨
@Entity
public class User {
@Id
@GeneratedValue
private long id;
private String firstName;
private String lastName;
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
@JoinColumn(name = "team_id", nullable = false)
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
private long id;
private String name;
@OneToMany(fetch = FetchType.EAGER)
private List<User> users = new ArrayList<>();
}
@ManyToOne 어노테이션을 활용하여 연관관계 매핑 시, fetch 속성을 FetchType.EAGER (즉시 로딩)으로 설정한 경우
JpaRepository에서 findAll 호출하게 되면
Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
위와 같이 TEAM을 전체 조회하는 쿼리가 발생하고 그 TEAM에 속한 USER를 조회하는 쿼리들이 파생되어 발생
TEAM이 여러개가 된다면? 조회되는 모든 TEAM에 대해 연관된 USER를 하나하나 조회하는 쿼리가 발생
불필요한 쿼리 증가
FetchType.LAZY
FetchType.EAGER를 FetchType.LAZY로 변경하게 되면
Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_
N+1 문제가 발생하지 않는 점 확인 가능
But
Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
Hibernate: select users0_.team_id as team_id1_1_0_, users0_.users_id as users_id2_1_0_, user1_.id as id1_2_1_, user1_.first_name as first_na2_2_1_, user1_.last_name as last_nam3_2_1_, user1_.team_id as team_id4_2_1_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id=?
USER를 탐색할 때 N+1문제가 발생
EAGER와 LAZY는 N+1문제가 발생되는 시점만 다름
Why?
우선 FetcyType.EAGER 일 때의 동작 과정을 살펴보면
1. findAll() 호출 시
select t from Team t 라는 jpql 실행, 해당 구문을 분석한 select * from team 이라는 SQL 생성되어 실행
(Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_)
2. DB의 결과를 받아 team의 Entity 인스턴스 생성
3. team과 연결되어 있는 user도 로딩 필요 >> 영속성 컨텍스트에서 연관된 user가 존재하는 확인
4. 없다면 2에서 만들어진 인스턴스 개수에 맞게 select * from user where team_id=? 라는 SQL생성 후 실행
FetchType.LAZY 라면
3, 4번 과정이 전체 객체 조회 시 발생하는 것이 아닌 user 객체를 사용하는 시점에서 발생
** user 객체를 활용하고 싶다 >> user도 로딩이 필요 >> 3, 4번 과정 반복 **
해결 방법
Fetch Join
jpql을 사용해 DB에서 데이터를 가져올 때 처음부터 연관된 데이터까지 함께 가져오는 방법
(SQL의 Join구문)
@Query 어노테이션을 활용하여 직접 jpql 작성 필요
@Query("select t from Team t join fetch t.users")
findAll() 호출 시
Hibernate: select team0_.id as id1_0_0_, user2_.id as id1_2_1_, team0_.name as name2_0_0_, user2_.first_name as first_na2_2_1_, user2_.last_name as last_nam3_2_1_, user2_.team_id as team_id4_2_1_, users1_.team_id as team_id1_1_0__, users1_.users_id as users_id2_1_0__ from team team0_ inner join team_users users1_ on team0_.id=users1_.team_id inner join user user2_ on users1_.users_id=user2_.id
Hibernate: select team0_.id as id1_0_, team0_.name as name2_0_ from team team0_
Hibernate: select users0_.team_id as team_id1_1_1_, users0_.users_id as users_id2_1_1_, user1_.id as id1_2_0_, user1_.first_name as first_na2_2_0_, user1_.last_name as last_nam3_2_0_, user1_.team_id as team_id4_2_0_ from team_users users0_ inner join user user1_ on users0_.users_id=user1_.id where users0_.team_id in (?, ?, ?, ?)
위와 같이 in 을 사용한 쿼리 발생
실무에서?
우선 연관관계애 대한 설정이 필요하다면 FetchType을 LAZY로 사용하고 최적화 필요한 부분에 대해서 Fetch Join 사용
기본적인 Batch Size의 값을 1000 이하로 설정 (대부분 DB에서 In절의 최대 개수가 1000개이기 때문)