[TIL] 20210827

열심히 사는 루피 🥰·2021년 8월 28일
0

데브코스 백엔드 TIL

목록 보기
16/20

2W D4

1. Spring의 트랜젝션관리

AOP : CrossCuttingConcern을 해결해주는 방법.
부가기능 : 로깅, 보안 등 각 레이어에 걸쳐서 적용할 부가적 기능
AOP
-> 레이어의 핵심기능(관심사)과 부가기능을 분리하게 해줌

각 비즈니스 로직 앞-뒤에 끼어들어갈 부가기능을 어떻게 추가할 수 있을까.

  • 컴파일시점
    AspectJ 같은 AOP 프레임워크가 소스코드를 컴파일 전에 부가기능을 소스에 삽입하는방식

  • 클래스 로딩시점
    클래스 로딩할때 바이트 코드에다가 부가기능을 삽입한다.

  • 런타임 시점
    스프링에서 제공하는 AOP 방식.
    사용할 객체의 프록시를 이용해서 프록시가 본 비지니스전에 부가기능을 호출해서 사용하는 방식.

스프링 AOP 프록시 두가지 방식
-JDK 다이내믹 프록시(인터페이스 기반) :인터페이스를 구현한 객체가 프록시의 타겟이 된다.
-CGLib 프록시(클래스 기반)

2. Spring Proxies

프록시는 일을 대신 해주는 패턴이었다.

프록시는 클라이언트의 요청을 받아서 적정한 선,후 처리후 타겟을 반환해준다.

즉. 프록시는 타겟을 감싸서 타겟의 요청을 대신 받아준다.

참고 블로그 - 그림참고

//JDK 프록시 사용예시 

//타겟 클래스 (실제)
class CalculatorImpl implements Calculator{

    @Override
    public int add(int a, int b) {
        return a+b;
    }
}

interface Calculator{
    int add(int a, int b);
}

//핸들러, 프록시가 할일 정의
class LoggingInvocationHandler implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(LoggingInvocationHandler.class);
    private final Object target;

    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        //부가 기능(로그) 먼저 실행
        log.info("{} executed",method.getName());
        //주요 기능 , 즉 진짜 일을하는 객체를 반환
        return method.invoke(target,objects);
    }
}



public class JdkProxyTest {
       public static void main(String[] args) {

        var calculator = new CalculatorImpl();
        var proxyInstance = (Calculator) Proxy.newProxyInstance(
            	LoggingInvocationHandler.class.getClassLoader(),
                new Class[]{Calculator.class},
                new LoggingInvocationHandler(calculator));

        var add = proxyInstance.add(1,2);
       
    }
}

스프링 aop 사용하기

  1. AOP 디펜던시 추가
<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. aspectJ 어노테이션을 이용해 aop기능을 사용
어노테이션을 읽고, 실제 스프링 AOP 방식으로 aspect(뒤에나옴)를 적용하게 해준다.
프록시로 구현되어 런타임에 적용되고
xml은 오래된 방식이라 aspectJ의 어노테이션 방식이 권장됨.

[추가 조사]

  • 위빙(뒤에 나옴) 과정에서 프록시객체가 생성된다.

    이 프록시 객체가 생성됨으로써 특정 조인포인트에 어드바이스가 적용된다.

  • 참고 블로그

3. Spring AoP 주요용어

타겟 (핵심기능을 가지고있는 모듈, 부가기능이 적용될 대상)

조인포인트 (부가기능이 적용될 수 있는 수많은 포인트(메서드)들 )

포인트 컷(어느 조인포인트를 선택할지 선별하는 정규표현식 : 기준)

에스펙트 : 어드바이스 + 포인트 컷, 부가기능의 set.
-> 부가기능(로깅기능)을 포함한 타겟.
-> 어떤 내용을 어디에 적용할지.
=> 스프링에서는 이 aspect를 빈으로 등록해서 사용

어드바이스(적용될 부가기능의 내용)
:Before, After,Around,After Returning, After Throwing
->어떤 조인포인트에서, 이 작업을 Before에 할지 언제할지 결정

위빙(부가기능이 적용되는 과정) == 타겟의 조인 포인트에 어드바이즈를 적용하는 과정.
-> 타겟의 특정메소드를 선정해서, 어떠한 방식을 통해, 어드바이스를 적용하는게 위빙이다.

4. Spring AoP 실습

 //지정자: 타겟의 여러 조인트 포인트중에서 어드바이스를 어떻게 적용시킬지 AOP에게 알려주는 키워드들
    //@Around("execution()") -> 메서드를 실행하는 시점에 내용을 적용하겠다.

    //public 메소드에 접근할때, 리턴 타입이 뭐든(*)
    //org.prgrms.kdt..* 해당 위치 뭐든
    //(..) 파라미터 뭐든
    //@Around("org.prgrms.kdt.aop.CommonPointCut.repositoryInsertMethodPointCut()")
    @Around("@annotation(org.prgrms.kdt.aop.TrackTime)") // 해당 에노테이션에서 부가기능을 실행하겠다.
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Before method called. {}", joinPoint.getSignature().toString());
        var startTime = System.nanoTime();
        var result = joinPoint.proceed();
        var endTime = System.nanoTime() - startTime;
        log.info("After method call with return result -> {} and time taken {} nanoseconds", result,endTime);
        return result;
    }
