개발을 하다보면 어느 순간 쿼리 한줄로는 끝낼 수 없는 상황에 부딪치게 되는데
이렇게 단일 쿼리로 해결할 수 없는 로직을 처리할 때 필요한 기술이 바로 transaction이다.
2개 이상의 쿼리를 하나의 커넥션으로 묶어 DB에 전송하고 이 과정에서 어떠한 에러가 발생할 경우
자동으로 모든 과정을 원래 상태로 돌려놓는다.
이 로직을 JAVA에서 처리한다.
transaction은 "쪼개질 수 없는 업무처리의 단위"를 의미한다.
예를 들어, ATM으로 계좌이체를 하면 내 계좌의 잔액에서 이체한 금액만큼 빼는 일과, 상대 계좌의 잔액에서 해당 금액만큼 더하는 일은 쪼개져서는 안된다.
즉 하나의 업무로 함께 진행되야 하는 일이다.
"더이상 쪼갤 수 없다는" 표현 때문에 이를 원자성을 보장해야 한다고 얘기한다.
참고로, transaction에는 4가지 특성이 있다.
원자성Atomicity, 일관성consistency, 고립성Isolation, 지속성durability
쪼갤 수 없기 때문에 일부만 동작해선 안된다.
부분 작업들 여러 개가 모여진 이러한 transaction을 처리하기 위해 DB는 다음과 같은 기술을 제공한다.
-롤백(Rollback): 부분 작업이 실패하면 transaction 실행 전으로 되돌린다.
-커밋(Commit): 모든 부분작업이 정상적으로 완료하면 이 변경사항을 한꺼번에 DB에 반영한다.
++첫 번째, Custom Exception으로 transaction거는방법!
내가 만든 Exception을 사용하여 강제적 rollback을 하고싶으면 이 방법을 사용한다.
반드시 try catch가 걸려있어야 한다.
catch영역에다가 TransactionAspectSupport.currentTransactionStatus( ).setRollbackOnly( ); 적어준다.
이렇게 작성해 주면 exception 발생 시 해당메서드를 rollback 시킬수 있게 된다. TransactionAspectSupport.currentTransactionStatus( ).setRollbackOnly( )가 실행될 때
바로 rollback이 진행되지는 않는다.
setRollbackOnly( )는 속성만 변경을 하는 것이기 때문에 실제 rollback이 일어나는 시점은
commit이 되기 직전에 수행되게 된다.
ex)
package egovframework.example.join.service.impl;
@Service
public class JoinServiceImpl implements JoinService {
@Resource
private JoinMapper joinMapper;
@Override
public void saveJoinMbr(JoinVO joinVO) {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
for (int i = 0; i < termsMngList.size(); i++) {
Integer termsMngNo = (Integer) termsMngList.get(i).get("termsMngNo");
joinMapper.insertTermsAgr();
throw new Exception();
}
} catch (Exception e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
그리고 context-transaction.xml에 가서 sample이란 업무폴더만 transaction이 적용되게 되어있다.
여기서 **바꿔주면 모든 업무에 transaction이 걸린다.
ex)
<aop:config>
<aop:pointcut id="requiredTx" expression="execution(* egovframework.example.sample..impl.*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="requiredTx" />
</aop:config>
--------------------------------------------------------------------------------------------------------
<aop:config>
<aop:pointcut id="requiredTx" expression="execution(* egovframework.example.**..impl.*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="requiredTx" />
</aop:config>
프로그램을 실행하고 오류가 나지 않는다면 try~catch구문을 지워준다.
++ 두 번째, AOP 방식으로 transaction 거는방법이다.
context-transaction.xml에 가서 서비스명으로 트랜잭션을 거는 방법이다.
ex)
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="requiredTx" expression="execution(* egovframework.example.**..impl.*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="requiredTx" />
</aop:config>
---------------------------------------------------------------------------------------------
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*Tx" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="requiredTx" expression="execution(* egovframework.example.**..impl.*Impl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="requiredTx" />
</aop:config>
그리고 관련 메서드명 뒤에 context-transaction.xml에서 만든 서비스명을 붙여준다. throws Exception도 붙여준다.
ex)
package egovframework.example.join.service.impl;
@Service
public class JoinServiceImpl implements JoinService {
@Resource
private JoinMapper joinMapper;
@Override
public void saveJoinMbr(JoinVO joinVO) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
for (int i = 0; i < termsMngList.size(); i++) {
Integer termsMngNo = (Integer) termsMngList.get(i).get("termsMngNo");
joinMapper.insertTermsAgr();
}
}
}
---------------------------------------------------------------------------------------------------------
package egovframework.example.join.service.impl;
@Service
public class JoinServiceImpl implements JoinService {
@Resource
private JoinMapper joinMapper;
@Override
public void saveJoinMbrTx(JoinVO joinVO) throws Exception{
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
for (int i = 0; i < termsMngList.size(); i++) {
Integer termsMngNo = (Integer) termsMngList.get(i).get("termsMngNo");
joinMapper.insertTermsAgr();
}
}
}
++세 번째는 선언전 transaction을 거는 방법이다.
@transaction을 사용한다. 어노테이션 방식의 경우 선언으로 지정할 수 있다. (@ : 어노테이션)
먼저, DataSourceTransaction클래스의 tx-annotaion-driven아이디를 기입한다.
<tx:annotation-driven 뒤에 transaction-manager="bean id"/>로 만들어주는 것이다.
그리고 @Transactiona에도 bean id를 지정해준다.
ex)
<!-- datasource가 있는 xml-->
<tx:annotation-driven transaction-manager="myTransactionManager"/>
<bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="egov.dataSource" />
</bean>
-----------------------------------------------------------------------------------------------
/* transaction 사용할 자바 클래스 */
@Transaction("myTransactionManager")
public int testMethod(){
int myCount = testService.insertMy;
return myCount;
}
선언적 transaction을 사용할 때 checked예외가 발생하면 rollback을 하지 않는다.
Q. 왜 Spring에서는 checked예외일 때 rollback을 하지 않을까?
A. 그 이유는 Spring의 예외전략은 unchecked예외다.
기본 java에서는 데이터베이스에 쿼리를 보내거나 연결을 할 때 error가 발생하면 checked예외를 던진다.
보통 일반적으로 SqlException을 던지고 이 예외는 checked예외다.
하지만 Spring에서 checked예외를 unchecked 예외로 포장해서 우리에게 던져준다.
그 이유는 데이터베이스에 예외가 났을 경우 거의 99프로가 복구 할 수 없는 예외이다.
개발자의 잘못으로 쿼리가 잘 못 작성되었거나 서버가 죽어있다면 복구 불가능한 예외이다.
그래서 Spring에서는 복구할 수 없는 예외로 판단하여 굳이 checked예외를 던지지 않고 unchecked예외로 포장해서 던져준다.
이 뿐만 아니라 Spring에서는 거의 대부분 unchecked예외를 던지지 checked예외를 던지는 경우는 드물다.
대부분의 웹 프로그래밍을 하면 checked예외는 사용하지 않겠지만 라이브러리, 프레임워크 등 코어 개발자라면
checked예외를 종종 사용한다.
예를들어 꼭 알려야 하는 예외(파일을 쓰지 못한다거나, 서버와 커넥션이 안되었거나 기타 등등)가 발생하였을 경우에는 checked예외를 사용해서 상위 메서드에게 알려줘야 한다. 우리는 예외를 잡아서 포장해서 던지면 된다.
출처 : 한큐의 자바 수강내용
출처: https://sjh836.tistory.com/11 [빨간색코딩]
출처 : http://egloos.zum.com/springmvc/v/495798
출처: https://krespo.net/159 [KRESPO.NET]
'웹 > Debugging' 카테고리의 다른 글
개발자도구에서 디버깅(debugging) 하기 (0) | 2019.06.09 |
---|