Spring AOP는 프록시 패턴이을 사용해서 AOP 효과를 낸다.
@Transactional
@Transactional 애노테이션이 붙어있으면 OwnerRepository 타입의 프록시가 새로 만들어지고 Spring AOP에 의해 자동으로 생성되는 OwnerRepository의 프록시에는 @Transactional 애노테이션이 지시하는 코드가 삽입
JDBC에서 트랜잭션 처리를 하려면 SQL 실행문 앞뒤에 setAutoCommit()와 commit()/rollback() 코드가 항상 붙는데 @Transactional 애노테이션은 프록시에 자동으로 그 코드를 넣어서 반복, 중복되는 코드를 생략
그외에도 로깅같은 기능을 정의하여 사용 가능
객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.
DI(의존성 주입)를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.
@Autowired
private BService Bservice;
private BService bService;
@Autowired
public void setBService(BService bService) {
this.bService = bService;
}
@Controller
public class BController {
//3. 생성자주입
private final BService bService;
@Autowired
public BController(BService bService) {
this.bService = bService;
}
}
무엇이 다른가?
3가지 방식에는 빈을 주입하는 순서가 다르다.
Field Injection은
주입받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록
생성자 인자에 사용하는 빈을 찾거나 생성
필드에 주입
Setter based Injection은
주입받으려는 빈의 생성자를 호출하여 빈을 찾거나 빈 팩토리에 등록
생성자 인자에 사용하는 빈을 찾거나 생성
주입하려는 빈 객체의 수정자를 호출하여 주입
위에 2가지 방식은 런타임에서 의존성을 주입하기 때문에 의존성을 주입하지 않아도 객체가 생성될 수 있다.
Constructor based Injection은
1. 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 생성
찾은 인자 빈으로 주입하려는 생성자를 호출
무엇을 사용하는 것이 좋을까?
스프링에서는 현재 생성자 주입 방식을 권고하고 있다.
반면, Constructor based Injection으로 통해 실행해보면 BeanCurrentlyInCreationException이 발생한다.
순환 참조뿐만 아니라 의존 관계에 내용을 외부로 노출시킴으로써 애플리케이션을 실행하는 시점에서 오류를 체크할 수 있다.
IOC 는 제어의 역전으로 인스턴스의 생성부터 소멸까지 개발자가 아닌 컨테이너가 대신 관리해주는 것을 말한다.
IOC 컨테이너는 DI를 통해 주입시킨다.
스프링이 모든 의존성 객체를 스프링이 실행될때 다 만들어주고 필요한곳에 주입시켜줌으로써 Bean들은 싱글턴 패턴의 특징을 가지며,
제어의 흐름을 사용자가 컨트롤 하는 것이 아니라 스프링에게 맡겨 작업을 처리
Spring IoC 컨테이너가 관리하는 자바 객체 - bean
실행되는 시점에서 차이가 있다. 필터는 dispatcherServlet으로 요청이 가기전에 실행되고 인터셉터는 Controller로 요청이 가기전에 실행된다.
따라서 컨트롤러에 들어가기 전 작업을 처리하기 위해 사용하는 공통점이 있지만, 호출되는 시점에서 차이가 존재한다.
둘다 빈등록은 된다
DTO(Data Transfer Object)의 핵심 관심사는 이름 그대로 데이터의 전달,
다른 계층(Layer) 또는 다른 컴포넌트들로 데이터를 넘겨주기 위한 자료구조
Domain 사용자가 이용하는 앱의 기능, 회사의 비즈니스 로직을 정의하고 있는 영역
Entity 클래스는 실제 데이터베이스의 테이블과 1:1로 매핑되는 클래스로 DB의 테이블내에 존재하는 컬럼만을 속성으로 가져야한다.
엔티티를 작성할 때 Setter를 무분별하게 사용하면 객체(Entity)의 값을 변경할 수 있으므로 객체의 일관성을 보장할 수 없다.
단 setter 한 대상이 다른 곳에서 변경될일이 절대 없다면 사용해도 무방
(명칭의 문제)
클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 한다.
어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다.
첫번째 차이점은 String은 불변하다는 특징을 가지고 있어서 수정을 하지못하고 변경시 새로운 String 인스턴스가 생성되고 전에 있던 String은 GC에 의해 사라지게 된다.
그래서 좋은 성능을 기대하기는 힘들다. (String 불변, StringBuffer, StringBuilder 가변)
StringBuffer/StringBuilder 는 가변성 가지기 때문에 .append() .delete() 등의 API를 이용하여 동일 객체내에서 문자열을 변경하는 것이 가능합니다. 따라서 문자열의 추가,수정,삭제가 빈번하게 발생할 경우라면 String 클래스가 아닌 StringBuffer/StringBuilder를 사용
반대로 StringBuilder는 동기화를 지원하지 않기때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 만큼 단일쓰레드에서의 성능은 StringBuffer 보다 뛰어남
멀티프로세스
여러 개의 자식 프로세스 중 하나에 문제가 발생하면 그 자식 프로세스만 죽고 다른 프로세스에는 영향이 확산되지 않는다.
프로세스는 운영체제의 메모리 자원을 받는 독립적인 객체이며 스레드는 프로세스 내에서 스택 영역을 제외한 코드, 데이터, 힙 영역의 메모리를 공유하는 실행의 단위, 프로세스는 독립적인 객체이기 때문에 스레드처럼 메모리에 직접적으로 접근하는 것은 불가
커널 영역에서 IPC라는 내부 프로세스간 통신을 제공하게 되고, 프로세스는 커널이 제공하는 IPC를 이용해서 데이터를 공유( ipc = 메시지 큐 , 세마포어 , 소켓 , 공유 메모리)
멀티 쓰레드
MVC 패턴은 Model, View, Controller 이 3가지로 나뉘어 역할을 분할하여 처리한다.
역할을 나누어 처리하기 때문에 서로의 결합도가 낮아져서 좋은 코드가 되며 유지보수도 하기 편해진다.
영속성 컨텍스트란 엔티티를 영구 저장하는 환경을 의미 한다.
애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할
영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
영속성 컨텍스트와 데이터베이스 저장
JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터 베이스에 반영하는데 이를 flush라 한다.
영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 장점이 있다.
1차 캐시 => 디비 접속 최소화(이미 컨텍스트에 있는 경우 조회 X)
동일성 보장 => 같은 정보의 데이터의 인스턴스는 동일한 인스턴스
트랙잭션을 지원하는 쓰기 지연 => 트랜잭션이 끝날때 까지 내부 쿼리 저장소에 모아 뒀다가 한번에 전송
변경 감지 :트랙잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시가 호출된다.
지연 로딩
연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 된다. 이를 N+1 문제라고 한다.
원인
그렇다면 왜 문제가 발생하는 것일까요? JPQL을 실행하면 JPA는 이것을 분석해서 SQL을 생성합니다. JPQL 입장에서는 즉시 로딩, 지연 로딩과 같은 글로벌 패치 전략을 무시하고 JPQL만 사용해서 SQL을 생성합니다.
JPQL 특징이 있습니다.. findById() 같은 경우에는 엔티티를 영속성 컨텍스트에서 먼저 찾고 영속성 컨텍스트에 없는 경우에 데이터베이스에 찾는 반면 JPQL은 항상 데이터베이스에 SQL을 실행해서 결과를 조회합니다. 그리고 아래와 같은 작업을 진행하게 됩니다.
JPQL의 동작 순서는 위와 같고 그렇다면 왜 N+1이 발생하는 것일까요? JPQL에서는 글로벌 패치 전략을 완전히 무시하고 SQL을 생성합니다. findAll()메서드를 호출하게 되면 아래와 같은 SQL이 실행됩니다.
select *from member // findAll()의 SQL
members = memberRepository.findAll()
JPQL에서 동작한 쿼리를 통해서 members에 데이터가 바인딩 됩니다. 그 이후 JPA에서는 글로벌 패치 전략(즉시 로딩)을 받아들여 해당 member 대해서 추가적인 레이지 로딩으로 N+1을 발생시킵니다.
동일하게 members에 데이터가 바인 딩되지만 JPA가 글로벌 패치 전략을 받아들이지만 지연 로딩이기 때문에 추가적인 SQL을 발생시키지 않습니다. 하지만 위에서 본 예제처럼 레이지로 딩으로 추가적인 작업을 진행하게되면 결국 N+1 문제가 발생하게 됩니다.
일반 Join
Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는
오직 JPQL에서 조회하는 주체가 되는 Entity만 조회하여 영속화
(조인 대상은 영속화 x , 억지로 하자면 조인 대상도 select 해야함)
조회의 주체가 되는 Entity만 SELECT 해서 영속화하기 때문에 데이터는 필요하지 않지만 연관 Entity가 검색조건에는 필요한 경우에 주로 사용됨
Fetch Join
어떻게 보면 무조건 Fetch Join이 좋아 보이기도 합니다. 하지만 일반 Join이 쓰임새도 분명 있습니다.
JPA는 기본적으로 "DB ↔ 객체" 의 일관성을 잘 고려해서 사용해야 하기 때문에
로직에 꼭 필요한 Entity만을 영속성 컨텍스트에 담아놓고 사용해야 합니다.
그러니 무작정 Fetch Join을 사용해서 전부 영속성 컨텍스트에 올려서 쓰기보다는
일반 Join을 적절히 이용하여 필요한 Entity만 영속성 컨텍스트에 올려서 사용하는 것이 괜한 오작동을 미리 방지할 수 있는 방법
패치조인 실행결과
[
Team(
id=1,
name=team1,
members=[
Member(
id=1,
name=team1member1,
age=1
),
Member(
id=2,
name=team2member2,
age=2
),
Member(
id=3,
name=team3member3,
age=3
)
]
),
Team(
id=2,
name=team2,
members=[
Member(
id=4,
name=team2member4,
age=4
),
Member(
id=5,
name=team2member5,
age=5
)
]
)
]
QueryDsl을 사용하면 컴파일 타임에 오류를 잡을 수 있고, 동적 쿼리를 쉽게 작성할 수 있다.
원하는 필드만 뽑아서 DTO로 만드는 기능도 지원한다.
순환참조가 발생되면 컴포넌트 간의 명확한 경계가 사라지고 연쇄적으로 변경에 의한 영향이 발생할 수 있다. 이로 인하여 개발과 유지보수 속도에 영향을 끼치고 예상치 못한 문제점을 만들어 낼 가능성도 높다. 이 후에 컴포넌트들을 분리해내도 어려워진다. 컴포넌트를 분리해내기 어렵다면 단기적으로 테스트하기가 어려워질 것이고, 장기적으로 협업하기 어려워지고 DDD나 MSA와 같은 개발 아키텍처를 구성할 수 없게 된다.
=> 해결
DTO 객체를 만들어서 반환하는 것 -> 추천!
나는 처음에 첫 번째 방법을 사용하다가 DTO 객체를 사용하는 방법으로 갈아탔는데, 이 이유는 Entity를 그대로 반환하게 되니까 필요 없는 정보까지 다 나오게 되는 경우가 있었다.
자바 컬렉션 List, set, map에 대한 설명
List : 순서가 있는 데이터의 집합으로 데이터의 중복을 허용한다.
Set : 순서를 유지하지 않는 데이터의 집합으로 데이터의 중복을 허용하지 않는다.
Map : 키, 값으로 이루어진 데이터의 집합으로, 순서는 유지되지 않으며 키의 중복을 허용하지 않으나 값의 중복은 허용한다.
스프링 자세하게 DI에 대한 설명, 생성자 injection이 좋은 이유
필드주입을 사용하게 되면 배터리 일체형 핸드폰과 같다고 볼 수 있다 반면에 setter와 생성자 주입은 분리형으로 볼 수 있어 조금 더 유연하다
테스트 코드에 대한 설명??
단위테스트를 사용하면 좋은점은 개발 초기에 문제를 발견할 수 있다.
ORM에 대한 설명
ORM이란 객체와 DB테이블이 매핑을 이루는 것을 말한다. 즉 객체가 테이블이 되도록 매핑 시켜주는 것을 말함
ORM을 이용하면 SQL Query가 아닌 직관적인 코드(메서드)로서 데이터를 조작할 수 있습니다.
spring security
스프링 기반의 어플리케이션의 보안(인증과 권한)을 담당하는 프레임워크이다.
세션-쿠키 방식으로 인증한다.
jwt
JWT 방식은 확장성에 큰 강점을 가진다. 만약 세션을 사용하는 경우, 서버를 확장할 때마다 각 서버에 세션 정보를 저장하게 된다. 이렇게 될 경우, 특정 서버에서 로그인 인증을 받을 때 다른 서버에서는 로그인을 했는지 알 수 없다는 단점이 있다.
OAuth2
OAuth (OpenID Authentication) 란, 타사의 사이트에 대한 접근 권한을 얻고 그 권한을 이용하여 개발할 수 있도록 도와주는 프레임워크다. 구글, 카카오, 네이버 등과 같은 사이트에서 로그인을 하면 직접 구현한 사이트에서도 로그인 인증을 받을 수 있도록 되는 구조다.
OAuth2 로그인을 사용한다면 UsernamePasswordAuthenticationFilter
대신 OAuth2LoginAuthenticationFilter
가 호출되게 해야한다.
oauth는 세션대신 토큰을 사용하여 인증 진행 (토큰을 또 jwt 토큰으로 바꿔서 사용 많이함)