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에게 일을 시키는 쪽에서 커밋/롤백 한다!
더 깊게 들어가면 : 프록시 패턴을 이해하면 된다.