You are using an outdated browser. Please upgrade your browser to improve your experience.

11/16 자바프로젝트

42-a 트랜젝션 : 현재 프로젝트에서 트랜잭션 방식의 문제점

mybatis에서 트랙잭션은 sql세션단위로 관리를한다.

컴파일러는 JVM이 아니다.(런타임이 아니라는 뜻) 문법만 맞으면 일단 컴파일이 통과됨!

TaskMapper에서 findAll()메서드 하나를 가지고 다용도로 쓸 수 있게 <where>절 추가.

<select id="findAll" resultMap="TaskMap" parameterType="map">
  select
   t.no, 
   t.content, 
   t.deadline, 
   t.status, 
   t.project_no,
   m.no owner_no, 
   m.name owner_name
  from pms_task t 
   inner join pms_member m on t.owner=m.no
  <where>
    <if test="projectNo != null">
      t.projectNo=#{projectNo}
    </if>    
  </where> 
  order by t.deadline asc
  </select>

실행결과

명령> /project/delete
[프로젝트 삭제]
번호? 20
정말 삭제하시겠습니까?(y/N) y
프로젝트 삭제 중 오류 발생!
java.lang.Exception: 일부러 예외 발생

명령> 	at com.eomcs.pms.dao.mariadb.ProjectDaoImpl.delete(ProjectDaoImpl.java:46)
	at com.eomcs.pms.handler.ProjectDeleteCommand.execute(ProjectDeleteCommand.java:32)
	at com.eomcs.pms.filter.DefaultCommandFilter.doFilter(DefaultCommandFilter.java:24)
	at com.eomcs.pms.filter.CommandFilterManager$Chain.doFilter(CommandFilterManager.java:55)
	at com.eomcs.pms.filter.LogCommandFilter.doFilter(LogCommandFilter.java:24)
	at com.eomcs.pms.filter.CommandFilterManager$Chain.doFilter(CommandFilterManager.java:55)
	at com.eomcs.pms.App.service(App.java:119)
	at com.eomcs.pms.App.main(App.java:68)
/project/detail
[프로젝트 상세보기]
번호? 20
프로젝트명: okok!
내용: hihi
기간: 2020-02-02 ~ 2020-03-03
관리자: b
팀원: x1 작업: 
----------------------------
번호, 작업내용, 마감일, 작업자, 상태

예외가 발생되었지만 작업은 삭제되고 프로젝트는 삭제되지 않은 것을 알 수 있다.

왜냐? projectDaoImpl.delete()에서 사용한 sqlSession객체와 TaskDao.deleteByProjectNo()에서 사용한 sqlSession객체가 다르기 때문이다. myBatis에서는 각 sqlSession이 트랙잭션을 관리한다.

해결책은?!

DAO객체에서 트랜젝션을 다루면 안되는 이유?

DAO의 각 메서드는 작업을 수행하기 위해 현재 별도의 sqlSession객체를 사용한다. 트랜젝션은 SqlSession객체에서 제어하고 있다.

즉 DAO각 메서드마다 트랜젝션이 분리되어있다.

실습한 상황에서처럼 DAO각 메서드마다 트랜젝션이 분리되어있으면 여러 DAO를 묶어서 한 단위로 작업할 수 없는 문제가 발생한다.

해결책

DAO의 각 메서드가 통제하지 않도록 만든다. 트랜젝션은 누가 관리하는가? DAO를 사용하는 Command객체가 통제하게 한다. 즉, 트랜젝션 통제권을 DAO를 사용하는 객체로 넘기는것이다.

Proxy디자인 패턴

SqlSessionFactoryProxy생성해서 SqlSessionFactory인터페이스를 구현해 아래 명령 실행

source - delegate methods

아래 필드들을 추가한다.

// 역할
// - sqlSessionFactory 구현체의 일을 대리한다.
// - 이런 객체를 프록시라고 한다.
// - 프록시는 반드시 원래 객체와 같은 인터페이스를 구현해야 한다.
public class SqlSessionFactoryProxy implements SqlSessionFactory {
  SqlSessionFactory original;
  boolean startingTransaction = false; // 트랜젝션 시작 여부
  SqlSession currentSqlSession;

  public SqlSessionFactoryProxy(SqlSessionFactory original) {
    // 생성자에서 원래의 구현체를 받아 보관해둔다.
    this.original = original;
  }

  //기존에 없던 메서드 추가
  public void startTransaction() {
    startingTransaction = true;
  }

  @Override
  public SqlSession openSession() {
    if(startingTransaction) {
      if(currentSqlSession == null) {
        //트랜젝션이 시작 중일때는 수동 커밋 상태의 SqlSession을 만든다.
        // 나중에 다시 쓸 수 있도록 보관해 둔다.
        currentSqlSession = original.openSession();
      }
      //기존에 만든 sqlSession을 리턴해준다.
      //왜?
      //같은 SqlSession을 리턴해 줘야 여러 작업을 한 트랜젝션으로 묶을 수 있다.
      return currentSqlSession;
    } else {
      //트랜젝션이 시작중이 아닐때는 자동 커밋 상태의 SqlSession을 리턴한다.
      //따로 보관해두지는 않는다.
      return original.openSession(true);
    }
  }

  public void commit() {
    //같은 SqlSession을 사용하여 수행한 모든 작업을 커밋한다.
    if(currentSqlSession != null) {
      startingTransaction = false;
      currentSqlSession.commit();
      currentSqlSession = null;
      // 트랜젝션 작업을 종료한 후에는 SqlSession객체를 종료한다.
    }
  }

  public void rollback() {
    //같은 SqlSession을 사용하여 수행한 모든 작업을 롤백한다.
    if(currentSqlSession != null) {
      startingTransaction = false;
      currentSqlSession.rollback();
      currentSqlSession = null;
      // 트랜젝션 작업을 종료한 후에는 SqlSession객체를 종료한다.
    }
  }

Dao객체에 오리지널 sqlSessionFactory대신 프록시 객체를 주입한다.

오토커밋했던 코드를 모두 수동 커밋으로 바꿔준다.

실무에서는 절대로 자동커밋 해서는 안됀다!!!!

이해해야할것 : 트랜젝션은 DAO안에서 커밋/롤백 하면 안됀다. 왜냐면 여러 DAO를 하나의 작업으로 묶을때가 있기 때문이다. 그래서 하나의 DAO안에 커밋과 롤백을 시키면안됀다. DAO에게 일을 시키는 쪽에서 커밋/롤백 한다!

더 깊게 들어가면 : 프록시 패턴을 이해하면 된다.