7. LocalThread & Transaction ( Runnable )

배고픈 징징이 ㅣ 2023. 2. 13. 16:31

1. 설명

이제 LocalThread를 이용해 사용자 정보를 저장하고 사용한다.

Filter에서 LocalThread의 초기화를 선언하여 초기화를 진행하고, 사용자의 정보를 LocalThread의 User에 저장한다.

그리고 트렌젝션 처리 여부를 결정하는 Transactional 어노테이션이 있으면

Transaction의 run() 메소드를 실행시켜 runnable 인자로 실행할 메소드를 전달한다.

 

LocalThread의 설명은 아래 포스팅 참조

 

ThreadLocal

1. ThreadLocal 이란? 쓰레드 단위로 로컬 변수를 할당하는 기능 쓰레드 영역에 변수를 설정하기 때문에, 해당 쓰레드가 실행하는 모든 코드에서 그 값을 사용할 수 있다. ThreadLocal에 보관된 데이터

pinokio5600.tistory.com

 

2. LocalThread

LocalThread에 사용자 정보와 트렌젝션 관련 정보를 관리하는 로직을 만들고 Filter에서 호출한다.

 

CallContext.java

public class CallContext {
    static ThreadLocal<CallContext> threadLocal = new ThreadLocal<>();
    User user;
    Transaction transaction = new Transaction();

    public static void start() {
        if(threadLocal.get() == null) threadLocal.set(new CallContext());
    }

    public static void end() {
        threadLocal.remove();
    }

    public static void setUser(User user) {
        threadLocal.get().user = user;
    }

    public static User user() {
        return threadLocal.get().user;
    }

    public static Transaction getTransaction(){
        if(threadLocal.get() == null) return null;
        return threadLocal.get().transaction;
    }
}

 

FrontFilter.java

...

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    ...

    try {
        CallContext.start();

        ...
    } finally {
        CallContext.end();
    }
}
    
...

 

2. Runnable vs Thread

Runnble

  • Functional Interface ( 함수형 인터페이스 ) 이다. → 람다식 사용 가능
  • 추상 메소드 run()을 반드시 구현해야 한다.
  • Thread를 구현할 때 사용된다.
  • 다른 Interface를 추가로 구현할 수 있고, 다른 클래스도 상속받을 수 있다.
    Thread를 사용하려면 Thread 클래스를 상속 받아야하기 때문에, 다른 클래스를 상속받을 수 없다.

 

Thread의 Start() & Runnable의 Run()

  • start() : Thread를 새롭게 생성해서 해당 스레드를 run() 메소드 실행
  • run() : Thread 클래스 내부의 run() 메소드 실행 → 기존 Thread를 그대로 사용하여, 새로운 스레드 생성 X

 

3. Transaction & Runnable

Transaction을 Runnable 인터페이스로 관리해주는 클래스를 만든다.

begin() 메소드에서는 Connection을 가져오고, 해당 Connection의 Transaction 처리를 위해 AutoCommit 옵션을 False로 꺼둔다. 또한 TransactionStatus를 Y로 변경한다.

Exception 발생시 Catch 구문에서 Rollback을 진행하고,

Transaction 사용 완료시에는 Connection을 닫아주고 TransactionStatus를 N으로 변경한다.

 

Transaction.java

public class Transaction {
    Connection connection = null;
    
    //TransactionStatus는 Enum으로 Y, N 두개의 값을 가진다.
    public TransactionStatus status = TransactionStatus.N;

    public static void run(Runnable runnable){
        var transaction = CallContext.getTransaction();
        if(transaction == null) throw new IllegalStateException();

        try{
            transaction.begin();
            runnable.run();
            transaction.commit();
        }catch (Exception e){
            transaction.rollBack();
            throw new RuntimeException(e);
        }finally {
            transaction.close();
        }
    }

    public void begin(){
        try {
            connection = Sqls.getConnection();
            connection.setAutoCommit(false);
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            status = TransactionStatus.Y;
        }
    }

    public void commit(){
        try {
            if(connection != null) connection.commit();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void rollBack(){
        try {
            if(connection != null) connection.rollback();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void close(){
        try {
            if(connection != null) connection.close();
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            status = TransactionStatus.N;
        }
    }

    public Connection getConnection(){
        return connection;
    }
}

 

4. Connection 관리

DB Connection을 관리해주는 Sqls 클래스를 수정한다.

Transaction.java에서 begin() 메소드를 호출했을 시

Transaction이 있고 Transaction이 필요하다면  LocalThread에 저장되어있는 Transaction을 반환하고,

그 외에는 새로 생성한 Connection을 반환한다.

Close 메소드는 트렌젝션 처리를 하는 경우에는 건너뛰고,

트렌젝션 처리를 하지 않는 경우에만 close를 내려준다.

 

Sqls.java

public class Sqls {
    ...
    
    public static Connection getConnection() {
        try {
            Transaction transaction = CallContext.getTransaction();
            if(transaction != null && transaction.status == TransactionStatus.Y) return transaction.getConnection();

            Context context = new InitialContext();
            DataSource dataSource = (DataSource) context.lookup("java:comp/env/jdbc/mysql");
            return dataSource.getConnection();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void close (Connection connection){
        Transaction transaction = CallContext.getTransaction();
        if(transaction != null && transaction.status == TransactionStatus.Y) return ;

        try {
            connection.close();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    
    ...
}

 

5. 사용

이제 Transaction처리가 필요한 메소드에서는 @Transactional 어노테이션을 사용한다.

어노테이션을 생성하는 코드는 전 포스팅에서도 작성했었기에 생략한다.

Sqls.close 를 finally 구문에 넣었는데,

@Transactional 어노테이션이 있을 경우에는 close하지 않고

@Transactional 어노테이션이 없는 경우에만 close를 해주는 메소드이다.

 

Try With Resource의 설명은 아래 참조

 

Try-With-Resources

1. 설명 try with resouces 기능은 Java7 이후에 추가된 메소드이다. try구문에 리소스를 선언하고. try문이 끝나면 자동으로 반납(Close)해주는 기능이다. Java7 이전에는 finally구문에서 close 해주었지만, 이

pinokio5600.tistory.com

 

FrontFilter.java

...

if (method.isAnnotationPresent(Transactional.class)) {
    Transaction.run(() -> {
        invokeMethod(method, controller, parameters, response);
    });
} else {
    invokeMethod(method, controller, parameters, response);
}

...

 

구현부 참조

//@Transactional에서 close가 있기에 Try With Resources 는 제거해주고,
//@Trnasactional이 없는 경우를 위해 finally에서 Sqls.close() 메소드를 호출해준다.
//Sqls.close() 메소드는 @Transactional 일 경우 close를 해주지 않는다.
boolean remove(Class<? extends Model> modelType, long id) {
    Connection connection = null;
    try{
        connection = Sqls.getConnection();
        
        ...
    } catch (Exception e) {
        throw new RuntimeException(e);
    }finally{
        Sqls.close(connection);
    }
}
반응형

'Java > - Pure Java Project' 카테고리의 다른 글

9. Fetch & XMLHttpRequest  (0) 2023.03.06
8. Query Generator  (0) 2023.02.16
6. Annotation  (0) 2023.02.09
5. JNDI : DB Connect  (0) 2023.02.08
4. Router (Dynamic Import & Create Class)  (0) 2023.02.07