Transaction, Commit, Rollback

 

1. Transaction이란?

 

더 이상 나눌 수 없는 작업의 단위. (insert, update, select 등...)

 

계좌 이체의 경우, 출금과 입금이 하나의 Tx로 묶여야 됨.

 

'모' 아니면 '도'(all or nothing). 출금과 입금이 모두 성공하지 않으면 실패

 

 

2. Transaction의 속성 - ACID

 

원자성(Atomicity) - 나눌 수 없는 하나의 작업으로 다뤄져야 한다.

일관성(Consistency) - Tx 수행 전과 후가 일관된 상태를 유지해야 한다.

고립성(Isolation) - 각 Tx는 독립적으로 수행되어야 한다.

영속성(Durability) - 성공한 Tx의 결과는 유지되어야 한다.

 

 

3. 커밋(commit)과 롤백(rollbock)

 

커밋(commit) - 작업 내용을 DB에 영구적으로 저장

롤백(rollback) - 최근 변경사항을 취소(마지막 커밋으로 복귀)

 

 

4. 자동 커밋과 수동 커밋

자동 커밋 - 명령 실행 후, 자동으로 커밋이 수행(rollback불가)

 

수동 커밋 - 명령 실행 후, 명시적으로 commit 또는 rollback을 입력해야 함.

 

5. Tx의 isolation level

 

READ UNCOMMITED : 커밋되지 않은 데이터도 읽기 가능

READ COMMITED : 커밋된 데이터만 읽기 가능

REPEATABLE READ : Tx이 시작된 이후 변경은 무시됨 (default)

SERIALIZABLE : 한번에 하나의 Tx만 독립적으로 수행

 

 

- READ UNCOMMITED : 커밋되지 않은 데이터도 읽기 가능

a=2 b=200이 커밋이 되지 않았음에도 SELECT a, b From tmp; 에 의해 읽기가 가능함.

dirty read라고도 부름

 

 

 

- READ COMMITED : 커밋된 데이터만 읽기 가능

Tx1 입장에서는 다른 Tx (Tx2)가 커밋한 데이터가 생겼기 때문에 귀신이 넣었다는 표현

phantom read라고도 부름.

 

 

 

- REPEATABLE READ : Tx이 시작된 이후 변경은 무시됨 (default)

다른 Tx에서 데이터를 넣고 커밋을 해도, 이미 시작된 Tx1에는 영향이 없음.

 

 

 

- SERIALIZABLE : 한번에 하나의 Tx만 독립적으로 수행

isolation level이 가장 높음. 직렬화 되어있음.

병렬처리시 data가 품질이 떨어질 가능성이 있음.

 

(SERIALIZABLE 에서도 다른 Tx가 읽기는 가능함. SELECT는 가능)

 

Tx1이 먼저 진행중이기 때문에, Tx2의 INSERT 작업이 진행이 안 되고 기다림.

Tx1의 작업이 다 끝나면 Tx2의 INSERT 작업이 실행됨. 

 

 

 @Test
    public void transactionTest() throws Exception {
        Connection conn=null;

        try {
            deleteAll();
            conn = ds.getConnection();
            conn.setAutoCommit(false); // conn.setAutoCommit(true);

//        insert into user_info (id, pwd, name, email, birth, sns, reg_date)
//        values ('asdf22','1234','smith','aaa@aaa.com', '2021-01-01', 'facebook', now());

            String sql = "insert into user_info values (?,?,?,?,?,?, now())";

            PreparedStatement pstat = conn.prepareStatement(sql);

            pstat.setString(1, "asdf");
            pstat.setString(2, "1234");
            pstat.setString(3, "abc");
            pstat.setString(4, "aaa@aaa.com");
            pstat.setDate(5, new java.sql.Date(new Date().getTime()));
            pstat.setString(6, "fb");

            int rowCnt = pstat.executeUpdate(); // insert, selecte, update

            pstat.setString(1, "asdf");
            rowCnt = pstat.executeUpdate(); // insert, selecte, update

            conn.commit();

        } catch (Exception e) {
            conn.rollback();
            e.printStackTrace();
        } finally {

        }


    }

java 코드로 커밋을 할 수 있다.

 

먼저 autocommit 설정을 false로 하면, 수동으로 commit 메소드를 호출해야 커밋이 된다.

데이터를 저장하다가 예외가 발생하면 rollback이 되도록 try catch문을 작성하였다.

예외가 발생하지 않으면 commit 메소드에 의해 commit이 된다.

예제의 경우 id가 동일하게 업데이트를 하게 되면 예외가 발생하여 rollback이 되고 아무런 데이터도 저장이 되지 않는다.

 

반대로 autocommit 설정을 true로 하게되면, 매번 executeUpdate마다 오토커밋이 되기 때문에 

처음 데이터는 정상적으로 저장이 되고, 다시 같은 id로 업데이트를 하게 되면 해당 업데이트만 반영이 되지 않는다.

 

 

AOP의 개념과 용어

 

1. 공통 코드의 분리

package com.fastcampus.ch3.aop;

import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AopMain {

    public static void main(String[] args) throws Exception {
        MyAdvice myAdvice = new MyAdvice();

        Class myClass = Class.forName("com.fastcampus.ch3.aop.MyClass");
        Object obj = myClass.newInstance();

        for(Method m : myClass.getDeclaredMethods()) {
            myAdvice.invoke(m, obj, null);
        }
    }
}
class MyAdvice {
    Pattern p = Pattern.compile("a.*");