어드바이스들 
1. execute
2. within : 특정 클래스를 쓸 수 있음

어드바이스들을 미리 지정하고 메소드명으로 소환이 가능한 @PointCut을 쓸 수 있음.

5.Spring 트랜잭션 실습

트랜잭션 -> ACID을 유지
중간에 망가지면 -> 롤백.
성공적으로 마무리되면 중간 과정을 커밋.

6.Spring 트랜잭션 매니저 실습

트랜잭션 매니저 : 
->
트랜잭션 템플릿

7.Transactional

지금까지직접 트랜잭션 매니저, 템플릿 가지고 우리가 호출해가면서 씀.
(programatic 한 트랜잭션 매니징)

-> 스프링에서는 @Tranactional을 통해 선언적 트랜잭션 관리가 가능

//에러가 나면 롤백한다 -> 이런건 사실상 부가기능임. 주요로직이 아니다.
@Transactional이 트랜잭션관리
-> 이는 사실 aop 기술이 적용되고 그 안엔 프록시가 작동한다.
여러 db sql 을 실행하는 서비스 단에 적용해서 뭔가 잘못되면 전체 롤백이 가능하도록함.

@Trasactional 어노테이션을 달면? (아직 몰라도될듯)
1.@Trasactional을 적용한 메서드가 있는 클래스 타입의 프록시가 생성됨
2. 위처럼 스프링 AOP에 의해서 자동으로 생성된 프록시에
3. 트랜잭션 관련 기능들이(부가기능) 앞뒤로 붙게된다.

@EnableTransactionManagement
-> 실행하는 스프링프로젝트에 달아줘야 트랜잭션 관리 가능

8.트랜잭션 전파와 격리

1. 트랜잭션 전파

트랜잭션 전파 : 트랜잭션이 처리되는 과정 중에, 또다른 트랜잭션이 진행되는 것.
롤백이 이어지는 상황이 있을 수 있다.
@Trasactional(propagation= )

REQUIRED바깥에 트랜잭션 있으면 사용하고 없으면 안에서 만들어 사용
REQUIRED_NEW바깥에 트랜잭션 있던없던 안에서는 만들어 사용 (바깥 트랜잭션의 커밋 롤백에 영향을 주지 않는다.)
SUPPORTS바깥에 트랜잭션이 있으면 사용하고 없으면 안만들고 안씀
NOT_SUPPORTED바깥에 트랜잭션이 있어도 그 트랜잭션 잠시 중단시키고, 메소드 실행종료되면 다시 실행되게 함. 안에서는 안만든다.
MANDATORY실행할때 반드시 이전에 만들어진 트랜잭션이 존재해야함
NEVER트랜잭션 진행 상황에서 실행 될 수 없다. 만약 이미 진행 중인 트랜잭션이 존재하 면 예외 발생
NESTED이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함(이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함)

2. 트랜젝션 격리

@Trasactional(isolation = )
트랜잭션이 이뤄지면서 다른 트랜잭션도 이뤄질 수 있다.
따라서 이런 트랜잭션 사이가 지독하게 엮이는 일이 생길 수 있다.
-> 그러면 아직 커밋도 안된 값이 다른데서 읽힌다거나.. 제법 복잡해진다.

그래서 이런 트랜잭션이 얼마나 다른 트랜잭션에 독립적인지.
독립성의 단계를 나눈게 Trasaction isolation 레벨이다.

isolation 레벨 / 발생문제dirty readnon-repeatable readphantom reads
READ_UNCOMMITTEDYesYesYes
READ_COMMITTEDNoYesYes
REPEATABLE_READNoNoYes
SERIALIZABLENoNoNo

READ_UNCOMMITTED : 커밋 안된것도 읽음
READ_COMMITTED : 커밋된 내용만 읽음
REPEATABLE_READ : 한 트랜잭션에서 먼저 읽어진 데이터의 경우에 다른 트랜잭션에서 변경,삭제되는 걸 방지한다.
SERIALIZABLE: 한 트랜잭션에서 먼저 읽어진 데이터의 경우에 다른 트랜잭션에서 새로 insert되는 것을 방지한다/ 막는다.


dirty read : (수정중인 데이터를 읽을 수 있는가?) 커밋되지 않은 것도 읽으므로, 롤백되어도 이미 읽어버린 후라서 돌이킬수없음. 지저분하게 읽게됨

non-repeatable read : 한트랜잭션에서 앞-뒤로 같은 쿼리값을 읽는데 중간에 다른 트랜잭션에서 값을 바꾸면 앞-뒤 쿼리의 결과가 다름.
일관성 없는 읽기.

phantom reads : 한트랜잭션에서 앞-뒤로 같은 쿼리값을 읽는데 중간에 다른 트랜잭션에서 값을 insert하면 앞-뒤 쿼리의 결과가 다름. 갑자기 뭐가 새로 생김.
없었는데? 있었습니다.


@Trasactional(isolation = )로 수준을 설정해줄 수 있다.

  • default: 사용하는 DB에서 설정한 아이솔레이션 레벨을 쓰겠다.
    (mysql : SELECT @@SESSION.transaction_isolation 쿼리로 확인이 가능)
profile
반가워_! 세상아!

0개의 댓글