mybatis에서 트랙잭션은 sql세션단위로 관리를한다.
TaskDao를 통해 작업을 삭제한다.ProjectDao를 통해 프로젝트 멤버와 프로젝트를 삭제한다.그러나 작업 삭제는 다른 트랜젝션에서 수행한다.
컴파일러는 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의 각 메서드는 작업을 수행하기 위해 현재 별도의 sqlSession객체를 사용한다. 트랜젝션은 SqlSession객체에서 제어하고 있다.
즉 DAO각 메서드마다 트랜젝션이 분리되어있다.
실습한 상황에서처럼 DAO각 메서드마다 트랜젝션이 분리되어있으면 여러 DAO를 묶어서 한 단위로 작업할 수 없는 문제가 발생한다.
DAO의 각 메서드가 통제하지 않도록 만든다. 트랜젝션은 누가 관리하는가? DAO를 사용하는 Command객체가 통제하게 한다. 즉, 트랜젝션 통제권을 DAO를 사용하는 객체로 넘기는것이다.
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에게 일을 시키는 쪽에서 커밋/롤백 한다!
더 깊게 들어가면 : 프록시 패턴을 이해하면 된다.