2020-11-16 TIL
«««< HEAD
PMS
트랜잭션
- 트랜잭션(Transaction)은 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들을 의미한다
트랜잭션 다루기
- sqlsession 단위로 관리한다.
- projectDeleteCommand에서 TaskDaoImpl와 PorjectDaoImpl에는 각각 sqlSession을 얻어서 작업을 수행하는데
- TaskDaoImpl는 프로젝트의 작업을 자동 커밋으로 삭제하고
- ProjectDaoImpl는 프로젝트의 멤버를 먼저 삭제하고 프로젝트를 삭제할 때 수동 커밋으로 삭제한다(한 트랙션으로 묶여있다)
- 이 경우 두 개의 작업은 하나의 트랜잭션으로 묶여있지 않게 된다.
- 작업 삭제는 다른 트랜잭션에서 수행한다.
- 만약 프로젝트 삭제 중에 예외가 발생한다면, 프로젝트 멤버 삭제는 자동 취소되어 롤백하지만
- 같은 트랜잭션에 묶여있지 않은 작업 삭제는 취소되지 않는다.
- project를 삭제할 때 projectDao에서 일부러 예외를 발생시키면 commit 이 되지 않는다.
- 작업은 삭제되었지만 멤버는 그대로 남아있다.
- 다른 sqlsession을 통해서 수행한 것은 그대로 실행되었다.
- TaskDaoImpl.deleteByProjectNo()에서 사용한 sqlsession 객체와 ProjectDaoImpl.delete()에서 사용한 sqlsession 객체가 다르다.
- mybatis에서는 각 sqlsession이 트랜잭션을 관리한다.
- Dao 객체에서 트랜잭션을 다루면 안되는 이유
- Dao의 각 메서드는 작업을 수행하기 위해 현재 별도의 sqlSessison 객체를 사용한다.
- 트랜잭션은 sqlSession 객체에서 제어한다.
- 즉 Dao 각 메서드마다 트랜잭션이 분리되어 있다.
- Dao의 각 메서드마다 트랜잭션이 분리되어 있으면 여러 Dao를 묶어서 한 단위로 작업할 때 통제할 수 없는 문제가 발생한다.
- 해결?
- Dao의 각 메서드가 트랜잭션을 통제하지 않도록 만든다.
- 그럼 누가 트랜잭션을 통제하는가?
- Dao를 사용하는 command 객체가 통제하게 한다.
- 즉, 트랜잭션 통제권을 Dao를 사용하는 객체로 넘긴다.
해결책
- Command 객체에서 트랜잭션을 통제한다.
- Command 객체에서 Dao에 작업을 call 하면 Dao가 openSession을 sqlSessionFactory에 요청한다. sqlSessionFactory가 sqlSession을 리턴해주면 Dao는 그 sqlSession을 call 해서 작업을 수행한다.
- 그 다음 다른 Dao를 call해서 같은 작업을 반복하는데 이때 호출되는 sqlsession은 기존에 호출된 sqlsession과 같은 객체이다.
- 이렇게 하면 작업을 하나의 트랜잭션으로 묶을 수 있다.
- Command 객체에서 Dao에 작업을 call 하면 Dao가 openSession을 sqlSessionFactory에 요청한다. sqlSessionFactory가 sqlSession을 리턴해주면 Dao는 그 sqlSession을 call 해서 작업을 수행한다.
Froxy 디자인 패턴
- https://yaboong.github.io/design-pattern/2018/10/17/proxy-pattern/
- https://jdm.kr/blog/235
- 똑같은 인터페이스, 수퍼 클래스를 상속받아서 똑같이 구현하고 원하는 기능을 추가한다.
// 역할
// 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을 사용하여 수행한 모든 작업을 commit한다.
if (currentSqlSession != null) {
startingTransaction = false;
currentSqlSession.commit();
currentSqlSession = null;
// 트랜잭션 작업을 종료한 후에는 SqlSession객체를 제거한다.
}
}
public void rollback() {
// 같은 SqlSession을 사용하여 수행한 모든 작업을 commit한다.
if (currentSqlSession != null) {
startingTransaction = false;
currentSqlSession.rollback();
currentSqlSession = null;
// 트랜잭션 작업을 종료한 후에는 SqlSession객체를 제거한다.
}
}
.
.
.
}
sqlSessionFactory에 Froxy 만들기
- 기존 클래스(기존 코드)에 손대지 않고 기능을 변경하는 방법
- 기존 클래스 행세를 하기
- 실제는 기존 코드 재활용
- 상황에 따라 여러 개의 Dao에서 수행한 작업을 한 트랜잭션으로 묶어서 다룰 경우가 있다.
- 이런 상황에서 여러개의 Dao에서 수행한 작업을 한 트랜잭션으로 묶어서 다룰 경우 트랜잭션 제어는 Command 객체에서 한다.
- Dao 구현체에서 오토 커밋을 모두 수동으로 바꿔야 한다.
Business Layer
- 클라이언트 -> 커맨드 -> DAO -> 마이바티스 -> DBMS
- Command
- UI 처리
- 업무 로직 처리
- 트랜잭션 제어
- 너무 많은 일을 하고 있기 때문에
- Command와 DAO 사이에 Service 클래스를 추가해서 업무 로직 처리와 트랜잭션 제어를 이관한다.
- 클라이언트 -> 커맨드 (/Presentaion Layer) -> Service (/Business Layer) -> DAO -> 마이바티스 ->DBMS (/Persistance Layer = 데이터의 지속성을 관리하는 레이어)
- Service 와 Dao 를 묶어서 Model 이라고 하고 Command를 Controller, view 라고도 한다.
- 클래스의 코드의 재사용성을 높이기 위해서 도입힌다.