    boolean matches(Method m) {
        Matcher matcher = p.matcher(m.getName());
        return matcher.matches();
    }

    void invoke(Method m, Object obj, Object... args) throws Exception {
        if(m.getAnnotation(Transactional.class)!=null)
            System.out.println("[before]{");

        m.invoke(obj, args); // aaa(), aaa2(), bbb() 호출 가능

        if(m.getAnnotation(Transactional.class)!=null)
            System.out.println("}[after]");

    }
}
class MyClass {
    @Transactional
    void aaa() {
        System.out.println("aaa() is called.");
    }

    void aaa2() {
        System.out.println("aaa2() is called.");
    }

    void bbb() {
        System.out.println("bbb() is called.");
    }
}

핵심기능과 부가기능을 따로 나눠놓음

 

 

2. 코드를 자동으로 추가한다면, 어디에?

코드의 맨 앞(Before advice), 코드의 맨 뒤(After advice), 코드의 맨 앞과 뒤(Around advice)

 

메소드 중간에는 추가할 수 없다.

 

3. AOP(Aspect Oriented Programming)란?

 

관점 지향 프로그래밍? 횡단 관심사? cross-cutting concerns?

 

부가 기능(advice)을 동적으로 추가해주는 기술

메소드의 시작 또는 끝에 자동으로 코드(advice)를 추가해주는 기술

 

4. AOP 관련 용어

target에는 핵심기능만 남겨놓고, 별도의 부가기능은 advice에 따로 나눠놨다가 실행중에 하나로 합쳐짐

advice + target = proxy,

합치는 것을 weaving 이라고 함.

 

class MyAdvice {
    Pattern p = Pattern.compile("a.*");

    boolean matches(Method m) {
        Matcher matcher = p.matcher(m.getName());
        return matcher.matches();
    }

    void invoke(Method m, Object obj, Object... args) throws Exception {
        if(m.getAnnotation(Transactional.class)!=null)
            System.out.println("[before]{");

        m.invoke(obj, args); // aaa(), aaa2(), bbb() 호출 가능

        if(m.getAnnotation(Transactional.class)!=null)
            System.out.println("}[after]");

    }
}

부가 기능만을 정의해놓은 클래스 (Advice).

 

 

class MyClass {
    @Transactional
    void aaa() {
        System.out.println("aaa() is called.");
    }

    void aaa2() {
        System.out.println("aaa2() is called.");
    }

    void bbb() {
        System.out.println("bbb() is called.");
    }
}

핵심 기능만을 정의해놓은 클래스 (target이 될 객체의 클래스)

각 println문이 join point 이다.

 

 

5. Advice의 종류

Advice의 설정은 XML과 애너테이션, 두 가지 방법으로 가능

 

 

6. pointcut expression 

 

advice가 추가될 메소드를 지정하기 위한 패턴

 

 


public class LoggingAdvice {
	@Around("execution(* com.fastcampus.ch3.aop.*.*(..))")
    public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
    	long start = System.currentTimeMillis();
        System.out.println("<<[start] "
        		+ pjp.getSignature(),getName+Arrays.deepToString(pjp.getArgs()));
                
        Object result = pjp.proceed(); // 메소드 호출
        
        System.out.println("result = " + result);
        System.out.println("[end]>> "+ (System.currentTimeMillis() - start)+"ms");
        return result; // 메소드 호출결과 반환
    }
}

execution(반환타입 패키지명.클래스명.메소드명(매개변수 목록))

 

이 코드의 경우 반환타입 앞에 접근제어자가 생략되어있음. (원래 생략이 가능)

반환타입 : * (모든 반환타입이 가능하다는 의미)

패키지명 : com.fastcampus.ch3.aop

클래스명 : * (모든 클래스가 가능하다는 의미)

메서드명 : * (모든 메소드가 가능하다는 의미)

매개변수 목록 : .. (개수 상관없고 모든 타입이 가능하다는 의미)

 

ProceedingJoinPoint pjp에는 메소드의 모든정보가 들어있음

pjp.getSignature().getName() : 메소드 이름

pjp.getArgs() : 매개변수들

 

 

 

실습)

package com.fastcampus.ch3.aop;

import org.springframework.stereotype.Component;

@Component
public class MyMath {
    public int add(int a, int b) {
        int result = a+b;
        return result;
    }

    public int add(int a, int b, int c) {
        int result = a+b+c;
        return result;
    }

    public int subtract(int a, int b) {
        int result = a - b;
        return result;
    }

    public int multiply(int a, int b) {
        int result = a * b;
        return result;
    }
}
package com.fastcampus.ch3.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class LoggingAdvice {
    @Around("execution(* com.fastcampus.ch3.aop.MyMath.*(..))") // pointcut - 부가기능이 적용될 메소드의 패턴
    public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("<<[start] "+pjp.getSignature().getName()+ Arrays.toString(pjp.getArgs()));

        Object result = pjp.proceed(); // target의 메소드를 호출

        System.out.println("result = " + result);
        System.out.println("[end]>> "+(System.currentTimeMillis() - start)+"ms");
        return result;

    }
}
package com.fastcampus.ch3.aop;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class AopMain2 {
    public static void main(String[] args) {
        ApplicationContext ac = new GenericXmlApplicationContext("file:src/main/webapp/WEB-INF/spring/**/root-context_aop.xml");
        MyMath mm = (MyMath) ac.getBean("myMath");
        System.out.println("mm.add(3,5) = " + mm.add(3, 5));
        System.out.println("mm.multiply(3,5) = " + mm.multiply(3, 5));

    }
}

 

+ Recent posts